From e593ded8e069eac961aa8adcb16a1b4139a7dfc1 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 5 Nov 2024 10:37:29 -0500 Subject: [PATCH 01/11] removing dot seperator in path (#15120) --- core/services/registrysyncer/monitoring.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/services/registrysyncer/monitoring.go b/core/services/registrysyncer/monitoring.go index ef317a1f1dc..97fd181515c 100644 --- a/core/services/registrysyncer/monitoring.go +++ b/core/services/registrysyncer/monitoring.go @@ -21,7 +21,7 @@ func initMonitoringResources() (err error) { return fmt.Errorf("failed to register sync failure counter: %w", err) } - launcherFailureCounter, err = beholder.GetMeter().Int64Counter("platform_registrysyncer_launch.failures") + launcherFailureCounter, err = beholder.GetMeter().Int64Counter("platform_registrysyncer_launch_failures") if err != nil { return fmt.Errorf("failed to register launcher failure counter: %w", err) } From fb4b5269283967bd04112995afb4a9fbfcede6c4 Mon Sep 17 00:00:00 2001 From: Gabriel Paradiso Date: Tue, 5 Nov 2024 17:00:13 +0100 Subject: [PATCH 02/11] [CAPPL-213] Workflow keystore for secrets management (#15057) * feat: implement encryptionkey * feat: implement keystore * fix: update master mock * fix: lint * feat create encryption key if capabilities registry is enabled * chore: remove debug line * chore: rename encryption to workflowEncryption * chore: address name changes * chore: rename WorkflowEncryption to Workflow --- core/cmd/shell_local.go | 7 + .../keystore/keys/workflowkey/export.go | 44 +++++ .../services/keystore/keys/workflowkey/key.go | 107 +++++++++++ .../keystore/keys/workflowkey/key_test.go | 88 +++++++++ core/services/keystore/master.go | 10 + core/services/keystore/mocks/master.go | 47 +++++ core/services/keystore/models.go | 3 + core/services/keystore/workflow.go | 163 ++++++++++++++++ core/services/keystore/workflow_test.go | 178 ++++++++++++++++++ 9 files changed, 647 insertions(+) create mode 100644 core/services/keystore/keys/workflowkey/export.go create mode 100644 core/services/keystore/keys/workflowkey/key.go create mode 100644 core/services/keystore/keys/workflowkey/key_test.go create mode 100644 core/services/keystore/workflow.go create mode 100644 core/services/keystore/workflow_test.go diff --git a/core/cmd/shell_local.go b/core/cmd/shell_local.go index 3fca9d6ad09..689e7d27d26 100644 --- a/core/cmd/shell_local.go +++ b/core/cmd/shell_local.go @@ -474,6 +474,13 @@ func (s *Shell) runNode(c *cli.Context) error { } } + if s.Config.Capabilities().Peering().Enabled() { + err2 := app.GetKeyStore().Workflow().EnsureKey(rootCtx) + if err2 != nil { + return errors.Wrap(err2, "failed to ensure workflow key") + } + } + err2 := app.GetKeyStore().CSA().EnsureKey(rootCtx) if err2 != nil { return errors.Wrap(err2, "failed to ensure CSA key") diff --git a/core/services/keystore/keys/workflowkey/export.go b/core/services/keystore/keys/workflowkey/export.go new file mode 100644 index 00000000000..bb0430a6701 --- /dev/null +++ b/core/services/keystore/keys/workflowkey/export.go @@ -0,0 +1,44 @@ +package workflowkey + +import ( + "github.com/ethereum/go-ethereum/accounts/keystore" + + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +const keyTypeIdentifier = "Workflow" + +func FromEncryptedJSON(keyJSON []byte, password string) (Key, error) { + return keys.FromEncryptedJSON( + keyTypeIdentifier, + keyJSON, + password, + adulteratedPassword, + func(_ keys.EncryptedKeyExport, rawPrivKey []byte) (Key, error) { + return Raw(rawPrivKey).Key(), nil + }, + ) +} + +func (k Key) ToEncryptedJSON(password string, scryptParams utils.ScryptParams) (export []byte, err error) { + return keys.ToEncryptedJSON( + keyTypeIdentifier, + k.Raw(), + k, + password, + scryptParams, + adulteratedPassword, + func(id string, key Key, cryptoJSON keystore.CryptoJSON) keys.EncryptedKeyExport { + return keys.EncryptedKeyExport{ + KeyType: id, + PublicKey: key.PublicKeyString(), + Crypto: cryptoJSON, + } + }, + ) +} + +func adulteratedPassword(password string) string { + return "workflowkey" + password +} diff --git a/core/services/keystore/keys/workflowkey/key.go b/core/services/keystore/keys/workflowkey/key.go new file mode 100644 index 00000000000..ce8560303e0 --- /dev/null +++ b/core/services/keystore/keys/workflowkey/key.go @@ -0,0 +1,107 @@ +package workflowkey + +import ( + cryptorand "crypto/rand" + "encoding/hex" + "errors" + "fmt" + + "golang.org/x/crypto/curve25519" + "golang.org/x/crypto/nacl/box" +) + +type Raw []byte + +func (raw Raw) Key() Key { + privateKey := [32]byte(raw) + return Key{ + privateKey: &privateKey, + publicKey: curve25519PubKeyFromPrivateKey(privateKey), + } +} + +func (raw Raw) String() string { + return fmt.Sprintf("<%s Raw Private Key>", keyTypeIdentifier) +} + +func (raw Raw) GoString() string { + return raw.String() +} + +func (raw Raw) Bytes() []byte { + return ([]byte)(raw) +} + +type Key struct { + privateKey *[curve25519.PointSize]byte + publicKey *[curve25519.PointSize]byte +} + +func New() (Key, error) { + publicKey, privateKey, err := box.GenerateKey(cryptorand.Reader) + if err != nil { + return Key{}, err + } + + return Key{ + privateKey: privateKey, + publicKey: publicKey, + }, nil +} + +func (k Key) PublicKey() [curve25519.PointSize]byte { + return *k.publicKey +} + +func (k Key) PublicKeyString() string { + return hex.EncodeToString(k.publicKey[:]) +} + +func (k Key) ID() string { + return k.PublicKeyString() +} + +func (k Key) Raw() Raw { + raw := make([]byte, curve25519.PointSize) + copy(raw, k.privateKey[:]) + return Raw(raw) +} + +func (k Key) String() string { + return fmt.Sprintf("%sKey{PrivateKey: , PublicKey: %s}", keyTypeIdentifier, *k.publicKey) +} + +func (k Key) GoString() string { + return k.String() +} + +// Encrypt encrypts a message using the public key +func (k Key) Encrypt(plaintext []byte) ([]byte, error) { + publicKey := k.PublicKey() + encrypted, err := box.SealAnonymous(nil, plaintext, &publicKey, cryptorand.Reader) + if err != nil { + return nil, err + } + + return encrypted, nil +} + +// Decrypt decrypts a message that was encrypted using the private key +func (k Key) Decrypt(ciphertext []byte) (plaintext []byte, err error) { + publicKey := k.PublicKey() + decrypted, success := box.OpenAnonymous(nil, ciphertext, &publicKey, k.privateKey) + if !success { + return nil, errors.New("decryption failed") + } + + return decrypted, nil +} + +func curve25519PubKeyFromPrivateKey(privateKey [curve25519.PointSize]byte) *[curve25519.PointSize]byte { + var publicKey [curve25519.PointSize]byte + + // Derive the public key + curve25519.ScalarBaseMult(&publicKey, &privateKey) + + return &publicKey +} diff --git a/core/services/keystore/keys/workflowkey/key_test.go b/core/services/keystore/keys/workflowkey/key_test.go new file mode 100644 index 00000000000..3e3a9413a90 --- /dev/null +++ b/core/services/keystore/keys/workflowkey/key_test.go @@ -0,0 +1,88 @@ +package workflowkey + +import ( + cryptorand "crypto/rand" + "encoding/hex" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/nacl/box" +) + +func TestNew(t *testing.T) { + key, err := New() + require.NoError(t, err) + + assert.NotNil(t, key.PublicKey) + assert.NotNil(t, key.privateKey) +} + +func TestPublicKey(t *testing.T) { + key, err := New() + require.NoError(t, err) + + assert.Equal(t, *key.publicKey, key.PublicKey()) +} + +func TestEncryptKeyRawPrivateKey(t *testing.T) { + privKey, err := New() + require.NoError(t, err) + + privateKey := privKey.Raw() + + assert.Equal(t, "", privateKey.String()) + assert.Equal(t, privateKey.String(), privateKey.GoString()) +} + +func TestEncryptKeyFromRawPrivateKey(t *testing.T) { + boxPubKey, boxPrivKey, err := box.GenerateKey(cryptorand.Reader) + require.NoError(t, err) + + privKey := make([]byte, 32) + copy(privKey, boxPrivKey[:]) + key := Raw(privKey).Key() + + assert.Equal(t, boxPubKey, key.publicKey) + assert.Equal(t, boxPrivKey, key.privateKey) + assert.Equal(t, key.String(), key.GoString()) + + byteBoxPubKey := make([]byte, 32) + copy(byteBoxPubKey, boxPubKey[:]) + + assert.Equal(t, hex.EncodeToString(byteBoxPubKey), key.PublicKeyString()) + assert.Equal(t, fmt.Sprintf("WorkflowKey{PrivateKey: , PublicKey: %s}", byteBoxPubKey), key.String()) +} + +func TestPublicKeyStringAndID(t *testing.T) { + key := "my-test-public-key" + var pubkey [32]byte + copy(pubkey[:], key) + k := Key{ + publicKey: &pubkey, + } + + expected := hex.EncodeToString([]byte(key)) + // given the key is a [32]byte we need to ensure the encoded string is 64 character long + for len(expected) < 64 { + expected += "0" + } + + assert.Equal(t, expected, k.PublicKeyString()) + assert.Equal(t, expected, k.ID()) +} + +func TestDecrypt(t *testing.T) { + key, err := New() + require.NoError(t, err) + + secret := []byte("my-secret") + ciphertext, err := key.Encrypt(secret) + require.NoError(t, err) + + plaintext, err := key.Decrypt(ciphertext) + require.NoError(t, err) + + assert.Equal(t, secret, plaintext) +} diff --git a/core/services/keystore/master.go b/core/services/keystore/master.go index 47136f1f2ec..72677a166a3 100644 --- a/core/services/keystore/master.go +++ b/core/services/keystore/master.go @@ -21,6 +21,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/solkey" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/starkkey" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/vrfkey" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/workflowkey" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -45,6 +46,7 @@ type Master interface { StarkNet() StarkNet Aptos() Aptos VRF() VRF + Workflow() Workflow Unlock(ctx context.Context, password string) error IsEmpty(ctx context.Context) (bool, error) } @@ -61,6 +63,7 @@ type master struct { starknet *starknet aptos *aptos vrf *vrf + workflow *workflow } func New(ds sqlutil.DataSource, scryptParams utils.ScryptParams, lggr logger.Logger) Master { @@ -89,6 +92,7 @@ func newMaster(ds sqlutil.DataSource, scryptParams utils.ScryptParams, lggr logg starknet: newStarkNetKeyStore(km), aptos: newAptosKeyStore(km), vrf: newVRFKeyStore(km), + workflow: newWorkflowKeyStore(km), } } @@ -132,6 +136,10 @@ func (ks *master) VRF() VRF { return ks.vrf } +func (ks *master) Workflow() Workflow { + return ks.workflow +} + type ORM interface { isEmpty(context.Context) (bool, error) saveEncryptedKeyRing(context.Context, *encryptedKeyRing, ...func(sqlutil.DataSource) error) error @@ -267,6 +275,8 @@ func GetFieldNameForKey(unknownKey Key) (string, error) { return "Aptos", nil case vrfkey.KeyV2: return "VRF", nil + case workflowkey.Key: + return "Workflow", nil } return "", fmt.Errorf("unknown key type: %T", unknownKey) } diff --git a/core/services/keystore/mocks/master.go b/core/services/keystore/mocks/master.go index 687449db98d..7c86001bc54 100644 --- a/core/services/keystore/mocks/master.go +++ b/core/services/keystore/mocks/master.go @@ -595,6 +595,53 @@ func (_c *Master_VRF_Call) RunAndReturn(run func() keystore.VRF) *Master_VRF_Cal return _c } +// Workflow provides a mock function with given fields: +func (_m *Master) Workflow() keystore.Workflow { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Workflow") + } + + var r0 keystore.Workflow + if rf, ok := ret.Get(0).(func() keystore.Workflow); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(keystore.Workflow) + } + } + + return r0 +} + +// Master_Workflow_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Workflow' +type Master_Workflow_Call struct { + *mock.Call +} + +// Workflow is a helper method to define mock.On call +func (_e *Master_Expecter) Workflow() *Master_Workflow_Call { + return &Master_Workflow_Call{Call: _e.mock.On("Workflow")} +} + +func (_c *Master_Workflow_Call) Run(run func()) *Master_Workflow_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Master_Workflow_Call) Return(_a0 keystore.Workflow) *Master_Workflow_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Master_Workflow_Call) RunAndReturn(run func() keystore.Workflow) *Master_Workflow_Call { + _c.Call.Return(run) + return _c +} + // NewMaster creates a new instance of Master. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMaster(t interface { diff --git a/core/services/keystore/models.go b/core/services/keystore/models.go index d5eec6802b9..e0b53ef95e4 100644 --- a/core/services/keystore/models.go +++ b/core/services/keystore/models.go @@ -21,6 +21,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/solkey" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/starkkey" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/vrfkey" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/workflowkey" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -158,6 +159,7 @@ type keyRing struct { StarkNet map[string]starkkey.Key Aptos map[string]aptoskey.Key VRF map[string]vrfkey.KeyV2 + Workflow map[string]workflowkey.Key LegacyKeys LegacyKeyStorage } @@ -173,6 +175,7 @@ func newKeyRing() *keyRing { StarkNet: make(map[string]starkkey.Key), Aptos: make(map[string]aptoskey.Key), VRF: make(map[string]vrfkey.KeyV2), + Workflow: make(map[string]workflowkey.Key), } } diff --git a/core/services/keystore/workflow.go b/core/services/keystore/workflow.go new file mode 100644 index 00000000000..2f3ac158681 --- /dev/null +++ b/core/services/keystore/workflow.go @@ -0,0 +1,163 @@ +package keystore + +import ( + "context" + "fmt" + + "github.com/pkg/errors" + + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/workflowkey" +) + +// ErrWorkflowKeyExists describes the error when the workflow key already exists +var ErrWorkflowKeyExists = errors.New("can only have 1 Workflow key") + +type Workflow interface { + Get(id string) (workflowkey.Key, error) + GetAll() ([]workflowkey.Key, error) + Create(ctx context.Context) (workflowkey.Key, error) + Add(ctx context.Context, key workflowkey.Key) error + Delete(ctx context.Context, id string) (workflowkey.Key, error) + Import(ctx context.Context, keyJSON []byte, password string) (workflowkey.Key, error) + Export(id string, password string) ([]byte, error) + EnsureKey(ctx context.Context) error +} + +type workflow struct { + *keyManager +} + +var _ Workflow = &workflow{} + +func newWorkflowKeyStore(km *keyManager) *workflow { + return &workflow{ + km, + } +} + +func (ks *workflow) Get(id string) (workflowkey.Key, error) { + ks.lock.RLock() + defer ks.lock.RUnlock() + if ks.isLocked() { + return workflowkey.Key{}, ErrLocked + } + return ks.getByID(id) +} + +func (ks *workflow) GetAll() (keys []workflowkey.Key, _ error) { + ks.lock.RLock() + defer ks.lock.RUnlock() + if ks.isLocked() { + return nil, ErrLocked + } + for _, key := range ks.keyRing.Workflow { + keys = append(keys, key) + } + return keys, nil +} + +func (ks *workflow) Create(ctx context.Context) (workflowkey.Key, error) { + ks.lock.Lock() + defer ks.lock.Unlock() + if ks.isLocked() { + return workflowkey.Key{}, ErrLocked + } + // Ensure you can only have one Workflow at a time. + if len(ks.keyRing.Workflow) > 0 { + return workflowkey.Key{}, ErrWorkflowKeyExists + } + + key, err := workflowkey.New() + if err != nil { + return workflowkey.Key{}, err + } + return key, ks.safeAddKey(ctx, key) +} + +func (ks *workflow) Add(ctx context.Context, key workflowkey.Key) error { + ks.lock.Lock() + defer ks.lock.Unlock() + if ks.isLocked() { + return ErrLocked + } + if len(ks.keyRing.Workflow) > 0 { + return ErrWorkflowKeyExists + } + return ks.safeAddKey(ctx, key) +} + +func (ks *workflow) Delete(ctx context.Context, id string) (workflowkey.Key, error) { + ks.lock.Lock() + defer ks.lock.Unlock() + if ks.isLocked() { + return workflowkey.Key{}, ErrLocked + } + key, err := ks.getByID(id) + if err != nil { + return workflowkey.Key{}, err + } + + err = ks.safeRemoveKey(ctx, key) + + return key, err +} + +func (ks *workflow) Import(ctx context.Context, keyJSON []byte, password string) (workflowkey.Key, error) { + ks.lock.Lock() + defer ks.lock.Unlock() + if ks.isLocked() { + return workflowkey.Key{}, ErrLocked + } + + key, err := workflowkey.FromEncryptedJSON(keyJSON, password) + if err != nil { + return workflowkey.Key{}, errors.Wrap(err, "WorkflowKeyStore#ImportKey failed to decrypt key") + } + if _, found := ks.keyRing.Workflow[key.ID()]; found { + return workflowkey.Key{}, fmt.Errorf("key with ID %s already exists", key.ID()) + } + return key, ks.keyManager.safeAddKey(ctx, key) +} + +func (ks *workflow) Export(id string, password string) ([]byte, error) { + ks.lock.RLock() + defer ks.lock.RUnlock() + if ks.isLocked() { + return nil, ErrLocked + } + key, err := ks.getByID(id) + if err != nil { + return nil, err + } + return key.ToEncryptedJSON(password, ks.scryptParams) +} + +// EnsureKey verifies whether the Workflow key has been seeded, if not, it creates it. +func (ks *workflow) EnsureKey(ctx context.Context) error { + ks.lock.Lock() + defer ks.lock.Unlock() + if ks.isLocked() { + return ErrLocked + } + + if len(ks.keyRing.Workflow) > 0 { + return nil + } + + key, err := workflowkey.New() + if err != nil { + return err + } + + ks.logger.Infof("Created Workflow key with ID %s", key.ID()) + + return ks.safeAddKey(ctx, key) +} + +func (ks *workflow) getByID(id string) (workflowkey.Key, error) { + key, found := ks.keyRing.Workflow[id] + if !found { + return workflowkey.Key{}, KeyNotFoundError{ID: id, KeyType: "Encryption"} + } + return key, nil +} diff --git a/core/services/keystore/workflow_test.go b/core/services/keystore/workflow_test.go new file mode 100644 index 00000000000..051ebdb76a7 --- /dev/null +++ b/core/services/keystore/workflow_test.go @@ -0,0 +1,178 @@ +package keystore_test + +import ( + "context" + "fmt" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/workflowkey" +) + +func Test_EncryptionKeyStore_E2E(t *testing.T) { + db := pgtest.NewSqlxDB(t) + keyStore := keystore.ExposedNewMaster(t, db) + require.NoError(t, keyStore.Unlock(testutils.Context(t), cltest.Password)) + ks := keyStore.Workflow() + reset := func() { + ctx := context.Background() // Executed on cleanup + _, err := db.Exec("DELETE FROM encrypted_key_rings") + require.NoError(t, err) + keyStore.ResetXXXTestOnly() + require.NoError(t, keyStore.Unlock(ctx, cltest.Password)) + } + + t.Run("initializes with an empty state", func(t *testing.T) { + defer reset() + keys, err := ks.GetAll() + require.NoError(t, err) + require.Empty(t, keys) + }) + + t.Run("errors when getting non-existent ID", func(t *testing.T) { + defer reset() + _, err := ks.Get("non-existent-id") + require.Error(t, err) + }) + + t.Run("creates a key", func(t *testing.T) { + defer reset() + ctx := testutils.Context(t) + key, err := ks.Create(ctx) + require.NoError(t, err) + retrievedKey, err := ks.Get(key.ID()) + require.NoError(t, err) + require.Equal(t, key, retrievedKey) + + t.Run("prevents creating more than one key", func(t *testing.T) { + ctx := testutils.Context(t) + k, err2 := ks.Create(ctx) + + assert.Zero(t, k) + require.Error(t, err2) + assert.True(t, errors.Is(err2, keystore.ErrWorkflowKeyExists)) + }) + }) + + t.Run("imports and exports a key", func(t *testing.T) { + defer reset() + ctx := testutils.Context(t) + key, err := ks.Create(ctx) + require.NoError(t, err) + exportJSON, err := ks.Export(key.ID(), cltest.Password) + require.NoError(t, err) + _, err = ks.Delete(ctx, key.ID()) + require.NoError(t, err) + _, err = ks.Get(key.ID()) + require.Error(t, err) + importedKey, err := ks.Import(ctx, exportJSON, cltest.Password) + require.NoError(t, err) + require.Equal(t, key.ID(), importedKey.ID()) + retrievedKey, err := ks.Get(key.ID()) + require.NoError(t, err) + require.Equal(t, importedKey, retrievedKey) + + t.Run("prevents importing more than one key", func(t *testing.T) { + k, err2 := ks.Import(testutils.Context(t), exportJSON, cltest.Password) + + assert.Zero(t, k) + require.Error(t, err2) + assert.Equal(t, fmt.Sprintf("key with ID %s already exists", key.ID()), err2.Error()) + }) + + t.Run("fails to import malformed key", func(t *testing.T) { + k, err2 := ks.Import(testutils.Context(t), []byte(""), cltest.Password) + + assert.Zero(t, k) + require.Error(t, err2) + }) + + t.Run("fails to export non-existent key", func(t *testing.T) { + exportJSON, err = ks.Export("non-existent", cltest.Password) + + require.Error(t, err) + assert.Empty(t, exportJSON) + }) + }) + + t.Run("adds an externally created key / deletes a key", func(t *testing.T) { + defer reset() + ctx := testutils.Context(t) + newKey, err := workflowkey.New() + require.NoError(t, err) + err = ks.Add(ctx, newKey) + require.NoError(t, err) + keys, err := ks.GetAll() + require.NoError(t, err) + require.Len(t, keys, 1) + _, err = ks.Delete(ctx, newKey.ID()) + require.NoError(t, err) + keys, err = ks.GetAll() + require.NoError(t, err) + require.Empty(t, keys) + _, err = ks.Get(newKey.ID()) + require.Error(t, err) + + t.Run("prevents adding more than one key", func(t *testing.T) { + ctx := testutils.Context(t) + err = ks.Add(ctx, newKey) + require.NoError(t, err) + + err = ks.Add(ctx, newKey) + + require.Error(t, err) + assert.True(t, errors.Is(err, keystore.ErrWorkflowKeyExists)) + }) + + t.Run("fails to delete non-existent key", func(t *testing.T) { + k, err2 := ks.Delete(testutils.Context(t), "non-existent") + + assert.Zero(t, k) + require.Error(t, err2) + }) + }) + + t.Run("adds an externally created key/ensures it already exists", func(t *testing.T) { + defer reset() + ctx := testutils.Context(t) + + newKey, err := workflowkey.New() + require.NoError(t, err) + err = ks.Add(ctx, newKey) + require.NoError(t, err) + + err = keyStore.Workflow().EnsureKey(ctx) + require.NoError(t, err) + keys, err2 := ks.GetAll() + require.NoError(t, err2) + + require.Len(t, keys, 1) + require.Equal(t, newKey.ID(), keys[0].ID()) + require.Equal(t, newKey.PublicKey(), keys[0].PublicKey()) + }) + + t.Run("auto creates a key if it doesn't exists when trying to ensure it already exists", func(t *testing.T) { + defer reset() + ctx := testutils.Context(t) + + keys, err := ks.GetAll() + require.NoError(t, err) + assert.Empty(t, keys) + + err = keyStore.Workflow().EnsureKey(ctx) + require.NoError(t, err) + + keys, err = ks.GetAll() + require.NoError(t, err) + + require.NoError(t, err) + require.Len(t, keys, 1) + }) +} From bf72fe3a433fb9f3b7bb9ae9f3cbda0249c4d800 Mon Sep 17 00:00:00 2001 From: Austin <107539019+0xAustinWang@users.noreply.github.com> Date: Wed, 6 Nov 2024 03:16:08 +1100 Subject: [PATCH 03/11] use deployment.Changeset as an interface for anything under 'changeset' directory (#15028) * use deployment.Changeset for anything under 'changeset' directory * active candidate changes * fix tests from merge --- deployment/ccip/active_candidate.go | 139 ++++++++++++++ deployment/ccip/changeset/active_candidate.go | 175 +++--------------- .../ccip/changeset/active_candidate_test.go | 6 +- deployment/ccip/changeset/add_chain.go | 44 +++-- deployment/ccip/changeset/add_chain_test.go | 24 +-- deployment/ccip/test_helpers.go | 29 +++ 6 files changed, 239 insertions(+), 178 deletions(-) create mode 100644 deployment/ccip/active_candidate.go diff --git a/deployment/ccip/active_candidate.go b/deployment/ccip/active_candidate.go new file mode 100644 index 00000000000..c65dac04103 --- /dev/null +++ b/deployment/ccip/active_candidate.go @@ -0,0 +1,139 @@ +package ccipdeployment + +import ( + "fmt" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" + "github.com/smartcontractkit/chainlink/deployment" + cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "math/big" +) + +// SetCandidateExecPluginOps calls setCandidate on CCIPHome contract through the UpdateDON call on CapReg contract +// This proposes to set up OCR3 config for the provided plugin for the DON +func SetCandidateOnExistingDon( + pluginConfig ccip_home.CCIPHomeOCR3Config, + capReg *capabilities_registry.CapabilitiesRegistry, + ccipHome *ccip_home.CCIPHome, + chainSelector uint64, + nodes deployment.Nodes, +) ([]mcms.Operation, error) { + // fetch DON ID for the chain + donID, err := DonIDForChain(capReg, ccipHome, chainSelector) + if err != nil { + return nil, fmt.Errorf("fetch don id for chain: %w", err) + } + fmt.Printf("donID: %d", donID) + encodedSetCandidateCall, err := CCIPHomeABI.Pack( + "setCandidate", + donID, + pluginConfig.PluginType, + pluginConfig, + [32]byte{}, + ) + if err != nil { + return nil, fmt.Errorf("pack set candidate call: %w", err) + } + + // set candidate call + updateDonTx, err := capReg.UpdateDON( + deployment.SimTransactOpts(), + donID, + nodes.PeerIDs(), + []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: CCIPCapabilityID, + Config: encodedSetCandidateCall, + }, + }, + false, + nodes.DefaultF(), + ) + if err != nil { + return nil, fmt.Errorf("update don w/ exec config: %w", err) + } + + return []mcms.Operation{{ + To: capReg.Address(), + Data: updateDonTx.Data(), + Value: big.NewInt(0), + }}, nil +} + +// PromoteCandidateOp will create the MCMS Operation for `promoteCandidateAndRevokeActive` directed towards the capabilityRegistry +func PromoteCandidateOp(donID uint32, pluginType uint8, capReg *capabilities_registry.CapabilitiesRegistry, + ccipHome *ccip_home.CCIPHome, nodes deployment.Nodes) (mcms.Operation, error) { + + allConfigs, err := ccipHome.GetAllConfigs(nil, donID, pluginType) + if err != nil { + return mcms.Operation{}, err + } + + if allConfigs.CandidateConfig.ConfigDigest == [32]byte{} { + return mcms.Operation{}, fmt.Errorf("candidate digest is empty, expected nonempty") + } + fmt.Printf("commit candidate digest after setCandidate: %x\n", allConfigs.CandidateConfig.ConfigDigest) + + encodedPromotionCall, err := CCIPHomeABI.Pack( + "promoteCandidateAndRevokeActive", + donID, + pluginType, + allConfigs.CandidateConfig.ConfigDigest, + allConfigs.ActiveConfig.ConfigDigest, + ) + if err != nil { + return mcms.Operation{}, fmt.Errorf("pack promotion call: %w", err) + } + + updateDonTx, err := capReg.UpdateDON( + deployment.SimTransactOpts(), + donID, + nodes.PeerIDs(), + []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: CCIPCapabilityID, + Config: encodedPromotionCall, + }, + }, + false, + nodes.DefaultF(), + ) + if err != nil { + return mcms.Operation{}, fmt.Errorf("error creating updateDon op for donID(%d) and plugin type (%d): %w", donID, pluginType, err) + } + return mcms.Operation{ + To: capReg.Address(), + Data: updateDonTx.Data(), + Value: big.NewInt(0), + }, nil +} + +// PromoteAllCandidatesForChainOps promotes the candidate commit and exec configs to active by calling promoteCandidateAndRevokeActive on CCIPHome through the UpdateDON call on CapReg contract +func PromoteAllCandidatesForChainOps( + capReg *capabilities_registry.CapabilitiesRegistry, + ccipHome *ccip_home.CCIPHome, + chainSelector uint64, + nodes deployment.Nodes, +) ([]mcms.Operation, error) { + // fetch DON ID for the chain + donID, err := DonIDForChain(capReg, ccipHome, chainSelector) + if err != nil { + return nil, fmt.Errorf("fetch don id for chain: %w", err) + } + + var mcmsOps []mcms.Operation + updateCommitOp, err := PromoteCandidateOp(donID, uint8(cctypes.PluginTypeCCIPCommit), capReg, ccipHome, nodes) + if err != nil { + return nil, fmt.Errorf("promote candidate op: %w", err) + } + mcmsOps = append(mcmsOps, updateCommitOp) + + updateExecOp, err := PromoteCandidateOp(donID, uint8(cctypes.PluginTypeCCIPExec), capReg, ccipHome, nodes) + if err != nil { + return nil, fmt.Errorf("promote candidate op: %w", err) + } + mcmsOps = append(mcmsOps, updateExecOp) + + return mcmsOps, nil +} diff --git a/deployment/ccip/changeset/active_candidate.go b/deployment/ccip/changeset/active_candidate.go index bf061224561..7d09f81049b 100644 --- a/deployment/ccip/changeset/active_candidate.go +++ b/deployment/ccip/changeset/active_candidate.go @@ -2,171 +2,47 @@ package changeset import ( "fmt" - "math/big" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink/deployment" ccdeploy "github.com/smartcontractkit/chainlink/deployment/ccip" cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" ) -// SetCandidateExecPluginOps calls setCandidate on CCIPHome contract through the UpdateDON call on CapReg contract -// This proposes to set up OCR3 config for the provided plugin for the DON -func SetCandidateOnExistingDon( - pluginConfig ccip_home.CCIPHomeOCR3Config, - capReg *capabilities_registry.CapabilitiesRegistry, - ccipHome *ccip_home.CCIPHome, - chainSelector uint64, - nodes deployment.Nodes, -) ([]mcms.Operation, error) { - // fetch DON ID for the chain - donID, err := ccdeploy.DonIDForChain(capReg, ccipHome, chainSelector) - if err != nil { - return nil, fmt.Errorf("fetch don id for chain: %w", err) - } - fmt.Printf("donID: %d", donID) - encodedSetCandidateCall, err := ccdeploy.CCIPHomeABI.Pack( - "setCandidate", - donID, - pluginConfig.PluginType, - pluginConfig, - [32]byte{}, - ) - if err != nil { - return nil, fmt.Errorf("pack set candidate call: %w", err) - } - - // set candidate call - updateDonTx, err := capReg.UpdateDON( - deployment.SimTransactOpts(), - donID, - nodes.PeerIDs(), - []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ - { - CapabilityId: ccdeploy.CCIPCapabilityID, - Config: encodedSetCandidateCall, - }, - }, - false, - nodes.DefaultF(), - ) - if err != nil { - return nil, fmt.Errorf("update don w/ exec config: %w", err) - } - - return []mcms.Operation{{ - To: capReg.Address(), - Data: updateDonTx.Data(), - Value: big.NewInt(0), - }}, nil -} - -// PromoteCandidateOp will create the MCMS Operation for `promoteCandidateAndRevokeActive` directed towards the capabilityRegistry -func PromoteCandidateOp(donID uint32, pluginType uint8, capReg *capabilities_registry.CapabilitiesRegistry, - ccipHome *ccip_home.CCIPHome, nodes deployment.Nodes) (mcms.Operation, error) { - - allConfigs, err := ccipHome.GetAllConfigs(nil, donID, pluginType) - if err != nil { - return mcms.Operation{}, err - } - - if allConfigs.CandidateConfig.ConfigDigest == [32]byte{} { - return mcms.Operation{}, fmt.Errorf("candidate digest is empty, expected nonempty") - } - fmt.Printf("commit candidate digest after setCandidate: %x\n", allConfigs.CandidateConfig.ConfigDigest) - - encodedPromotionCall, err := ccdeploy.CCIPHomeABI.Pack( - "promoteCandidateAndRevokeActive", - donID, - pluginType, - allConfigs.CandidateConfig.ConfigDigest, - allConfigs.ActiveConfig.ConfigDigest, - ) - if err != nil { - return mcms.Operation{}, fmt.Errorf("pack promotion call: %w", err) - } - - updateDonTx, err := capReg.UpdateDON( - deployment.SimTransactOpts(), - donID, - nodes.PeerIDs(), - []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ - { - CapabilityId: ccdeploy.CCIPCapabilityID, - Config: encodedPromotionCall, - }, - }, - false, - nodes.DefaultF(), - ) - if err != nil { - return mcms.Operation{}, fmt.Errorf("error creating updateDon op for donID(%d) and plugin type (%d): %w", donID, pluginType, err) - } - return mcms.Operation{ - To: capReg.Address(), - Data: updateDonTx.Data(), - Value: big.NewInt(0), - }, nil -} - -// PromoteAllCandidatesForChainOps promotes the candidate commit and exec configs to active by calling promoteCandidateAndRevokeActive on CCIPHome through the UpdateDON call on CapReg contract -func PromoteAllCandidatesForChainOps( - capReg *capabilities_registry.CapabilitiesRegistry, - ccipHome *ccip_home.CCIPHome, - chainSelector uint64, - nodes deployment.Nodes, -) ([]mcms.Operation, error) { - // fetch DON ID for the chain - donID, err := ccdeploy.DonIDForChain(capReg, ccipHome, chainSelector) - if err != nil { - return nil, fmt.Errorf("fetch don id for chain: %w", err) - } - - var mcmsOps []mcms.Operation - updateCommitOp, err := PromoteCandidateOp(donID, uint8(cctypes.PluginTypeCCIPCommit), capReg, ccipHome, nodes) - if err != nil { - return nil, fmt.Errorf("promote candidate op: %w", err) - } - mcmsOps = append(mcmsOps, updateCommitOp) - - updateExecOp, err := PromoteCandidateOp(donID, uint8(cctypes.PluginTypeCCIPExec), capReg, ccipHome, nodes) - if err != nil { - return nil, fmt.Errorf("promote candidate op: %w", err) - } - mcmsOps = append(mcmsOps, updateExecOp) - - return mcmsOps, nil -} - -// PromoteAllCandidatesProposal generates a proposal to call promoteCandidate on the CCIPHome through CapReg. +// PromoteAllCandidatesChangeset generates a proposal to call promoteCandidate on the CCIPHome through CapReg. // This needs to be called after SetCandidateProposal is executed. -func PromoteAllCandidatesProposal( +func PromoteAllCandidatesChangeset( state ccdeploy.CCIPOnChainState, homeChainSel, newChainSel uint64, nodes deployment.Nodes, -) (*timelock.MCMSWithTimelockProposal, error) { - promoteCandidateOps, err := PromoteAllCandidatesForChainOps( +) (deployment.ChangesetOutput, error) { + promoteCandidateOps, err := ccdeploy.PromoteAllCandidatesForChainOps( state.Chains[homeChainSel].CapabilityRegistry, state.Chains[homeChainSel].CCIPHome, newChainSel, nodes.NonBootstraps(), ) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } - return ccdeploy.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ + prop, err := ccdeploy.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ ChainIdentifier: mcms.ChainIdentifier(homeChainSel), Batch: promoteCandidateOps, }}, "promoteCandidate for commit and execution", 0) + if err != nil { + return deployment.ChangesetOutput{}, err + } + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{ + *prop, + }, + }, nil } // SetCandidateExecPluginProposal calls setCandidate on the CCIPHome for setting up OCR3 exec Plugin config for the new chain. -func SetCandidatePluginProposal( +func SetCandidatePluginChangeset( state ccdeploy.CCIPOnChainState, e deployment.Environment, nodes deployment.Nodes, @@ -174,7 +50,7 @@ func SetCandidatePluginProposal( homeChainSel, feedChainSel, newChainSel uint64, tokenConfig ccdeploy.TokenConfig, pluginType cctypes.PluginType, -) (*timelock.MCMSWithTimelockProposal, error) { +) (deployment.ChangesetOutput, error) { newDONArgs, err := ccdeploy.BuildOCR3ConfigForCCIPHome( e.Logger, ocrSecrets, @@ -186,15 +62,15 @@ func SetCandidatePluginProposal( state.Chains[homeChainSel].RMNHome.Address(), ) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } execConfig, ok := newDONArgs[pluginType] if !ok { - return nil, fmt.Errorf("missing exec plugin in ocr3Configs") + return deployment.ChangesetOutput{}, fmt.Errorf("missing exec plugin in ocr3Configs") } - setCandidateMCMSOps, err := SetCandidateOnExistingDon( + setCandidateMCMSOps, err := ccdeploy.SetCandidateOnExistingDon( execConfig, state.Chains[homeChainSel].CapabilityRegistry, state.Chains[homeChainSel].CCIPHome, @@ -202,11 +78,20 @@ func SetCandidatePluginProposal( nodes.NonBootstraps(), ) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } - return ccdeploy.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ + prop, err := ccdeploy.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ ChainIdentifier: mcms.ChainIdentifier(homeChainSel), Batch: setCandidateMCMSOps, }}, "SetCandidate for execution", 0) + if err != nil { + return deployment.ChangesetOutput{}, err + } + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{ + *prop, + }, + }, nil + } diff --git a/deployment/ccip/changeset/active_candidate_test.go b/deployment/ccip/changeset/active_candidate_test.go index 19f41aa6820..a91b2104a60 100644 --- a/deployment/ccip/changeset/active_candidate_test.go +++ b/deployment/ccip/changeset/active_candidate_test.go @@ -156,7 +156,7 @@ func TestActiveCandidate(t *testing.T) { ) require.NoError(t, err) - setCommitCandidateOp, err := SetCandidateOnExistingDon( + setCommitCandidateOp, err := ccdeploy.SetCandidateOnExistingDon( ocr3ConfigMap[cctypes.PluginTypeCCIPCommit], state.Chains[homeCS].CapabilityRegistry, state.Chains[homeCS].CCIPHome, @@ -173,7 +173,7 @@ func TestActiveCandidate(t *testing.T) { ccdeploy.ExecuteProposal(t, e, setCommitCandidateSigned, state, homeCS) // create the op for the commit plugin as well - setExecCandidateOp, err := SetCandidateOnExistingDon( + setExecCandidateOp, err := ccdeploy.SetCandidateOnExistingDon( ocr3ConfigMap[cctypes.PluginTypeCCIPExec], state.Chains[homeCS].CapabilityRegistry, state.Chains[homeCS].CCIPHome, @@ -207,7 +207,7 @@ func TestActiveCandidate(t *testing.T) { oldCandidateDigest, err := state.Chains[homeCS].CCIPHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPExec)) require.NoError(t, err) - promoteOps, err := PromoteAllCandidatesForChainOps(state.Chains[homeCS].CapabilityRegistry, state.Chains[homeCS].CCIPHome, destCS, nodes.NonBootstraps()) + promoteOps, err := ccdeploy.PromoteAllCandidatesForChainOps(state.Chains[homeCS].CapabilityRegistry, state.Chains[homeCS].CCIPHome, destCS, nodes.NonBootstraps()) require.NoError(t, err) promoteProposal, err := ccdeploy.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ ChainIdentifier: mcms.ChainIdentifier(homeCS), diff --git a/deployment/ccip/changeset/add_chain.go b/deployment/ccip/changeset/add_chain.go index 7f0b096f0a9..9742de64bc6 100644 --- a/deployment/ccip/changeset/add_chain.go +++ b/deployment/ccip/changeset/add_chain.go @@ -16,15 +16,15 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" ) -// NewChainInboundProposal generates a proposal +// NewChainInboundChangeset generates a proposal // to connect the new chain to the existing chains. -func NewChainInboundProposal( +func NewChainInboundChangeset( e deployment.Environment, state ccipdeployment.CCIPOnChainState, homeChainSel uint64, newChainSel uint64, sources []uint64, -) (*timelock.MCMSWithTimelockProposal, error) { +) (deployment.ChangesetOutput, error) { // Generate proposal which enables new destination (from test router) on all source chains. var batches []timelock.BatchChainOperation for _, source := range sources { @@ -35,7 +35,7 @@ func NewChainInboundProposal( }, }) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } enableFeeQuoterDest, err := state.Chains[source].FeeQuoter.ApplyDestChainConfigUpdates( deployment.SimTransactOpts(), @@ -46,7 +46,7 @@ func NewChainInboundProposal( }, }) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } batches = append(batches, timelock.BatchChainOperation{ ChainIdentifier: mcms.ChainIdentifier(source), @@ -68,7 +68,7 @@ func NewChainInboundProposal( addChainOp, err := ccipdeployment.ApplyChainConfigUpdatesOp(e, state, homeChainSel, []uint64{newChainSel}) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } batches = append(batches, timelock.BatchChainOperation{ @@ -78,12 +78,19 @@ func NewChainInboundProposal( }, }) - return ccipdeployment.BuildProposalFromBatches(state, batches, "proposal to set new chains", 0) + prop, err := ccipdeployment.BuildProposalFromBatches(state, batches, "proposal to set new chains", 0) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*prop}, + }, nil } -// AddDonAndSetCandidateProposal adds new DON for destination to home chain +// AddDonAndSetCandidateChangeset adds new DON for destination to home chain // and sets the commit plugin config as candidateConfig for the don. -func AddDonAndSetCandidateProposal( +func AddDonAndSetCandidateChangeset( state ccipdeployment.CCIPOnChainState, e deployment.Environment, nodes deployment.Nodes, @@ -91,7 +98,7 @@ func AddDonAndSetCandidateProposal( homeChainSel, feedChainSel, newChainSel uint64, tokenConfig ccipdeployment.TokenConfig, pluginType types.PluginType, -) (*timelock.MCMSWithTimelockProposal, error) { +) (deployment.ChangesetOutput, error) { newDONArgs, err := ccipdeployment.BuildOCR3ConfigForCCIPHome( e.Logger, ocrSecrets, @@ -103,15 +110,15 @@ func AddDonAndSetCandidateProposal( state.Chains[homeChainSel].RMNHome.Address(), ) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } latestDon, err := ccipdeployment.LatestCCIPDON(state.Chains[homeChainSel].CapabilityRegistry) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } commitConfig, ok := newDONArgs[pluginType] if !ok { - return nil, fmt.Errorf("missing commit plugin in ocr3Configs") + return deployment.ChangesetOutput{}, fmt.Errorf("missing commit plugin in ocr3Configs") } donID := latestDon.Id + 1 addDonOp, err := ccipdeployment.NewDonWithCandidateOp( @@ -120,11 +127,18 @@ func AddDonAndSetCandidateProposal( nodes.NonBootstraps(), ) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } - return ccipdeployment.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ + prop, err := ccipdeployment.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ ChainIdentifier: mcms.ChainIdentifier(homeChainSel), Batch: []mcms.Operation{addDonOp}, }}, "setCandidate for commit and AddDon on new Chain", 0) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*prop}, + }, nil } diff --git a/deployment/ccip/changeset/add_chain_test.go b/deployment/ccip/changeset/add_chain_test.go index 69320bd4dac..8149b6e1b0b 100644 --- a/deployment/ccip/changeset/add_chain_test.go +++ b/deployment/ccip/changeset/add_chain_test.go @@ -132,34 +132,28 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, err) // Generate and sign inbound proposal to new 4th chain. - chainInboundProposal, err := NewChainInboundProposal(e.Env, state, e.HomeChainSel, newChain, initialDeploy) + chainInboundChangeset, err := NewChainInboundChangeset(e.Env, state, e.HomeChainSel, newChain, initialDeploy) require.NoError(t, err) - chainInboundExec := ccipdeployment.SignProposal(t, e.Env, chainInboundProposal) - for _, sel := range initialDeploy { - ccipdeployment.ExecuteProposal(t, e.Env, chainInboundExec, state, sel) - } + ccipdeployment.ProcessChangeset(t, e.Env, chainInboundChangeset) + // TODO This currently is not working - Able to send the request here but request gets stuck in execution // Send a new message and expect that this is delivered once the chain is completely set up as inbound //TestSendRequest(t, e.Env, state, initialDeploy[0], newChain, true) t.Logf("Executing add don and set candidate proposal for commit plugin on chain %d", newChain) - addDonProp, err := AddDonAndSetCandidateProposal(state, e.Env, nodes, deployment.XXXGenerateTestOCRSecrets(), e.HomeChainSel, e.FeedChainSel, newChain, tokenConfig, types.PluginTypeCCIPCommit) + addDonChangeset, err := AddDonAndSetCandidateChangeset(state, e.Env, nodes, deployment.XXXGenerateTestOCRSecrets(), e.HomeChainSel, e.FeedChainSel, newChain, tokenConfig, types.PluginTypeCCIPCommit) require.NoError(t, err) - - addDonExec := ccipdeployment.SignProposal(t, e.Env, addDonProp) - ccipdeployment.ExecuteProposal(t, e.Env, addDonExec, state, e.HomeChainSel) + ccipdeployment.ProcessChangeset(t, e.Env, addDonChangeset) t.Logf("Executing promote candidate proposal for exec plugin on chain %d", newChain) - setCandidateForExecProposal, err := SetCandidatePluginProposal(state, e.Env, nodes, deployment.XXXGenerateTestOCRSecrets(), e.HomeChainSel, e.FeedChainSel, newChain, tokenConfig, types.PluginTypeCCIPExec) + setCandidateForExecChangeset, err := SetCandidatePluginChangeset(state, e.Env, nodes, deployment.XXXGenerateTestOCRSecrets(), e.HomeChainSel, e.FeedChainSel, newChain, tokenConfig, types.PluginTypeCCIPExec) require.NoError(t, err) - setCandidateForExecExec := ccipdeployment.SignProposal(t, e.Env, setCandidateForExecProposal) - ccipdeployment.ExecuteProposal(t, e.Env, setCandidateForExecExec, state, e.HomeChainSel) + ccipdeployment.ProcessChangeset(t, e.Env, setCandidateForExecChangeset) t.Logf("Executing promote candidate proposal for both commit and exec plugins on chain %d", newChain) - donPromoteProposal, err := PromoteAllCandidatesProposal(state, e.HomeChainSel, newChain, nodes) + donPromoteChangeset, err := PromoteAllCandidatesChangeset(state, e.HomeChainSel, newChain, nodes) require.NoError(t, err) - donPromoteExec := ccipdeployment.SignProposal(t, e.Env, donPromoteProposal) - ccipdeployment.ExecuteProposal(t, e.Env, donPromoteExec, state, e.HomeChainSel) + ccipdeployment.ProcessChangeset(t, e.Env, donPromoteChangeset) // verify if the configs are updated require.NoError(t, ccipdeployment.ValidateCCIPHomeConfigSetUp( diff --git a/deployment/ccip/test_helpers.go b/deployment/ccip/test_helpers.go index 97d311c4240..3f9db87db15 100644 --- a/deployment/ccip/test_helpers.go +++ b/deployment/ccip/test_helpers.go @@ -3,6 +3,7 @@ package ccipdeployment import ( "context" "fmt" + mapset "github.com/deckarep/golang-set/v2" "math/big" "sort" "testing" @@ -388,3 +389,31 @@ func ConfirmRequestOnSourceAndDest(t *testing.T, env deployment.Environment, sta return nil } + +func ProcessChangeset(t *testing.T, e deployment.Environment, c deployment.ChangesetOutput) { + + // TODO: Add support for jobspecs as well + + // sign and execute all proposals provided + if len(c.Proposals) != 0 { + state, err := LoadOnchainState(e) + require.NoError(t, err) + for _, prop := range c.Proposals { + chains := mapset.NewSet[uint64]() + for _, op := range prop.Transactions { + chains.Add(uint64(op.ChainIdentifier)) + } + + signed := SignProposal(t, e, &prop) + for _, sel := range chains.ToSlice() { + ExecuteProposal(t, e, signed, state, sel) + } + } + } + + // merge address books + if c.AddressBook != nil { + err := e.ExistingAddresses.Merge(c.AddressBook) + require.NoError(t, err) + } +} From 97abe0f7d650c7bb55104cc01209bb90510d5b08 Mon Sep 17 00:00:00 2001 From: Austin <107539019+0xAustinWang@users.noreply.github.com> Date: Wed, 6 Nov 2024 03:46:46 +1100 Subject: [PATCH 04/11] use a map to get difference between oracles to start and stop (#15118) * use a map to get difference between oracles to start and stop * lint * size the slice dynamically * use error group to handle mutex and waitgroup * remove chained iota Co-authored-by: Makram --------- Co-authored-by: Makram --- core/capabilities/ccip/launcher/deployment.go | 82 ++++++++++--------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/core/capabilities/ccip/launcher/deployment.go b/core/capabilities/ccip/launcher/deployment.go index 64571183425..870c0409494 100644 --- a/core/capabilities/ccip/launcher/deployment.go +++ b/core/capabilities/ccip/launcher/deployment.go @@ -1,12 +1,11 @@ package launcher import ( - "errors" "fmt" - "sync" - + mapset "github.com/deckarep/golang-set/v2" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" - "go.uber.org/multierr" + "golang.org/x/exp/maps" + "golang.org/x/sync/errgroup" cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" ) @@ -35,47 +34,54 @@ func (c pluginRegistry) CloseAll() error { // If any of the previous config digests are no longer present, we need to shut those down // We don't care about if they're exec/commit or active/candidate, that all happens in the plugin func (c pluginRegistry) TransitionFrom(prevPlugins pluginRegistry) error { - var allErrs error - if len(c) > MaxPlugins || len(prevPlugins) > MaxPlugins { return fmt.Errorf("current pluginRegistry or prevPlugins have more than 4 instances: len(prevPlugins): %d, len(currPlugins): %d", len(prevPlugins), len(c)) } - var wg sync.WaitGroup - var mu sync.Mutex - // This shuts down instances that were present previously, but are no longer needed - for digest, oracle := range prevPlugins { - if _, ok := c[digest]; !ok { - wg.Add(1) - go func(o cctypes.CCIPOracle) { - defer wg.Done() - if err := o.Close(); err != nil { - mu.Lock() - allErrs = multierr.Append(allErrs, err) - mu.Unlock() - } - }(oracle) - } + prevOracles := mapset.NewSet[ocrtypes.ConfigDigest](maps.Keys(prevPlugins)...) + currOracles := mapset.NewSet[ocrtypes.ConfigDigest](maps.Keys(c)...) + + var ops = make([]syncAction, 0, 2*MaxPlugins) + for digest := range prevOracles.Difference(currOracles).Iterator().C { + ops = append(ops, syncAction{ + command: closeAction, + oracle: prevPlugins[digest], + }) + } + + for digest := range currOracles.Difference(prevOracles).Iterator().C { + ops = append(ops, syncAction{ + command: openAction, + oracle: c[digest], + }) } - wg.Wait() - // This will start the instances that were not previously present, but are in the new config - for digest, oracle := range c { - if digest == [32]byte{} { - allErrs = multierr.Append(allErrs, errors.New("cannot start a plugin with an empty config digest")) - } else if _, ok := prevPlugins[digest]; !ok { - wg.Add(1) - go func(o cctypes.CCIPOracle) { - defer wg.Done() - if err := o.Start(); err != nil { - mu.Lock() - allErrs = multierr.Append(allErrs, err) - mu.Unlock() + g := new(errgroup.Group) + for _, op := range ops { + op := op + g.Go(func() error { + if op.command == closeAction { + if err := op.oracle.Close(); err != nil { + return err } - }(oracle) - } + } else if op.command == openAction { + if err := op.oracle.Start(); err != nil { + return err + } + } + return nil + }) } - wg.Wait() - return allErrs + return g.Wait() +} + +const ( + closeAction = iota + openAction +) + +type syncAction struct { + command int + oracle cctypes.CCIPOracle } From f8a62187273bac1f12eeee0fe33355fe1b4faebb Mon Sep 17 00:00:00 2001 From: Dmytro Haidashenko <34754799+dhaidashenko@users.noreply.github.com> Date: Tue, 5 Nov 2024 18:29:28 +0100 Subject: [PATCH 05/11] Fix/head tracker config doc (#15115) * Fix headtracker config doc * make config-docs * changeset --- .changeset/great-spiders-greet.md | 5 +++++ core/config/docs/chains-evm.toml | 4 ++-- docs/CONFIG.md | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 .changeset/great-spiders-greet.md diff --git a/.changeset/great-spiders-greet.md b/.changeset/great-spiders-greet.md new file mode 100644 index 00000000000..cd8e20a32a6 --- /dev/null +++ b/.changeset/great-spiders-greet.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Fixed outdated headtracker config doc. #internal diff --git a/core/config/docs/chains-evm.toml b/core/config/docs/chains-evm.toml index b67c57ba40e..62360cb02cb 100644 --- a/core/config/docs/chains-evm.toml +++ b/core/config/docs/chains-evm.toml @@ -336,8 +336,8 @@ CacheTimeout = '10s' # Default [EVM.HeadTracker] # HistoryDepth tracks the top N blocks on top of the latest finalized block to keep in the `heads` database table. # Note that this can easily result in MORE than `N + finality depth` records since in the case of re-orgs we keep multiple heads for a particular block height. -# This number should be at least as large as `FinalityDepth`. -# There may be a small performance penalty to setting this to something very large (10,000+) +# Higher values help reduce number of RPC requests performed by TXM's Finalizer and improve TXM's Confirmer reorg protection on restarts. +# At the same time, setting the value too high could lead to higher CPU consumption. The following formula could be used to calculate the optimal value: `expected_downtime_on_restart/block_time`. HistoryDepth = 100 # Default # MaxBufferSize is the maximum number of heads that may be # buffered in front of the head tracker before older heads start to be diff --git a/docs/CONFIG.md b/docs/CONFIG.md index a1f583ec2d7..b28a83d1dfa 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -9503,8 +9503,8 @@ HistoryDepth = 100 # Default ``` HistoryDepth tracks the top N blocks on top of the latest finalized block to keep in the `heads` database table. Note that this can easily result in MORE than `N + finality depth` records since in the case of re-orgs we keep multiple heads for a particular block height. -This number should be at least as large as `FinalityDepth`. -There may be a small performance penalty to setting this to something very large (10,000+) +Higher values help reduce number of RPC requests performed by TXM's Finalizer and improve TXM's Confirmer reorg protection on restarts. +At the same time, setting the value too high could lead to higher CPU consumption. The following formula could be used to calculate the optimal value: `expected_downtime_on_restart/block_time`. ### MaxBufferSize ```toml From 0b38fca31ee21a6ca143fa53d777aa1578b532f1 Mon Sep 17 00:00:00 2001 From: David Cauchi <13139524+davidcauchi@users.noreply.github.com> Date: Tue, 5 Nov 2024 18:45:57 +0100 Subject: [PATCH 06/11] [SHIP-3903]Remove WS requirement for data feeds soak tests (#15003) * Add events polling * Sort endless loop * Increase polling time * Update logic * Adjust * Update * Add todo * Improve readability * Track goroutine * Update event logic --- integration-tests/testsetups/ocr.go | 240 ++++++++++++++++++++-------- 1 file changed, 175 insertions(+), 65 deletions(-) diff --git a/integration-tests/testsetups/ocr.go b/integration-tests/testsetups/ocr.go index 284c3c40c65..69da49ae404 100644 --- a/integration-tests/testsetups/ocr.go +++ b/integration-tests/testsetups/ocr.go @@ -11,6 +11,7 @@ import ( "os/signal" "sort" "strings" + "sync" "syscall" "testing" "time" @@ -20,7 +21,6 @@ import ( geth "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/pelletier/go-toml/v2" "github.com/rs/zerolog" "github.com/stretchr/testify/require" @@ -615,12 +615,19 @@ func (o *OCRSoakTest) testLoop(testDuration time.Duration, newValue int) { interruption := make(chan os.Signal, 1) //nolint:staticcheck //ignore SA1016 we need to send the os.Kill signal signal.Notify(interruption, os.Kill, os.Interrupt, syscall.SIGTERM) + + ctx, cancel := context.WithCancel(context.Background()) + var wg sync.WaitGroup + // Channel to signal polling to reset round event counter + resetEventCounter := make(chan struct{}) + defer close(resetEventCounter) + lastValue := 0 newRoundTrigger := time.NewTimer(0) // Want to trigger a new round ASAP defer newRoundTrigger.Stop() o.setFilterQuery() - err := o.observeOCREvents() - require.NoError(o.t, err, "Error subscribing to OCR events") + wg.Add(1) + go o.pollingOCREvents(ctx, &wg, resetEventCounter) n := o.Config.GetNetworkConfig() @@ -709,6 +716,8 @@ func (o *OCRSoakTest) testLoop(testDuration time.Duration, newValue int) { o.deleteChaosSimulations() os.Exit(interruptedExitCode) // Exit with interrupted code to indicate test was interrupted, not just a normal failure case <-endTest: + cancel() + wg.Wait() // Wait for polling to complete return case <-newRoundTrigger.C: err := o.triggerNewRound(newValue) @@ -719,6 +728,8 @@ func (o *OCRSoakTest) testLoop(testDuration time.Duration, newValue int) { Str("Waiting", timerReset.String()). Msg("Error triggering new round, waiting and trying again. Possible connection issues with mockserver") } + // Signal polling to reset event counter + resetEventCounter <- struct{}{} newRoundTrigger.Reset(timerReset) // Change value for the next round @@ -824,75 +835,171 @@ func (o *OCRSoakTest) setFilterQuery() { Msg("Filter Query Set") } -// observeOCREvents subscribes to OCR events and logs them to the test logger -// WARNING: Should only be used for observation and logging. This is not a reliable way to collect events. -func (o *OCRSoakTest) observeOCREvents() error { - eventLogs := make(chan types.Log) - ctx, cancel := context.WithTimeout(testcontext.Get(o.t), 5*time.Second) - eventSub, err := o.seth.Client.SubscribeFilterLogs(ctx, o.filterQuery, eventLogs) - cancel() - if err != nil { - return err - } +// pollingOCREvents Polls the blocks for OCR events and logs them to the test logger +func (o *OCRSoakTest) pollingOCREvents(ctx context.Context, wg *sync.WaitGroup, resetEventCounter <-chan struct{}) { + defer wg.Done() + // Keep track of the last processed block number + processedBlockNum := o.startingBlockNum - 1 + // TODO: Make this configurable + pollInterval := time.Second * 30 + ticker := time.NewTicker(pollInterval) + defer ticker.Stop() + + // Retrieve expected number of events per round from configuration + expectedEventsPerRound := *o.Config.GetActiveOCRConfig().Common.NumberOfContracts + eventCounter := 0 + roundTimeout := o.Config.GetActiveOCRConfig().Soak.TimeBetweenRounds.Duration + timeoutTimer := time.NewTimer(roundTimeout) + round := 0 + defer timeoutTimer.Stop() + + o.log.Info().Msg("Start Polling for Answer Updated Events") - go func() { - for { - select { - case event := <-eventLogs: - if o.OCRVersion == "1" { - answerUpdated, err := o.ocrV1Instances[0].ParseEventAnswerUpdated(event) - if err != nil { - o.log.Warn(). - Err(err). - Str("Address", event.Address.Hex()). - Uint64("Block Number", event.BlockNumber). - Msg("Error parsing event as AnswerUpdated") - continue - } - o.log.Info(). - Str("Address", event.Address.Hex()). - Uint64("Block Number", event.BlockNumber). - Uint64("Round ID", answerUpdated.RoundId.Uint64()). - Int64("Answer", answerUpdated.Current.Int64()). - Msg("Answer Updated Event") - } else if o.OCRVersion == "2" { - answerUpdated, err := o.ocrV2Instances[0].ParseEventAnswerUpdated(event) - if err != nil { - o.log.Warn(). - Err(err). - Str("Address", event.Address.Hex()). - Uint64("Block Number", event.BlockNumber). - Msg("Error parsing event as AnswerUpdated") - continue - } + for { + select { + case <-resetEventCounter: + if round != 0 { + if eventCounter == expectedEventsPerRound { o.log.Info(). - Str("Address", event.Address.Hex()). - Uint64("Block Number", event.BlockNumber). - Uint64("Round ID", answerUpdated.RoundId.Uint64()). - Int64("Answer", answerUpdated.Current.Int64()). - Msg("Answer Updated Event") + Int("Events found", eventCounter). + Int("Events Expected", expectedEventsPerRound). + Msg("All expected events found") + } else if eventCounter < expectedEventsPerRound { + o.log.Warn(). + Int("Events found", eventCounter). + Int("Events Expected", expectedEventsPerRound). + Msg("Expected to find more events") } - case err = <-eventSub.Err(): - backoff := time.Second - for err != nil { - o.log.Info(). - Err(err). - Str("Backoff", backoff.String()). - Interface("Query", o.filterQuery). - Msg("Error while subscribed to OCR Logs. Resubscribing") - ctx, cancel = context.WithTimeout(testcontext.Get(o.t), backoff) - eventSub, err = o.seth.Client.SubscribeFilterLogs(ctx, o.filterQuery, eventLogs) - cancel() - if err != nil { - time.Sleep(backoff) - backoff = time.Duration(math.Min(float64(backoff)*2, float64(30*time.Second))) - } + } + // Reset event counter and timer for new round + eventCounter = 0 + // Safely stop and drain the timer if a value is present + if !timeoutTimer.Stop() { + <-timeoutTimer.C + } + timeoutTimer.Reset(roundTimeout) + o.log.Info().Msg("Polling for new round, event counter reset") + round++ + case <-ctx.Done(): + o.log.Info().Msg("Test duration ended, finalizing event polling") + timeoutTimer.Reset(roundTimeout) + // Wait until expected events are fetched or until timeout + for eventCounter < expectedEventsPerRound { + select { + case <-timeoutTimer.C: + o.log.Warn().Msg("Timeout reached while waiting for final events") + return + case <-ticker.C: + o.fetchAndProcessEvents(&eventCounter, expectedEventsPerRound, &processedBlockNum) } } + o.log.Info(). + Int("Events found", eventCounter). + Int("Events Expected", expectedEventsPerRound). + Msg("Stop polling.") + return + case <-ticker.C: + o.fetchAndProcessEvents(&eventCounter, expectedEventsPerRound, &processedBlockNum) } - }() + } +} - return nil +// Helper function to poll events and update eventCounter +func (o *OCRSoakTest) fetchAndProcessEvents(eventCounter *int, expectedEvents int, processedBlockNum *uint64) { + latestBlock, err := o.seth.Client.BlockNumber(context.Background()) + if err != nil { + o.log.Error().Err(err).Msg("Error getting latest block number") + return + } + + if *processedBlockNum == latestBlock { + o.log.Debug(). + Uint64("Latest Block", latestBlock). + Uint64("Last Processed Block Number", *processedBlockNum). + Msg("No new blocks since last poll") + return + } + + // Check if the latest block is behind processedBlockNum due to possible reorgs + if *processedBlockNum > latestBlock { + o.log.Error(). + Uint64("From Block", *processedBlockNum). + Uint64("To Block", latestBlock). + Msg("The latest block is behind the processed block. This could happen due to RPC issues or possibly a reorg") + *processedBlockNum = latestBlock + return + } + + fromBlock := *processedBlockNum + 1 + o.filterQuery.FromBlock = big.NewInt(0).SetUint64(fromBlock) + o.filterQuery.ToBlock = big.NewInt(0).SetUint64(latestBlock) + + o.log.Debug(). + Uint64("From Block", fromBlock). + Uint64("To Block", latestBlock). + Msg("Fetching logs for the specified range") + + logs, err := o.seth.Client.FilterLogs(context.Background(), o.filterQuery) + if err != nil { + o.log.Error().Err(err).Msg("Error fetching logs") + return + } + + for _, event := range logs { + *eventCounter++ + if o.OCRVersion == "1" { + answerUpdated, err := o.ocrV1Instances[0].ParseEventAnswerUpdated(event) + if err != nil { + o.log.Warn(). + Err(err). + Str("Address", event.Address.Hex()). + Uint64("Block Number", event.BlockNumber). + Msg("Error parsing event as AnswerUpdated") + continue + } + if *eventCounter <= expectedEvents { + o.log.Info(). + Str("Address", event.Address.Hex()). + Uint64("Block Number", event.BlockNumber). + Uint64("Round ID", answerUpdated.RoundId.Uint64()). + Int64("Answer", answerUpdated.Current.Int64()). + Msg("Answer Updated Event") + } else { + o.log.Error(). + Str("Address", event.Address.Hex()). + Uint64("Block Number", event.BlockNumber). + Uint64("Round ID", answerUpdated.RoundId.Uint64()). + Int64("Answer", answerUpdated.Current.Int64()). + Msg("Excess event detected, beyond expected count") + } + } else if o.OCRVersion == "2" { + answerUpdated, err := o.ocrV2Instances[0].ParseEventAnswerUpdated(event) + if err != nil { + o.log.Warn(). + Err(err). + Str("Address", event.Address.Hex()). + Uint64("Block Number", event.BlockNumber). + Msg("Error parsing event as AnswerUpdated") + continue + } + if *eventCounter <= expectedEvents { + o.log.Info(). + Str("Address", event.Address.Hex()). + Uint64("Block Number", event.BlockNumber). + Uint64("Round ID", answerUpdated.RoundId.Uint64()). + Int64("Answer", answerUpdated.Current.Int64()). + Msg("Answer Updated Event") + } else { + o.log.Error(). + Str("Address", event.Address.Hex()). + Uint64("Block Number", event.BlockNumber). + Uint64("Round ID", answerUpdated.RoundId.Uint64()). + Int64("Answer", answerUpdated.Current.Int64()). + Msg("Excess event detected, beyond expected count") + } + } + } + *processedBlockNum = latestBlock } // triggers a new OCR round by setting a new mock adapter value @@ -941,6 +1048,9 @@ func (o *OCRSoakTest) collectEvents() error { o.ocrRoundStates[len(o.ocrRoundStates)-1].EndTime = start // Set end time for last expected event o.log.Info().Msg("Collecting on-chain events") + // Set from block to be starting block before filtering + o.filterQuery.FromBlock = big.NewInt(0).SetUint64(o.startingBlockNum) + // We must retrieve the events, use exponential backoff for timeout to retry timeout := time.Second * 15 o.log.Info().Interface("Filter Query", o.filterQuery).Str("Timeout", timeout.String()).Msg("Retrieving on-chain events") From dc5c1ec779db37c6bdeb8282b2492795ef056619 Mon Sep 17 00:00:00 2001 From: Josh Weintraub <26035072+jhweintraub@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:21:03 -0500 Subject: [PATCH 07/11] CCIP-4114 refactor token pool tests for one function per file (#15119) * refactor for one function per file * better align naming convention with source code * rename allowList test files * modify file name for more accuracy * fix compiler bugs that weren't caught for some reason --- contracts/gas-snapshots/ccip.gas-snapshot | 78 +- .../BurnFromMintTokenPool.lockOrBurn.t.sol} | 26 +- .../BurnFromMintTokenPoolSetup.t.sol | 18 + .../BurnMintSetup.t.sol | 10 +- .../BurnMintTokenPool.lockOrBurn.t.sol} | 78 +- .../BurnMintTokenPool.releaseOrMint.t.sol | 90 ++ ...LockReleaseFlagTokenPool.lockOrBurn.t.sol} | 14 +- ...urnWithFromMintTokenPool.lockOrBurn.t.sol} | 12 +- .../HybridLockReleaseUSDCTokenPool.t.sol | 961 ------------------ .../test/pools/LockReleaseTokenPool.t.sol | 439 -------- ...kReleaseTokenPool.canAcceptLiquidity.t.sol | 15 + .../LockReleaseTokenPool.lockOrBurn.t.sol | 102 ++ ...ockReleaseTokenPool.provideLiquidity.t.sol | 46 + .../LockReleaseTokenPool.releaseOrMint.t.sol | 146 +++ .../LockReleaseTokenPool.setRebalancer.t.sol | 20 + ...ckReleaseTokenPool.supportsInterface.t.sol | 15 + ...ckReleaseTokenPool.transferLiquidity.t.sol | 43 + ...ReleaseTokenPool.withdrawalLiquidity.t.sol | 42 + .../LockReleaseTokenPoolSetup.t.sol | 56 + .../src/v0.8/ccip/test/pools/TokenPool.t.sol | 786 -------------- .../TokenPool.applyAllowListUpdates.t.sol | 91 ++ .../TokenPool.applyChainUpdates.t.sol | 267 +++++ .../TokenPool/TokenPool.constructor.t.sol | 24 + .../TokenPool/TokenPool.getAllowList.t.sol | 13 + .../TokenPool.getAllowListEnabled.t.sol | 10 + .../TokenPool/TokenPool.getRemotePool.t.sol | 29 + .../TokenPool/TokenPool.onlyOffRamp.t.sol | 103 ++ .../TokenPool/TokenPool.onlyOnRamp.t.sol | 103 ++ .../TokenPool.setChainRateLimiterConfig.t.sol | 87 ++ .../TokenPool.setRateLimitAdmin.t.sol | 22 + .../TokenPool/TokenPool.setRemotePool.t.sol | 51 + .../pools/TokenPool/TokenPool.setRouter.t.sol | 20 + .../test/pools/TokenPool/TokenPoolSetup.t.sol | 21 + .../TokenPoolWithAllowListSetup.t.sol | 18 + ...dLockReleaseUSDCTokenPool.lockOrBurn.t.sol | 279 +++++ ...ckReleaseUSDCTokenPool.releaseOrMint.t.sol | 301 ++++++ ...leaseUSDCTokenPool.transferLiquidity.t.sol | 223 ++++ .../USDCBridgeMigrator.burnLockedUSDC.t.sol | 250 +++++ ...idgeMigrator.cancelMigrationProposal.t.sol | 179 ++++ ...BridgeMigrator.excludeTokensFromBurn.t.sol | 145 +++ .../USDCBridgeMigrator.proposeMigration.t.sol | 145 +++ .../USDCBridgeMigrator.provideLiquidity.t.sol | 179 ++++ .../USDCBridgeMigrator.releaseOrMint.t.sol | 318 ++++++ ...igrator.updateChainSelectorMechanism.t.sol | 155 +++ .../USDCTokenPool.lockOrBurn.t.sol | 201 ++++ .../USDCTokenPool.releaseOrMint.t.sol | 215 ++++ .../USDCTokenPool.setDomains.t.sol | 87 ++ .../USDCTokenPool.supportsInterface.t.sol | 14 + .../USDCTokenPool.validateMessage.t.sol | 89 ++ .../USDCTokenPool/USDCTokenPoolSetup.t.sol | 128 +++ .../v0.8/ccip/test/pools/USDCTokenPool.t.sol | 703 ------------- 51 files changed, 4432 insertions(+), 3035 deletions(-) rename contracts/src/v0.8/ccip/test/pools/{BurnFromMintTokenPool.t.sol => BurnFromMintTokenPool/BurnFromMintTokenPool.lockOrBurn.t.sol} (76%) create mode 100644 contracts/src/v0.8/ccip/test/pools/BurnFromMintTokenPool/BurnFromMintTokenPoolSetup.t.sol rename contracts/src/v0.8/ccip/test/pools/{ => BurnMintTokenPool}/BurnMintSetup.t.sol (83%) rename contracts/src/v0.8/ccip/test/pools/{BurnMintTokenPool.t.sol => BurnMintTokenPool/BurnMintTokenPool.lockOrBurn.t.sol} (54%) create mode 100644 contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool/BurnMintTokenPool.releaseOrMint.t.sol rename contracts/src/v0.8/ccip/test/pools/{BurnMintWithLockReleaseFlagTokenPool.t.sol => BurnMintWithLockReleaseFlagTokenPool/BurnMintWithLockReleaseFlagTokenPool.lockOrBurn.t.sol} (76%) rename contracts/src/v0.8/ccip/test/pools/{BurnWithFromMintTokenPool.t.sol => BurnWithFromMintTokenPool/BurnWithFromMintTokenPool.lockOrBurn.t.sol} (88%) delete mode 100644 contracts/src/v0.8/ccip/test/pools/HybridLockReleaseUSDCTokenPool.t.sol delete mode 100644 contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.canAcceptLiquidity.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.lockOrBurn.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.provideLiquidity.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.releaseOrMint.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.setRebalancer.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.supportsInterface.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.transferLiquidity.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.withdrawalLiquidity.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol delete mode 100644 contracts/src/v0.8/ccip/test/pools/TokenPool.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.applyAllowListUpdates.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.applyChainUpdates.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.constructor.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.getAllowList.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.getAllowListEnabled.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.getRemotePool.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.onlyOffRamp.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.onlyOnRamp.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setChainRateLimiterConfig.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setRateLimitAdmin.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setRemotePool.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setRouter.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolSetup.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolWithAllowListSetup.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/HybridLockReleaseUSDCTokenPool/HybridLockReleaseUSDCTokenPool.lockOrBurn.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/HybridLockReleaseUSDCTokenPool/HybridLockReleaseUSDCTokenPool.releaseOrMint.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/HybridLockReleaseUSDCTokenPool/HybridLockReleaseUSDCTokenPool.transferLiquidity.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.burnLockedUSDC.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.cancelMigrationProposal.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.excludeTokensFromBurn.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.proposeMigration.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.provideLiquidity.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.releaseOrMint.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.updateChainSelectorMechanism.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.lockOrBurn.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.releaseOrMint.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.setDomains.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.supportsInterface.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.validateMessage.t.sol create mode 100644 contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPoolSetup.t.sol delete mode 100644 contracts/src/v0.8/ccip/test/pools/USDCTokenPool.t.sol diff --git a/contracts/gas-snapshots/ccip.gas-snapshot b/contracts/gas-snapshots/ccip.gas-snapshot index 42621b196c7..b5b42e26d06 100644 --- a/contracts/gas-snapshots/ccip.gas-snapshot +++ b/contracts/gas-snapshots/ccip.gas-snapshot @@ -7,7 +7,7 @@ ARMProxyTest:test_ARMIsCursed_Success() (gas: 47082) BurnFromMintTokenPool_lockOrBurn:test_ChainNotAllowed_Revert() (gas: 28962) BurnFromMintTokenPool_lockOrBurn:test_PoolBurnRevertNotHealthy_Revert() (gas: 55341) BurnFromMintTokenPool_lockOrBurn:test_PoolBurn_Success() (gas: 244152) -BurnFromMintTokenPool_lockOrBurn:test_Setup_Success() (gas: 24209) +BurnFromMintTokenPool_lockOrBurn:test_setup_Success() (gas: 24187) BurnMintTokenPool_lockOrBurn:test_ChainNotAllowed_Revert() (gas: 27681) BurnMintTokenPool_lockOrBurn:test_PoolBurnRevertNotHealthy_Revert() (gas: 55341) BurnMintTokenPool_lockOrBurn:test_PoolBurn_Success() (gas: 242036) @@ -227,38 +227,15 @@ FeeQuoter_validateDestFamilyAddress:test_InvalidEVMAddressPrecompiles_Revert() ( FeeQuoter_validateDestFamilyAddress:test_InvalidEVMAddress_Revert() (gas: 10884) FeeQuoter_validateDestFamilyAddress:test_ValidEVMAddress_Success() (gas: 6819) FeeQuoter_validateDestFamilyAddress:test_ValidNonEVMAddress_Success() (gas: 6545) -HybridUSDCTokenPoolMigrationTests:test_LockOrBurn_LockReleaseMechanism_then_switchToPrimary_Success() (gas: 209719) -HybridUSDCTokenPoolMigrationTests:test_LockOrBurn_PrimaryMechanism_Success() (gas: 136083) -HybridUSDCTokenPoolMigrationTests:test_LockOrBurn_WhileMigrationPause_Revert() (gas: 109924) -HybridUSDCTokenPoolMigrationTests:test_LockOrBurn_onLockReleaseMechanism_Success() (gas: 147189) -HybridUSDCTokenPoolMigrationTests:test_MintOrRelease_OnLockReleaseMechanism_Success() (gas: 217255) -HybridUSDCTokenPoolMigrationTests:test_MintOrRelease_OnLockReleaseMechanism_then_switchToPrimary_Success() (gas: 426523) -HybridUSDCTokenPoolMigrationTests:test_MintOrRelease_incomingMessageWithPrimaryMechanism() (gas: 269168) -HybridUSDCTokenPoolMigrationTests:test_ProposeMigration_ChainNotUsingLockRelease_Revert() (gas: 15876) -HybridUSDCTokenPoolMigrationTests:test_ReleaseOrMint_WhileMigrationPause_Revert() (gas: 113657) -HybridUSDCTokenPoolMigrationTests:test_burnLockedUSDC_invalidPermissions_Revert() (gas: 39333) -HybridUSDCTokenPoolMigrationTests:test_cancelExistingCCTPMigrationProposal() (gas: 56302) -HybridUSDCTokenPoolMigrationTests:test_cannotCancelANonExistentMigrationProposal() (gas: 12758) -HybridUSDCTokenPoolMigrationTests:test_cannotModifyLiquidityWithoutPermissions_Revert() (gas: 13423) -HybridUSDCTokenPoolMigrationTests:test_cannotProvideLiquidityWhenMigrationProposalPending_Revert() (gas: 67449) -HybridUSDCTokenPoolMigrationTests:test_cannotRevertChainMechanism_afterMigration_Revert() (gas: 313663) -HybridUSDCTokenPoolMigrationTests:test_cannotTransferLiquidityDuringPendingMigration_Revert() (gas: 177187) -HybridUSDCTokenPoolMigrationTests:test_cnanotProvideLiquidity_AfterMigration_Revert() (gas: 314046) -HybridUSDCTokenPoolMigrationTests:test_excludeTokensWhenNoMigrationProposalPending_Revert() (gas: 13712) -HybridUSDCTokenPoolMigrationTests:test_lockOrBurn_then_BurnInCCTPMigration_Success() (gas: 310162) -HybridUSDCTokenPoolMigrationTests:test_transferLiquidity_Success() (gas: 167156) -HybridUSDCTokenPoolMigrationTests:test_unstickManualTxAfterMigration_destChain_Success() (gas: 156334) -HybridUSDCTokenPoolMigrationTests:test_unstickManualTxAfterMigration_homeChain_Success() (gas: 516342) -HybridUSDCTokenPoolTests:test_LockOrBurn_LockReleaseMechanism_then_switchToPrimary_Success() (gas: 209569) -HybridUSDCTokenPoolTests:test_LockOrBurn_PrimaryMechanism_Success() (gas: 136048) -HybridUSDCTokenPoolTests:test_LockOrBurn_WhileMigrationPause_Revert() (gas: 109914) -HybridUSDCTokenPoolTests:test_LockOrBurn_onLockReleaseMechanism_Success() (gas: 147177) -HybridUSDCTokenPoolTests:test_MintOrRelease_OnLockReleaseMechanism_Success() (gas: 217112) -HybridUSDCTokenPoolTests:test_MintOrRelease_OnLockReleaseMechanism_then_switchToPrimary_Success() (gas: 426248) -HybridUSDCTokenPoolTests:test_MintOrRelease_incomingMessageWithPrimaryMechanism() (gas: 269124) -HybridUSDCTokenPoolTests:test_ReleaseOrMint_WhileMigrationPause_Revert() (gas: 113635) -HybridUSDCTokenPoolTests:test_cannotTransferLiquidityDuringPendingMigration_Revert() (gas: 177099) -HybridUSDCTokenPoolTests:test_transferLiquidity_Success() (gas: 167068) +HybridLockReleaseUSDCTokenPool_TransferLiquidity:test_cannotTransferLiquidityDuringPendingMigration_Revert() (gas: 176969) +HybridLockReleaseUSDCTokenPool_TransferLiquidity:test_transferLiquidity_Success() (gas: 167002) +HybridLockReleaseUSDCTokenPool_lockOrBurn:test_PrimaryMechanism_Success() (gas: 135921) +HybridLockReleaseUSDCTokenPool_lockOrBurn:test_WhileMigrationPause_Revert() (gas: 109740) +HybridLockReleaseUSDCTokenPool_lockOrBurn:test_onLockReleaseMechanism_Success() (gas: 147013) +HybridLockReleaseUSDCTokenPool_lockOrBurn:test_onLockReleaseMechanism_thenswitchToPrimary_Success() (gas: 209245) +HybridLockReleaseUSDCTokenPool_releaseOrMint:test_OnLockReleaseMechanism_Success() (gas: 216909) +HybridLockReleaseUSDCTokenPool_releaseOrMint:test_WhileMigrationPause_Revert() (gas: 113472) +HybridLockReleaseUSDCTokenPool_releaseOrMint:test_incomingMessageWithPrimaryMechanism() (gas: 268981) LockReleaseTokenPool_canAcceptLiquidity:test_CanAcceptLiquidity_Success() (gas: 2778635) LockReleaseTokenPool_lockOrBurn:test_LockOrBurnWithAllowList_Revert() (gas: 30110) LockReleaseTokenPool_lockOrBurn:test_LockOrBurnWithAllowList_Success() (gas: 80282) @@ -275,8 +252,6 @@ LockReleaseTokenPool_transferLiquidity:test_transferLiquidity_Success() (gas: 83 LockReleaseTokenPool_transferLiquidity:test_transferLiquidity_transferTooMuch_Revert() (gas: 56013) LockReleaseTokenPool_withdrawalLiquidity:test_InsufficientLiquidity_Revert() (gas: 60164) LockReleaseTokenPool_withdrawalLiquidity:test_Unauthorized_Revert() (gas: 11464) -LockRelease_setRateLimitAdmin:test_SetRateLimitAdmin_Revert() (gas: 11024) -LockRelease_setRateLimitAdmin:test_SetRateLimitAdmin_Success() (gas: 35126) MerkleMultiProofTest:test_CVE_2023_34459() (gas: 5500) MerkleMultiProofTest:test_EmptyLeaf_Revert() (gas: 3607) MerkleMultiProofTest:test_MerkleRoot256() (gas: 394891) @@ -734,9 +709,42 @@ TokenPool_onlyOnRamp:test_ChainNotAllowed_Revert() (gas: 254443) TokenPool_onlyOnRamp:test_onlyOnRamp_Success() (gas: 305359) TokenPool_setChainRateLimiterConfig:test_NonExistentChain_Revert() (gas: 17225) TokenPool_setChainRateLimiterConfig:test_OnlyOwnerOrRateLimitAdmin_Revert() (gas: 15330) +TokenPool_setRateLimitAdmin:test_SetRateLimitAdmin_Revert() (gas: 11024) +TokenPool_setRateLimitAdmin:test_SetRateLimitAdmin_Success() (gas: 35126) TokenPool_setRemotePool:test_setRemotePool_NonExistentChain_Reverts() (gas: 15796) TokenPool_setRemotePool:test_setRemotePool_OnlyOwner_Reverts() (gas: 13285) TokenPool_setRemotePool:test_setRemotePool_Success() (gas: 282581) +USDCBridgeMigrator_BurnLockedUSDC:test_PrimaryMechanism_Success() (gas: 135930) +USDCBridgeMigrator_BurnLockedUSDC:test_WhileMigrationPause_Revert() (gas: 109773) +USDCBridgeMigrator_BurnLockedUSDC:test_invalidPermissions_Revert() (gas: 39343) +USDCBridgeMigrator_BurnLockedUSDC:test_lockOrBurn_then_BurnInCCTPMigration_Success() (gas: 309779) +USDCBridgeMigrator_BurnLockedUSDC:test_onLockReleaseMechanism_Success() (gas: 147037) +USDCBridgeMigrator_BurnLockedUSDC:test_onLockReleaseMechanism_thenswitchToPrimary_Success() (gas: 209263) +USDCBridgeMigrator_cancelMigrationProposal:test_cancelExistingCCTPMigrationProposal_Success() (gas: 56190) +USDCBridgeMigrator_cancelMigrationProposal:test_cannotCancelANonExistentMigrationProposal_Revert() (gas: 12691) +USDCBridgeMigrator_excludeTokensFromBurn:test_excludeTokensWhenNoMigrationProposalPending_Revert() (gas: 13579) +USDCBridgeMigrator_proposeMigration:test_ChainNotUsingLockRelease_Revert() (gas: 15765) +USDCBridgeMigrator_provideLiquidity:test_PrimaryMechanism_Success() (gas: 135912) +USDCBridgeMigrator_provideLiquidity:test_WhileMigrationPause_Revert() (gas: 109795) +USDCBridgeMigrator_provideLiquidity:test_cannotModifyLiquidityWithoutPermissions_Revert() (gas: 13378) +USDCBridgeMigrator_provideLiquidity:test_cannotProvideLiquidityWhenMigrationProposalPending_Revert() (gas: 67436) +USDCBridgeMigrator_provideLiquidity:test_cannotProvideLiquidity_AfterMigration_Revert() (gas: 313619) +USDCBridgeMigrator_provideLiquidity:test_invalidPermissions_Revert() (gas: 39343) +USDCBridgeMigrator_provideLiquidity:test_lockOrBurn_then_BurnInCCTPMigration_Success() (gas: 309779) +USDCBridgeMigrator_provideLiquidity:test_onLockReleaseMechanism_Success() (gas: 147082) +USDCBridgeMigrator_provideLiquidity:test_onLockReleaseMechanism_thenswitchToPrimary_Success() (gas: 209299) +USDCBridgeMigrator_releaseOrMint:test_OnLockReleaseMechanism_Success() (gas: 216942) +USDCBridgeMigrator_releaseOrMint:test_WhileMigrationPause_Revert() (gas: 113505) +USDCBridgeMigrator_releaseOrMint:test_incomingMessageWithPrimaryMechanism() (gas: 269034) +USDCBridgeMigrator_releaseOrMint:test_unstickManualTxAfterMigration_destChain_Success() (gas: 156071) +USDCBridgeMigrator_releaseOrMint:test_unstickManualTxAfterMigration_homeChain_Success() (gas: 516094) +USDCBridgeMigrator_updateChainSelectorMechanism:test_PrimaryMechanism_Success() (gas: 135930) +USDCBridgeMigrator_updateChainSelectorMechanism:test_WhileMigrationPause_Revert() (gas: 109773) +USDCBridgeMigrator_updateChainSelectorMechanism:test_cannotRevertChainMechanism_afterMigration_Revert() (gas: 313216) +USDCBridgeMigrator_updateChainSelectorMechanism:test_invalidPermissions_Revert() (gas: 39321) +USDCBridgeMigrator_updateChainSelectorMechanism:test_lockOrBurn_then_BurnInCCTPMigration_Success() (gas: 309779) +USDCBridgeMigrator_updateChainSelectorMechanism:test_onLockReleaseMechanism_Success() (gas: 147037) +USDCBridgeMigrator_updateChainSelectorMechanism:test_onLockReleaseMechanism_thenswitchToPrimary_Success() (gas: 209263) USDCTokenPool__validateMessage:test_ValidateInvalidMessage_Revert() (gas: 25854) USDCTokenPool_lockOrBurn:test_CallerIsNotARampOnRouter_Revert() (gas: 35504) USDCTokenPool_lockOrBurn:test_LockOrBurnWithAllowList_Revert() (gas: 30235) diff --git a/contracts/src/v0.8/ccip/test/pools/BurnFromMintTokenPool.t.sol b/contracts/src/v0.8/ccip/test/pools/BurnFromMintTokenPool/BurnFromMintTokenPool.lockOrBurn.t.sol similarity index 76% rename from contracts/src/v0.8/ccip/test/pools/BurnFromMintTokenPool.t.sol rename to contracts/src/v0.8/ccip/test/pools/BurnFromMintTokenPool/BurnFromMintTokenPool.lockOrBurn.t.sol index b5967e74d1e..5074d573e2f 100644 --- a/contracts/src/v0.8/ccip/test/pools/BurnFromMintTokenPool.t.sol +++ b/contracts/src/v0.8/ccip/test/pools/BurnFromMintTokenPool/BurnFromMintTokenPool.lockOrBurn.t.sol @@ -1,29 +1,15 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; -import {Pool} from "../../libraries/Pool.sol"; -import {RateLimiter} from "../../libraries/RateLimiter.sol"; -import {BurnFromMintTokenPool} from "../../pools/BurnFromMintTokenPool.sol"; -import {TokenPool} from "../../pools/TokenPool.sol"; -import {BurnMintSetup} from "./BurnMintSetup.t.sol"; +import {Pool} from "../../../libraries/Pool.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {BurnFromMintTokenPoolSetup} from "./BurnFromMintTokenPoolSetup.t.sol"; -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; - -contract BurnFromMintTokenPoolSetup is BurnMintSetup { - BurnFromMintTokenPool internal s_pool; - - function setUp() public virtual override { - BurnMintSetup.setUp(); - - s_pool = new BurnFromMintTokenPool(s_burnMintERC677, new address[](0), address(s_mockRMN), address(s_sourceRouter)); - s_burnMintERC677.grantMintAndBurnRoles(address(s_pool)); - - _applyChainUpdates(address(s_pool)); - } -} +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; contract BurnFromMintTokenPool_lockOrBurn is BurnFromMintTokenPoolSetup { - function test_Setup_Success() public view { + function test_setup_Success() public view { assertEq(address(s_burnMintERC677), address(s_pool.getToken())); assertEq(address(s_mockRMN), s_pool.getRmnProxy()); assertEq(false, s_pool.getAllowListEnabled()); diff --git a/contracts/src/v0.8/ccip/test/pools/BurnFromMintTokenPool/BurnFromMintTokenPoolSetup.t.sol b/contracts/src/v0.8/ccip/test/pools/BurnFromMintTokenPool/BurnFromMintTokenPoolSetup.t.sol new file mode 100644 index 00000000000..d743550b153 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/BurnFromMintTokenPool/BurnFromMintTokenPoolSetup.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {BurnFromMintTokenPool} from "../../../pools/BurnFromMintTokenPool.sol"; +import {BurnMintSetup} from "../BurnMintTokenPool/BurnMintSetup.t.sol"; + +contract BurnFromMintTokenPoolSetup is BurnMintSetup { + BurnFromMintTokenPool internal s_pool; + + function setUp() public virtual override { + BurnMintSetup.setUp(); + + s_pool = new BurnFromMintTokenPool(s_burnMintERC677, new address[](0), address(s_mockRMN), address(s_sourceRouter)); + s_burnMintERC677.grantMintAndBurnRoles(address(s_pool)); + + _applyChainUpdates(address(s_pool)); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/BurnMintSetup.t.sol b/contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool/BurnMintSetup.t.sol similarity index 83% rename from contracts/src/v0.8/ccip/test/pools/BurnMintSetup.t.sol rename to contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool/BurnMintSetup.t.sol index 7bf0ce57d5b..7b3d875de4c 100644 --- a/contracts/src/v0.8/ccip/test/pools/BurnMintSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool/BurnMintSetup.t.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; -import {BurnMintERC677} from "../../../shared/token/ERC677/BurnMintERC677.sol"; -import {Router} from "../../Router.sol"; -import {BurnMintTokenPool} from "../../pools/BurnMintTokenPool.sol"; -import {TokenPool} from "../../pools/TokenPool.sol"; -import {RouterSetup} from "../router/RouterSetup.t.sol"; +import {BurnMintERC677} from "../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {Router} from "../../../Router.sol"; +import {BurnMintTokenPool} from "../../../pools/BurnMintTokenPool.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {RouterSetup} from "../../router/RouterSetup.t.sol"; contract BurnMintSetup is RouterSetup { BurnMintERC677 internal s_burnMintERC677; diff --git a/contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool.t.sol b/contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool/BurnMintTokenPool.lockOrBurn.t.sol similarity index 54% rename from contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool.t.sol rename to contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool/BurnMintTokenPool.lockOrBurn.t.sol index 8a6d047380c..4c520af27b6 100644 --- a/contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool.t.sol +++ b/contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool/BurnMintTokenPool.lockOrBurn.t.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; -import {Pool} from "../../libraries/Pool.sol"; -import {RateLimiter} from "../../libraries/RateLimiter.sol"; -import {BurnMintTokenPool} from "../../pools/BurnMintTokenPool.sol"; -import {TokenPool} from "../../pools/TokenPool.sol"; +import {Pool} from "../../../libraries/Pool.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; +import {BurnMintTokenPool} from "../../../pools/BurnMintTokenPool.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; import {BurnMintSetup} from "./BurnMintSetup.t.sol"; -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; contract BurnMintTokenPoolSetup is BurnMintSetup { BurnMintTokenPool internal s_pool; @@ -98,71 +98,3 @@ contract BurnMintTokenPool_lockOrBurn is BurnMintTokenPoolSetup { ); } } - -contract BurnMintTokenPool_releaseOrMint is BurnMintTokenPoolSetup { - function test_PoolMint_Success() public { - uint256 amount = 1e19; - address receiver = makeAddr("receiver_address"); - - vm.startPrank(s_burnMintOffRamp); - - vm.expectEmit(); - emit IERC20.Transfer(address(0), receiver, amount); - - s_pool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: bytes(""), - receiver: receiver, - amount: amount, - localToken: address(s_burnMintERC677), - remoteChainSelector: DEST_CHAIN_SELECTOR, - sourcePoolAddress: abi.encode(s_remoteBurnMintPool), - sourcePoolData: "", - offchainTokenData: "" - }) - ); - - assertEq(s_burnMintERC677.balanceOf(receiver), amount); - } - - function test_PoolMintNotHealthy_Revert() public { - // Should not mint tokens if cursed. - s_mockRMN.setGlobalCursed(true); - uint256 before = s_burnMintERC677.balanceOf(OWNER); - vm.startPrank(s_burnMintOffRamp); - - vm.expectRevert(TokenPool.CursedByRMN.selector); - s_pool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: bytes(""), - receiver: OWNER, - amount: 1e5, - localToken: address(s_burnMintERC677), - remoteChainSelector: DEST_CHAIN_SELECTOR, - sourcePoolAddress: _generateSourceTokenData().sourcePoolAddress, - sourcePoolData: _generateSourceTokenData().extraData, - offchainTokenData: "" - }) - ); - - assertEq(s_burnMintERC677.balanceOf(OWNER), before); - } - - function test_ChainNotAllowed_Revert() public { - uint64 wrongChainSelector = 8838833; - - vm.expectRevert(abi.encodeWithSelector(TokenPool.ChainNotAllowed.selector, wrongChainSelector)); - s_pool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: bytes(""), - receiver: OWNER, - amount: 1, - localToken: address(s_burnMintERC677), - remoteChainSelector: wrongChainSelector, - sourcePoolAddress: _generateSourceTokenData().sourcePoolAddress, - sourcePoolData: _generateSourceTokenData().extraData, - offchainTokenData: "" - }) - ); - } -} diff --git a/contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool/BurnMintTokenPool.releaseOrMint.t.sol b/contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool/BurnMintTokenPool.releaseOrMint.t.sol new file mode 100644 index 00000000000..e16d4c7b59d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool/BurnMintTokenPool.releaseOrMint.t.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Pool} from "../../../libraries/Pool.sol"; +import {BurnMintTokenPool} from "../../../pools/BurnMintTokenPool.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {BurnMintSetup} from "./BurnMintSetup.t.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +contract BurnMintTokenPoolSetup is BurnMintSetup { + BurnMintTokenPool internal s_pool; + + function setUp() public virtual override { + BurnMintSetup.setUp(); + + s_pool = new BurnMintTokenPool(s_burnMintERC677, new address[](0), address(s_mockRMN), address(s_sourceRouter)); + s_burnMintERC677.grantMintAndBurnRoles(address(s_pool)); + + _applyChainUpdates(address(s_pool)); + } +} + +contract BurnMintTokenPool_releaseOrMint is BurnMintTokenPoolSetup { + function test_PoolMint_Success() public { + uint256 amount = 1e19; + address receiver = makeAddr("receiver_address"); + + vm.startPrank(s_burnMintOffRamp); + + vm.expectEmit(); + emit IERC20.Transfer(address(0), receiver, amount); + + s_pool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: bytes(""), + receiver: receiver, + amount: amount, + localToken: address(s_burnMintERC677), + remoteChainSelector: DEST_CHAIN_SELECTOR, + sourcePoolAddress: abi.encode(s_remoteBurnMintPool), + sourcePoolData: "", + offchainTokenData: "" + }) + ); + + assertEq(s_burnMintERC677.balanceOf(receiver), amount); + } + + function test_PoolMintNotHealthy_Revert() public { + // Should not mint tokens if cursed. + s_mockRMN.setGlobalCursed(true); + uint256 before = s_burnMintERC677.balanceOf(OWNER); + vm.startPrank(s_burnMintOffRamp); + + vm.expectRevert(TokenPool.CursedByRMN.selector); + s_pool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: bytes(""), + receiver: OWNER, + amount: 1e5, + localToken: address(s_burnMintERC677), + remoteChainSelector: DEST_CHAIN_SELECTOR, + sourcePoolAddress: _generateSourceTokenData().sourcePoolAddress, + sourcePoolData: _generateSourceTokenData().extraData, + offchainTokenData: "" + }) + ); + + assertEq(s_burnMintERC677.balanceOf(OWNER), before); + } + + function test_ChainNotAllowed_Revert() public { + uint64 wrongChainSelector = 8838833; + + vm.expectRevert(abi.encodeWithSelector(TokenPool.ChainNotAllowed.selector, wrongChainSelector)); + s_pool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: bytes(""), + receiver: OWNER, + amount: 1, + localToken: address(s_burnMintERC677), + remoteChainSelector: wrongChainSelector, + sourcePoolAddress: _generateSourceTokenData().sourcePoolAddress, + sourcePoolData: _generateSourceTokenData().extraData, + offchainTokenData: "" + }) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/BurnMintWithLockReleaseFlagTokenPool.t.sol b/contracts/src/v0.8/ccip/test/pools/BurnMintWithLockReleaseFlagTokenPool/BurnMintWithLockReleaseFlagTokenPool.lockOrBurn.t.sol similarity index 76% rename from contracts/src/v0.8/ccip/test/pools/BurnMintWithLockReleaseFlagTokenPool.t.sol rename to contracts/src/v0.8/ccip/test/pools/BurnMintWithLockReleaseFlagTokenPool/BurnMintWithLockReleaseFlagTokenPool.lockOrBurn.t.sol index c9080a0e145..7392dc1ce83 100644 --- a/contracts/src/v0.8/ccip/test/pools/BurnMintWithLockReleaseFlagTokenPool.t.sol +++ b/contracts/src/v0.8/ccip/test/pools/BurnMintWithLockReleaseFlagTokenPool/BurnMintWithLockReleaseFlagTokenPool.lockOrBurn.t.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; -import {Pool} from "../../libraries/Pool.sol"; -import {RateLimiter} from "../../libraries/RateLimiter.sol"; -import {TokenPool} from "../../pools/TokenPool.sol"; -import {BurnMintWithLockReleaseFlagTokenPool} from "../../pools/USDC/BurnMintWithLockReleaseFlagTokenPool.sol"; +import {Pool} from "../../../libraries/Pool.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {BurnMintWithLockReleaseFlagTokenPool} from "../../../pools/USDC/BurnMintWithLockReleaseFlagTokenPool.sol"; -import {LOCK_RELEASE_FLAG} from "../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; -import {BurnMintSetup} from "./BurnMintSetup.t.sol"; +import {LOCK_RELEASE_FLAG} from "../../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; +import {BurnMintSetup} from "../BurnMintTokenPool/BurnMintSetup.t.sol"; -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; contract BurnMintWithLockReleaseFlagTokenPoolSetup is BurnMintSetup { BurnMintWithLockReleaseFlagTokenPool internal s_pool; diff --git a/contracts/src/v0.8/ccip/test/pools/BurnWithFromMintTokenPool.t.sol b/contracts/src/v0.8/ccip/test/pools/BurnWithFromMintTokenPool/BurnWithFromMintTokenPool.lockOrBurn.t.sol similarity index 88% rename from contracts/src/v0.8/ccip/test/pools/BurnWithFromMintTokenPool.t.sol rename to contracts/src/v0.8/ccip/test/pools/BurnWithFromMintTokenPool/BurnWithFromMintTokenPool.lockOrBurn.t.sol index 92e871708da..7595bf76c69 100644 --- a/contracts/src/v0.8/ccip/test/pools/BurnWithFromMintTokenPool.t.sol +++ b/contracts/src/v0.8/ccip/test/pools/BurnWithFromMintTokenPool/BurnWithFromMintTokenPool.lockOrBurn.t.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; -import {Pool} from "../../libraries/Pool.sol"; -import {RateLimiter} from "../../libraries/RateLimiter.sol"; -import {BurnWithFromMintTokenPool} from "../../pools/BurnWithFromMintTokenPool.sol"; -import {TokenPool} from "../../pools/TokenPool.sol"; -import {BurnMintSetup} from "./BurnMintSetup.t.sol"; +import {Pool} from "../../../libraries/Pool.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; +import {BurnWithFromMintTokenPool} from "../../../pools/BurnWithFromMintTokenPool.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {BurnMintSetup} from "../BurnMintTokenPool/BurnMintSetup.t.sol"; -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; contract BurnWithFromMintTokenPoolSetup is BurnMintSetup { BurnWithFromMintTokenPool internal s_pool; diff --git a/contracts/src/v0.8/ccip/test/pools/HybridLockReleaseUSDCTokenPool.t.sol b/contracts/src/v0.8/ccip/test/pools/HybridLockReleaseUSDCTokenPool.t.sol deleted file mode 100644 index 091db560b34..00000000000 --- a/contracts/src/v0.8/ccip/test/pools/HybridLockReleaseUSDCTokenPool.t.sol +++ /dev/null @@ -1,961 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {ILiquidityContainer} from "../../../liquiditymanager/interfaces/ILiquidityContainer.sol"; -import {IBurnMintERC20} from "../../../shared/token/ERC20/IBurnMintERC20.sol"; -import {ITokenMessenger} from "../../pools/USDC/ITokenMessenger.sol"; - -import {BurnMintERC677} from "../../../shared/token/ERC677/BurnMintERC677.sol"; -import {Router} from "../../Router.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {Pool} from "../../libraries/Pool.sol"; -import {RateLimiter} from "../../libraries/RateLimiter.sol"; - -import {TokenPool} from "../../pools/TokenPool.sol"; -import {HybridLockReleaseUSDCTokenPool} from "../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; -import {LOCK_RELEASE_FLAG} from "../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; -import {USDCBridgeMigrator} from "../../pools/USDC/USDCBridgeMigrator.sol"; -import {USDCTokenPool} from "../../pools/USDC/USDCTokenPool.sol"; -import {BaseTest} from "../BaseTest.t.sol"; -import {MockE2EUSDCTransmitter} from "../mocks/MockE2EUSDCTransmitter.sol"; -import {MockUSDCTokenMessenger} from "../mocks/MockUSDCTokenMessenger.sol"; - -contract USDCTokenPoolSetup is BaseTest { - IBurnMintERC20 internal s_token; - MockUSDCTokenMessenger internal s_mockUSDC; - MockE2EUSDCTransmitter internal s_mockUSDCTransmitter; - uint32 internal constant USDC_DEST_TOKEN_GAS = 150_000; - - struct USDCMessage { - uint32 version; - uint32 sourceDomain; - uint32 destinationDomain; - uint64 nonce; - bytes32 sender; - bytes32 recipient; - bytes32 destinationCaller; - bytes messageBody; - } - - uint32 internal constant SOURCE_DOMAIN_IDENTIFIER = 0x02020202; - uint32 internal constant DEST_DOMAIN_IDENTIFIER = 0; - - bytes32 internal constant SOURCE_CHAIN_TOKEN_SENDER = bytes32(uint256(uint160(0x01111111221))); - address internal constant SOURCE_CHAIN_USDC_POOL = address(0x23789765456789); - address internal constant DEST_CHAIN_USDC_POOL = address(0x987384873458734); - address internal constant DEST_CHAIN_USDC_TOKEN = address(0x23598918358198766); - - address internal s_routerAllowedOnRamp = address(3456); - address internal s_routerAllowedOffRamp = address(234); - Router internal s_router; - - HybridLockReleaseUSDCTokenPool internal s_usdcTokenPool; - HybridLockReleaseUSDCTokenPool internal s_usdcTokenPoolTransferLiquidity; - address[] internal s_allowedList; - - function setUp() public virtual override { - BaseTest.setUp(); - BurnMintERC677 usdcToken = new BurnMintERC677("LINK", "LNK", 18, 0); - s_token = usdcToken; - deal(address(s_token), OWNER, type(uint256).max); - _setUpRamps(); - - s_mockUSDCTransmitter = new MockE2EUSDCTransmitter(0, DEST_DOMAIN_IDENTIFIER, address(s_token)); - s_mockUSDC = new MockUSDCTokenMessenger(0, address(s_mockUSDCTransmitter)); - - usdcToken.grantMintAndBurnRoles(address(s_mockUSDCTransmitter)); - - s_usdcTokenPool = - new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); - - s_usdcTokenPoolTransferLiquidity = - new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); - - usdcToken.grantMintAndBurnRoles(address(s_mockUSDC)); - usdcToken.grantMintAndBurnRoles(address(s_usdcTokenPool)); - - TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](2); - chainUpdates[0] = TokenPool.ChainUpdate({ - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - remotePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), - remoteTokenAddress: abi.encode(address(s_token)), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - chainUpdates[1] = TokenPool.ChainUpdate({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - remotePoolAddress: abi.encode(DEST_CHAIN_USDC_POOL), - remoteTokenAddress: abi.encode(DEST_CHAIN_USDC_TOKEN), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - - s_usdcTokenPool.applyChainUpdates(chainUpdates); - - USDCTokenPool.DomainUpdate[] memory domains = new USDCTokenPool.DomainUpdate[](1); - domains[0] = USDCTokenPool.DomainUpdate({ - destChainSelector: DEST_CHAIN_SELECTOR, - domainIdentifier: 9999, - allowedCaller: keccak256("allowedCaller"), - enabled: true - }); - - s_usdcTokenPool.setDomains(domains); - - vm.expectEmit(); - emit HybridLockReleaseUSDCTokenPool.LiquidityProviderSet(address(0), OWNER, DEST_CHAIN_SELECTOR); - - s_usdcTokenPool.setLiquidityProvider(DEST_CHAIN_SELECTOR, OWNER); - s_usdcTokenPool.setLiquidityProvider(SOURCE_CHAIN_SELECTOR, OWNER); - } - - function _setUpRamps() internal { - s_router = new Router(address(s_token), address(s_mockRMN)); - - Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); - onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_routerAllowedOnRamp}); - Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); - address[] memory offRamps = new address[](1); - offRamps[0] = s_routerAllowedOffRamp; - offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: offRamps[0]}); - - s_router.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); - } - - function _generateUSDCMessage( - USDCMessage memory usdcMessage - ) internal pure returns (bytes memory) { - return abi.encodePacked( - usdcMessage.version, - usdcMessage.sourceDomain, - usdcMessage.destinationDomain, - usdcMessage.nonce, - usdcMessage.sender, - usdcMessage.recipient, - usdcMessage.destinationCaller, - usdcMessage.messageBody - ); - } -} - -contract HybridUSDCTokenPoolTests is USDCTokenPoolSetup { - function test_LockOrBurn_onLockReleaseMechanism_Success() public { - bytes32 receiver = bytes32(uint256(uint160(STRANGER))); - - // Mark the destination chain as supporting CCTP, so use L/R instead. - uint64[] memory destChainAdds = new uint64[](1); - destChainAdds[0] = DEST_CHAIN_SELECTOR; - - s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); - - assertTrue( - s_usdcTokenPool.shouldUseLockRelease(DEST_CHAIN_SELECTOR), - "Lock/Release mech not configured for outgoing message to DEST_CHAIN_SELECTOR" - ); - - uint256 amount = 1e6; - - s_token.transfer(address(s_usdcTokenPool), amount); - - vm.startPrank(s_routerAllowedOnRamp); - - vm.expectEmit(); - emit TokenPool.Locked(s_routerAllowedOnRamp, amount); - - s_usdcTokenPool.lockOrBurn( - Pool.LockOrBurnInV1({ - originalSender: OWNER, - receiver: abi.encodePacked(receiver), - amount: amount, - remoteChainSelector: DEST_CHAIN_SELECTOR, - localToken: address(s_token) - }) - ); - - assertEq(s_token.balanceOf(address(s_usdcTokenPool)), amount, "Incorrect token amount in the tokenPool"); - } - - function test_MintOrRelease_OnLockReleaseMechanism_Success() public { - address recipient = address(1234); - - // Designate the SOURCE_CHAIN as not using native-USDC, and so the L/R mechanism must be used instead - uint64[] memory destChainAdds = new uint64[](1); - destChainAdds[0] = SOURCE_CHAIN_SELECTOR; - - s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); - - assertTrue( - s_usdcTokenPool.shouldUseLockRelease(SOURCE_CHAIN_SELECTOR), - "Lock/Release mech not configured for incoming message from SOURCE_CHAIN_SELECTOR" - ); - - vm.startPrank(OWNER); - s_usdcTokenPool.setLiquidityProvider(SOURCE_CHAIN_SELECTOR, OWNER); - - // Add 1e12 liquidity so that there's enough to release - vm.startPrank(s_usdcTokenPool.getLiquidityProvider(SOURCE_CHAIN_SELECTOR)); - - s_token.approve(address(s_usdcTokenPool), type(uint256).max); - - uint256 liquidityAmount = 1e12; - s_usdcTokenPool.provideLiquidity(SOURCE_CHAIN_SELECTOR, liquidityAmount); - - Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ - sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), - destTokenAddress: abi.encode(address(s_usdcTokenPool)), - extraData: abi.encode(USDCTokenPool.SourceTokenDataPayload({nonce: 1, sourceDomain: SOURCE_DOMAIN_IDENTIFIER})), - destGasAmount: USDC_DEST_TOKEN_GAS - }); - - uint256 amount = 1e6; - - vm.startPrank(s_routerAllowedOffRamp); - - vm.expectEmit(); - emit TokenPool.Released(s_routerAllowedOffRamp, recipient, amount); - - Pool.ReleaseOrMintOutV1 memory poolReturnDataV1 = s_usdcTokenPool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: abi.encode(OWNER), - receiver: recipient, - amount: amount, - localToken: address(s_token), - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - sourcePoolAddress: sourceTokenData.sourcePoolAddress, - sourcePoolData: abi.encode(LOCK_RELEASE_FLAG), - offchainTokenData: "" - }) - ); - - assertEq(poolReturnDataV1.destinationAmount, amount, "destinationAmount and actual amount transferred differ"); - - // Simulate the off-ramp forwarding tokens to the recipient on destination chain - // s_token.transfer(recipient, amount); - - assertEq( - s_token.balanceOf(address(s_usdcTokenPool)), - liquidityAmount - amount, - "Incorrect remaining liquidity in TokenPool" - ); - assertEq(s_token.balanceOf(recipient), amount, "Tokens not transferred to recipient"); - } - - function test_LockOrBurn_PrimaryMechanism_Success() public { - bytes32 receiver = bytes32(uint256(uint160(STRANGER))); - uint256 amount = 1; - - vm.startPrank(OWNER); - - s_token.transfer(address(s_usdcTokenPool), amount); - - vm.startPrank(s_routerAllowedOnRamp); - - USDCTokenPool.Domain memory expectedDomain = s_usdcTokenPool.getDomain(DEST_CHAIN_SELECTOR); - - vm.expectEmit(); - emit RateLimiter.TokensConsumed(amount); - - vm.expectEmit(); - emit ITokenMessenger.DepositForBurn( - s_mockUSDC.s_nonce(), - address(s_token), - amount, - address(s_usdcTokenPool), - receiver, - expectedDomain.domainIdentifier, - s_mockUSDC.DESTINATION_TOKEN_MESSENGER(), - expectedDomain.allowedCaller - ); - - vm.expectEmit(); - emit TokenPool.Burned(s_routerAllowedOnRamp, amount); - - Pool.LockOrBurnOutV1 memory poolReturnDataV1 = s_usdcTokenPool.lockOrBurn( - Pool.LockOrBurnInV1({ - originalSender: OWNER, - receiver: abi.encodePacked(receiver), - amount: amount, - remoteChainSelector: DEST_CHAIN_SELECTOR, - localToken: address(s_token) - }) - ); - - uint64 nonce = abi.decode(poolReturnDataV1.destPoolData, (uint64)); - assertEq(s_mockUSDC.s_nonce() - 1, nonce); - } - - // https://etherscan.io/tx/0xac9f501fe0b76df1f07a22e1db30929fd12524bc7068d74012dff948632f0883 - function test_MintOrRelease_incomingMessageWithPrimaryMechanism() public { - bytes memory encodedUsdcMessage = - hex"000000000000000300000000000000000000127a00000000000000000000000019330d10d9cc8751218eaf51e8885d058642e08a000000000000000000000000bd3fa81b58ba92a82136038b25adec7066af3155000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000004af08f56978be7dce2d1be3c65c005b41e79401c000000000000000000000000000000000000000000000000000000002057ff7a0000000000000000000000003a23f943181408eac424116af7b7790c94cb97a50000000000000000000000000000000000000000000000000000000000000000000000000000008274119237535fd659626b090f87e365ff89ebc7096bb32e8b0e85f155626b73ae7c4bb2485c184b7cc3cf7909045487890b104efb62ae74a73e32901bdcec91df1bb9ee08ccb014fcbcfe77b74d1263fd4e0b0e8de05d6c9a5913554364abfd5ea768b222f50c715908183905d74044bb2b97527c7e70ae7983c443a603557cac3b1c000000000000000000000000000000000000000000000000000000000000"; - bytes memory attestation = bytes("attestation bytes"); - - uint32 nonce = 4730; - uint32 sourceDomain = 3; - uint256 amount = 100; - - Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ - sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), - destTokenAddress: abi.encode(address(s_usdcTokenPool)), - extraData: abi.encode(USDCTokenPool.SourceTokenDataPayload({nonce: nonce, sourceDomain: sourceDomain})), - destGasAmount: USDC_DEST_TOKEN_GAS - }); - - // The mocked receiver does not release the token to the pool, so we manually do it here - deal(address(s_token), address(s_usdcTokenPool), amount); - - bytes memory offchainTokenData = - abi.encode(USDCTokenPool.MessageAndAttestation({message: encodedUsdcMessage, attestation: attestation})); - - vm.expectCall( - address(s_mockUSDCTransmitter), - abi.encodeWithSelector(MockE2EUSDCTransmitter.receiveMessage.selector, encodedUsdcMessage, attestation) - ); - - vm.startPrank(s_routerAllowedOffRamp); - s_usdcTokenPool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: abi.encode(OWNER), - receiver: OWNER, - amount: amount, - localToken: address(s_token), - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - sourcePoolAddress: sourceTokenData.sourcePoolAddress, - sourcePoolData: sourceTokenData.extraData, - offchainTokenData: offchainTokenData - }) - ); - } - - function test_LockOrBurn_LockReleaseMechanism_then_switchToPrimary_Success() public { - // Test Enabling the LR mechanism and sending an outgoing message - test_LockOrBurn_PrimaryMechanism_Success(); - - // Disable the LR mechanism so that primary CCTP is used and then attempt to send a message - uint64[] memory destChainRemoves = new uint64[](1); - destChainRemoves[0] = DEST_CHAIN_SELECTOR; - - vm.startPrank(OWNER); - - vm.expectEmit(); - emit HybridLockReleaseUSDCTokenPool.LockReleaseDisabled(DEST_CHAIN_SELECTOR); - - s_usdcTokenPool.updateChainSelectorMechanisms(destChainRemoves, new uint64[](0)); - - // Send an outgoing message - test_LockOrBurn_PrimaryMechanism_Success(); - } - - function test_MintOrRelease_OnLockReleaseMechanism_then_switchToPrimary_Success() public { - test_MintOrRelease_OnLockReleaseMechanism_Success(); - - // Disable the LR mechanism so that primary CCTP is used and then attempt to send a message - uint64[] memory destChainRemoves = new uint64[](1); - destChainRemoves[0] = SOURCE_CHAIN_SELECTOR; - - vm.startPrank(OWNER); - - vm.expectEmit(); - emit HybridLockReleaseUSDCTokenPool.LockReleaseDisabled(SOURCE_CHAIN_SELECTOR); - - s_usdcTokenPool.updateChainSelectorMechanisms(destChainRemoves, new uint64[](0)); - - vm.expectEmit(); - emit HybridLockReleaseUSDCTokenPool.LiquidityProviderSet(OWNER, OWNER, SOURCE_CHAIN_SELECTOR); - - s_usdcTokenPool.setLiquidityProvider(SOURCE_CHAIN_SELECTOR, OWNER); - - // Test incoming on the primary mechanism after disable LR, simulating Circle's new support for CCTP on - // DEST_CHAIN_SELECTOR - test_MintOrRelease_incomingMessageWithPrimaryMechanism(); - } - - function test_LockOrBurn_WhileMigrationPause_Revert() public { - // Mark the destination chain as supporting CCTP, so use L/R instead. - uint64[] memory destChainAdds = new uint64[](1); - destChainAdds[0] = DEST_CHAIN_SELECTOR; - - s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); - - // Create a fake migration proposal - s_usdcTokenPool.proposeCCTPMigration(DEST_CHAIN_SELECTOR); - - assertEq(s_usdcTokenPool.getCurrentProposedCCTPChainMigration(), DEST_CHAIN_SELECTOR); - - bytes32 receiver = bytes32(uint256(uint160(STRANGER))); - - assertTrue( - s_usdcTokenPool.shouldUseLockRelease(DEST_CHAIN_SELECTOR), - "Lock Release mech not configured for outgoing message to DEST_CHAIN_SELECTOR" - ); - - uint256 amount = 1e6; - - s_token.transfer(address(s_usdcTokenPool), amount); - - vm.startPrank(s_routerAllowedOnRamp); - - // Expect the lockOrBurn to fail because a pending CCTP-Migration has paused outgoing messages on CCIP - vm.expectRevert( - abi.encodeWithSelector(HybridLockReleaseUSDCTokenPool.LanePausedForCCTPMigration.selector, DEST_CHAIN_SELECTOR) - ); - - s_usdcTokenPool.lockOrBurn( - Pool.LockOrBurnInV1({ - originalSender: OWNER, - receiver: abi.encodePacked(receiver), - amount: amount, - remoteChainSelector: DEST_CHAIN_SELECTOR, - localToken: address(s_token) - }) - ); - } - - function test_ReleaseOrMint_WhileMigrationPause_Revert() public { - address recipient = address(1234); - - // Designate the SOURCE_CHAIN as not using native-USDC, and so the L/R mechanism must be used instead - uint64[] memory destChainAdds = new uint64[](1); - destChainAdds[0] = SOURCE_CHAIN_SELECTOR; - - s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); - - assertTrue( - s_usdcTokenPool.shouldUseLockRelease(SOURCE_CHAIN_SELECTOR), - "Lock/Release mech not configured for incoming message from SOURCE_CHAIN_SELECTOR" - ); - - vm.startPrank(OWNER); - - vm.expectEmit(); - emit USDCBridgeMigrator.CCTPMigrationProposed(SOURCE_CHAIN_SELECTOR); - - // Propose the migration to CCTP - s_usdcTokenPool.proposeCCTPMigration(SOURCE_CHAIN_SELECTOR); - - Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ - sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), - destTokenAddress: abi.encode(address(s_usdcTokenPool)), - extraData: abi.encode(USDCTokenPool.SourceTokenDataPayload({nonce: 1, sourceDomain: SOURCE_DOMAIN_IDENTIFIER})), - destGasAmount: USDC_DEST_TOKEN_GAS - }); - - bytes memory sourcePoolDataLockRelease = abi.encode(LOCK_RELEASE_FLAG); - - uint256 amount = 1e6; - - vm.startPrank(s_routerAllowedOffRamp); - - // Expect revert because the lane is paused and no incoming messages should be allowed - vm.expectRevert( - abi.encodeWithSelector(HybridLockReleaseUSDCTokenPool.LanePausedForCCTPMigration.selector, SOURCE_CHAIN_SELECTOR) - ); - - s_usdcTokenPool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: abi.encode(OWNER), - receiver: recipient, - amount: amount, - localToken: address(s_token), - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - sourcePoolAddress: sourceTokenData.sourcePoolAddress, - sourcePoolData: sourcePoolDataLockRelease, - offchainTokenData: "" - }) - ); - } - - function test_transferLiquidity_Success() public { - // Set as the OWNER so we can provide liquidity - vm.startPrank(OWNER); - - s_usdcTokenPool.setLiquidityProvider(DEST_CHAIN_SELECTOR, OWNER); - s_token.approve(address(s_usdcTokenPool), type(uint256).max); - - uint256 liquidityAmount = 1e9; - - // Provide some liquidity to the pool - s_usdcTokenPool.provideLiquidity(DEST_CHAIN_SELECTOR, liquidityAmount); - - // Set the new token pool as the rebalancer - s_usdcTokenPool.transferOwnership(address(s_usdcTokenPoolTransferLiquidity)); - - vm.expectEmit(); - emit ILiquidityContainer.LiquidityRemoved(address(s_usdcTokenPoolTransferLiquidity), liquidityAmount); - - vm.expectEmit(); - emit HybridLockReleaseUSDCTokenPool.LiquidityTransferred( - address(s_usdcTokenPool), DEST_CHAIN_SELECTOR, liquidityAmount - ); - - s_usdcTokenPoolTransferLiquidity.transferLiquidity(address(s_usdcTokenPool), DEST_CHAIN_SELECTOR); - - assertEq( - s_usdcTokenPool.owner(), - address(s_usdcTokenPoolTransferLiquidity), - "Ownership of the old pool should be transferred to the new pool" - ); - - assertEq( - s_usdcTokenPoolTransferLiquidity.getLockedTokensForChain(DEST_CHAIN_SELECTOR), - liquidityAmount, - "Tokens locked for dest chain doesn't match expected amount in storage" - ); - - assertEq( - s_usdcTokenPool.getLockedTokensForChain(DEST_CHAIN_SELECTOR), - 0, - "Tokens locked for dest chain in old token pool doesn't match expected amount in storage" - ); - - assertEq( - s_token.balanceOf(address(s_usdcTokenPoolTransferLiquidity)), - liquidityAmount, - "Liquidity amount of tokens should be new in new pool, but aren't" - ); - - assertEq( - s_token.balanceOf(address(s_usdcTokenPool)), - 0, - "Liquidity amount of tokens should be zero in old pool, but aren't" - ); - } - - function test_cannotTransferLiquidityDuringPendingMigration_Revert() public { - // Set as the OWNER so we can provide liquidity - vm.startPrank(OWNER); - - // Mark the destination chain as supporting CCTP, so use L/R instead. - uint64[] memory destChainAdds = new uint64[](1); - destChainAdds[0] = DEST_CHAIN_SELECTOR; - - s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); - - s_usdcTokenPool.setLiquidityProvider(DEST_CHAIN_SELECTOR, OWNER); - s_token.approve(address(s_usdcTokenPool), type(uint256).max); - - uint256 liquidityAmount = 1e9; - - // Provide some liquidity to the pool - s_usdcTokenPool.provideLiquidity(DEST_CHAIN_SELECTOR, liquidityAmount); - - // Set the new token pool as the rebalancer - s_usdcTokenPool.transferOwnership(address(s_usdcTokenPoolTransferLiquidity)); - - s_usdcTokenPool.proposeCCTPMigration(DEST_CHAIN_SELECTOR); - - vm.expectRevert( - abi.encodeWithSelector(HybridLockReleaseUSDCTokenPool.LanePausedForCCTPMigration.selector, DEST_CHAIN_SELECTOR) - ); - - s_usdcTokenPoolTransferLiquidity.transferLiquidity(address(s_usdcTokenPool), DEST_CHAIN_SELECTOR); - } -} - -contract HybridUSDCTokenPoolMigrationTests is HybridUSDCTokenPoolTests { - function test_lockOrBurn_then_BurnInCCTPMigration_Success() public { - bytes32 receiver = bytes32(uint256(uint160(STRANGER))); - address CIRCLE = makeAddr("CIRCLE CCTP Migrator"); - - // Mark the destination chain as supporting CCTP, so use L/R instead. - uint64[] memory destChainAdds = new uint64[](1); - destChainAdds[0] = DEST_CHAIN_SELECTOR; - - s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); - - assertTrue( - s_usdcTokenPool.shouldUseLockRelease(DEST_CHAIN_SELECTOR), - "Lock/Release mech not configured for outgoing message to DEST_CHAIN_SELECTOR" - ); - - uint256 amount = 1e6; - - s_token.transfer(address(s_usdcTokenPool), amount); - - vm.startPrank(s_routerAllowedOnRamp); - - vm.expectEmit(); - emit TokenPool.Locked(s_routerAllowedOnRamp, amount); - - s_usdcTokenPool.lockOrBurn( - Pool.LockOrBurnInV1({ - originalSender: OWNER, - receiver: abi.encodePacked(receiver), - amount: amount, - remoteChainSelector: DEST_CHAIN_SELECTOR, - localToken: address(s_token) - }) - ); - - // Ensure that the tokens are properly locked - assertEq(s_token.balanceOf(address(s_usdcTokenPool)), amount, "Incorrect token amount in the tokenPool"); - - assertEq( - s_usdcTokenPool.getLockedTokensForChain(DEST_CHAIN_SELECTOR), - amount, - "Internal locked token accounting is incorrect" - ); - - vm.startPrank(OWNER); - - vm.expectEmit(); - emit USDCBridgeMigrator.CircleMigratorAddressSet(CIRCLE); - - s_usdcTokenPool.setCircleMigratorAddress(CIRCLE); - - vm.expectEmit(); - emit USDCBridgeMigrator.CCTPMigrationProposed(DEST_CHAIN_SELECTOR); - - // Propose the migration to CCTP - s_usdcTokenPool.proposeCCTPMigration(DEST_CHAIN_SELECTOR); - - assertEq( - s_usdcTokenPool.getCurrentProposedCCTPChainMigration(), - DEST_CHAIN_SELECTOR, - "Current proposed chain migration does not match expected for DEST_CHAIN_SELECTOR" - ); - - // Impersonate the set circle address and execute the proposal - vm.startPrank(CIRCLE); - - vm.expectEmit(); - emit USDCBridgeMigrator.CCTPMigrationExecuted(DEST_CHAIN_SELECTOR, amount); - - // Ensure the call to the burn function is properly - vm.expectCall(address(s_token), abi.encodeWithSelector(bytes4(keccak256("burn(uint256)")), amount)); - - s_usdcTokenPool.burnLockedUSDC(); - - // Assert that the tokens were actually burned - assertEq(s_token.balanceOf(address(s_usdcTokenPool)), 0, "Tokens were not burned out of the tokenPool"); - - // Ensure the proposal slot was cleared and there's no tokens locked for the destination chain anymore - assertEq(s_usdcTokenPool.getCurrentProposedCCTPChainMigration(), 0, "Proposal Slot should be empty"); - assertEq( - s_usdcTokenPool.getLockedTokensForChain(DEST_CHAIN_SELECTOR), - 0, - "No tokens should be locked for DEST_CHAIN_SELECTOR after CCTP-approved burn" - ); - - assertFalse( - s_usdcTokenPool.shouldUseLockRelease(DEST_CHAIN_SELECTOR), "Lock/Release mech should be disabled after a burn" - ); - - test_LockOrBurn_PrimaryMechanism_Success(); - } - - function test_cancelExistingCCTPMigrationProposal() public { - vm.startPrank(OWNER); - - // Mark the destination chain as supporting CCTP, so use L/R instead. - uint64[] memory destChainAdds = new uint64[](1); - destChainAdds[0] = DEST_CHAIN_SELECTOR; - - s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); - - vm.expectEmit(); - emit USDCBridgeMigrator.CCTPMigrationProposed(DEST_CHAIN_SELECTOR); - - s_usdcTokenPool.proposeCCTPMigration(DEST_CHAIN_SELECTOR); - - assertEq( - s_usdcTokenPool.getCurrentProposedCCTPChainMigration(), - DEST_CHAIN_SELECTOR, - "migration proposal should exist, but doesn't" - ); - - vm.expectEmit(); - emit USDCBridgeMigrator.CCTPMigrationCancelled(DEST_CHAIN_SELECTOR); - - s_usdcTokenPool.cancelExistingCCTPMigrationProposal(); - - assertEq( - s_usdcTokenPool.getCurrentProposedCCTPChainMigration(), - 0, - "migration proposal exists, but shouldn't after being cancelled" - ); - - vm.expectRevert(USDCBridgeMigrator.NoMigrationProposalPending.selector); - s_usdcTokenPool.cancelExistingCCTPMigrationProposal(); - } - - function test_burnLockedUSDC_invalidPermissions_Revert() public { - address CIRCLE = makeAddr("CIRCLE"); - - vm.startPrank(OWNER); - - // Set the circle migrator address for later, but don't start pranking as it yet - s_usdcTokenPool.setCircleMigratorAddress(CIRCLE); - - vm.expectRevert(abi.encodeWithSelector(USDCBridgeMigrator.onlyCircle.selector)); - - // Should fail because only Circle can call this function - s_usdcTokenPool.burnLockedUSDC(); - - vm.startPrank(CIRCLE); - - vm.expectRevert(abi.encodeWithSelector(USDCBridgeMigrator.NoMigrationProposalPending.selector)); - s_usdcTokenPool.burnLockedUSDC(); - } - - function test_cannotModifyLiquidityWithoutPermissions_Revert() public { - address randomAddr = makeAddr("RANDOM"); - - vm.startPrank(randomAddr); - - vm.expectRevert(abi.encodeWithSelector(TokenPool.Unauthorized.selector, randomAddr)); - - // Revert because there's insufficient permissions for the DEST_CHAIN_SELECTOR to provide liquidity - s_usdcTokenPool.provideLiquidity(DEST_CHAIN_SELECTOR, 1e6); - } - - function test_cannotCancelANonExistentMigrationProposal() public { - vm.expectRevert(USDCBridgeMigrator.NoMigrationProposalPending.selector); - - // Proposal to migrate doesn't exist, and so the chain selector is zero, and therefore should revert - s_usdcTokenPool.cancelExistingCCTPMigrationProposal(); - } - - function test_unstickManualTxAfterMigration_destChain_Success() public { - address recipient = address(1234); - // Test the edge case where a tx is stuck in the manual tx queue and the destination chain is the one that - // should process is after a migration. I.E the message will have the Lock-Release flag set in the OffChainData, - // which should tell it to use the lock-release mechanism with the tokens provided. - - // We want the released amount to be 1e6, so to simulate the workflow, we sent those tokens to the contract as - // liquidity - uint256 amount = 1e6; - // Add 1e12 liquidity so that there's enough to release - vm.startPrank(s_usdcTokenPool.getLiquidityProvider(SOURCE_CHAIN_SELECTOR)); - - s_token.approve(address(s_usdcTokenPool), type(uint256).max); - s_usdcTokenPool.provideLiquidity(SOURCE_CHAIN_SELECTOR, amount); - - // By Default, the source chain will be indicated as use-CCTP so we need to change that. We create a message - // that will use the Lock-Release flag in the offchain data to indicate that the tokens should be released - // instead of minted since there's no attestation for us to use. - - vm.startPrank(s_routerAllowedOffRamp); - - vm.expectEmit(); - emit TokenPool.Released(s_routerAllowedOffRamp, recipient, amount); - - Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ - sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), - destTokenAddress: abi.encode(address(s_usdcTokenPool)), - extraData: abi.encode(USDCTokenPool.SourceTokenDataPayload({nonce: 1, sourceDomain: SOURCE_DOMAIN_IDENTIFIER})), - destGasAmount: USDC_DEST_TOKEN_GAS - }); - - Pool.ReleaseOrMintOutV1 memory poolReturnDataV1 = s_usdcTokenPool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: abi.encode(OWNER), - receiver: recipient, - amount: amount, - localToken: address(s_token), - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - sourcePoolAddress: sourceTokenData.sourcePoolAddress, - sourcePoolData: abi.encode(LOCK_RELEASE_FLAG), - offchainTokenData: "" - }) - ); - - // By this point, the tx should have executed, with the Lock-Release taking over, and being forwaded to the - // recipient - - assertEq(poolReturnDataV1.destinationAmount, amount, "destinationAmount and actual amount transferred differ"); - assertEq(s_token.balanceOf(address(s_usdcTokenPool)), 0, "Tokens should be transferred out of the pool"); - assertEq(s_token.balanceOf(recipient), amount, "Tokens should be transferred to the recipient"); - - // We also want to check that the system uses CCTP Burn/Mint for all other messages that don't have that flag - // which after a migration will mean all new messages. - - // The message should fail without an error because it failed to decode a non-existent attestation which would - // revert without an error - vm.expectRevert(); - - s_usdcTokenPool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: abi.encode(OWNER), - receiver: recipient, - amount: amount, - localToken: address(s_token), - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - sourcePoolAddress: sourceTokenData.sourcePoolAddress, - sourcePoolData: "", - offchainTokenData: "" - }) - ); - } - - function test_unstickManualTxAfterMigration_homeChain_Success() public { - address CIRCLE = makeAddr("CIRCLE"); - address recipient = address(1234); - - // Mark the destination chain as supporting CCTP, so use L/R instead. - uint64[] memory destChainAdds = new uint64[](1); - destChainAdds[0] = SOURCE_CHAIN_SELECTOR; - - s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); - - // Test the edge case where a tx is stuck in the manual tx queue and the source chain (mainnet) needs unsticking - // In this test we want 1e6 worth of tokens to be stuck, so first we provide liquidity to the pool >1e6 - - uint256 amount = 1e6; - // Add 1e12 liquidity so that there's enough to release - vm.startPrank(s_usdcTokenPool.getLiquidityProvider(SOURCE_CHAIN_SELECTOR)); - - s_token.approve(address(s_usdcTokenPool), type(uint256).max); - - // I picked 3x the amount to be stuck so that we can have enough to release with a buffer - s_usdcTokenPool.provideLiquidity(SOURCE_CHAIN_SELECTOR, amount * 3); - - // At this point in the process, the router will lock new messages, so we want to simulate excluding tokens - // stuck coming back from the destination, to the home chain. This way they can be released and not minted - // since there's no corresponding attestation to use for minting. - vm.startPrank(OWNER); - - s_usdcTokenPool.proposeCCTPMigration(SOURCE_CHAIN_SELECTOR); - - // Exclude the tokens from being burned and check for the event - vm.expectEmit(); - emit USDCBridgeMigrator.TokensExcludedFromBurn(SOURCE_CHAIN_SELECTOR, amount, (amount * 3) - amount); - - s_usdcTokenPool.excludeTokensFromBurn(SOURCE_CHAIN_SELECTOR, amount); - - assertEq( - s_usdcTokenPool.getLockedTokensForChain(SOURCE_CHAIN_SELECTOR), - (amount * 3), - "Tokens locked minus ones excluded from the burn should be 2e6" - ); - - assertEq( - s_usdcTokenPool.getExcludedTokensByChain(SOURCE_CHAIN_SELECTOR), - 1e6, - "1e6 tokens should be excluded from the burn" - ); - - s_usdcTokenPool.setCircleMigratorAddress(CIRCLE); - - vm.startPrank(CIRCLE); - - s_usdcTokenPool.burnLockedUSDC(); - - assertEq( - s_usdcTokenPool.getLockedTokensForChain(SOURCE_CHAIN_SELECTOR), 0, "All tokens should be burned out of the pool" - ); - - assertEq( - s_usdcTokenPool.getExcludedTokensByChain(SOURCE_CHAIN_SELECTOR), - 1e6, - "There should still be 1e6 tokens excluded from the burn" - ); - - assertEq(s_token.balanceOf(address(s_usdcTokenPool)), 1e6, "All tokens minus the excluded should be in the pool"); - - // Now that the burn is successful, we can release the tokens that were excluded from the burn - vm.startPrank(s_routerAllowedOffRamp); - - vm.expectEmit(); - emit TokenPool.Released(s_routerAllowedOffRamp, recipient, amount); - - Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ - sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), - destTokenAddress: abi.encode(address(s_usdcTokenPool)), - extraData: abi.encode(USDCTokenPool.SourceTokenDataPayload({nonce: 1, sourceDomain: SOURCE_DOMAIN_IDENTIFIER})), - destGasAmount: USDC_DEST_TOKEN_GAS - }); - - Pool.ReleaseOrMintOutV1 memory poolReturnDataV1 = s_usdcTokenPool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: abi.encode(OWNER), - receiver: recipient, - amount: amount, - localToken: address(s_token), - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - sourcePoolAddress: sourceTokenData.sourcePoolAddress, - sourcePoolData: abi.encode(LOCK_RELEASE_FLAG), - offchainTokenData: "" - }) - ); - - assertEq(poolReturnDataV1.destinationAmount, amount, "destinationAmount and actual amount transferred differ"); - assertEq(s_token.balanceOf(address(s_usdcTokenPool)), 0, "Tokens should be transferred out of the pool"); - assertEq(s_token.balanceOf(recipient), amount, "Tokens should be transferred to the recipient"); - assertEq( - s_usdcTokenPool.getExcludedTokensByChain(SOURCE_CHAIN_SELECTOR), - 0, - "All tokens should be released from the exclusion list" - ); - - // We also want to check that the system uses CCTP Burn/Mint for all other messages that don't have that flag - test_MintOrRelease_incomingMessageWithPrimaryMechanism(); - } - - function test_ProposeMigration_ChainNotUsingLockRelease_Revert() public { - vm.expectRevert(abi.encodeWithSelector(USDCBridgeMigrator.InvalidChainSelector.selector)); - - vm.startPrank(OWNER); - - s_usdcTokenPool.proposeCCTPMigration(0x98765); - } - - function test_excludeTokensWhenNoMigrationProposalPending_Revert() public { - vm.expectRevert(abi.encodeWithSelector(USDCBridgeMigrator.NoMigrationProposalPending.selector)); - - vm.startPrank(OWNER); - - s_usdcTokenPool.excludeTokensFromBurn(SOURCE_CHAIN_SELECTOR, 1e6); - } - - function test_cannotProvideLiquidityWhenMigrationProposalPending_Revert() public { - vm.startPrank(OWNER); - - // Mark the destination chain as supporting CCTP, so use L/R instead. - uint64[] memory destChainAdds = new uint64[](1); - destChainAdds[0] = DEST_CHAIN_SELECTOR; - - s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); - - s_usdcTokenPool.proposeCCTPMigration(DEST_CHAIN_SELECTOR); - - vm.expectRevert( - abi.encodeWithSelector(HybridLockReleaseUSDCTokenPool.LanePausedForCCTPMigration.selector, DEST_CHAIN_SELECTOR) - ); - s_usdcTokenPool.provideLiquidity(DEST_CHAIN_SELECTOR, 1e6); - } - - function test_cannotRevertChainMechanism_afterMigration_Revert() public { - test_lockOrBurn_then_BurnInCCTPMigration_Success(); - - vm.startPrank(OWNER); - - // Mark the destination chain as supporting CCTP, so use L/R instead. - uint64[] memory destChainAdds = new uint64[](1); - destChainAdds[0] = DEST_CHAIN_SELECTOR; - - vm.expectRevert( - abi.encodeWithSelector( - HybridLockReleaseUSDCTokenPool.TokenLockingNotAllowedAfterMigration.selector, DEST_CHAIN_SELECTOR - ) - ); - - s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); - } - - function test_cnanotProvideLiquidity_AfterMigration_Revert() public { - test_lockOrBurn_then_BurnInCCTPMigration_Success(); - - vm.startPrank(OWNER); - - vm.expectRevert( - abi.encodeWithSelector( - HybridLockReleaseUSDCTokenPool.TokenLockingNotAllowedAfterMigration.selector, DEST_CHAIN_SELECTOR - ) - ); - - s_usdcTokenPool.provideLiquidity(DEST_CHAIN_SELECTOR, 1e6); - } -} diff --git a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool.t.sol b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool.t.sol deleted file mode 100644 index f17ef80b4d5..00000000000 --- a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool.t.sol +++ /dev/null @@ -1,439 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {IPoolV1} from "../../interfaces/IPool.sol"; - -import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; -import {BurnMintERC677} from "../../../shared/token/ERC677/BurnMintERC677.sol"; -import {Router} from "../../Router.sol"; -import {Pool} from "../../libraries/Pool.sol"; -import {RateLimiter} from "../../libraries/RateLimiter.sol"; -import {LockReleaseTokenPool} from "../../pools/LockReleaseTokenPool.sol"; -import {TokenPool} from "../../pools/TokenPool.sol"; - -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; -import {IERC165} from "../../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/introspection/IERC165.sol"; -import {RouterSetup} from "../router/RouterSetup.t.sol"; - -contract LockReleaseTokenPoolSetup is RouterSetup { - IERC20 internal s_token; - LockReleaseTokenPool internal s_lockReleaseTokenPool; - LockReleaseTokenPool internal s_lockReleaseTokenPoolWithAllowList; - address[] internal s_allowedList; - - address internal s_allowedOnRamp = address(123); - address internal s_allowedOffRamp = address(234); - - address internal s_destPoolAddress = address(2736782345); - address internal s_sourcePoolAddress = address(53852352095); - - function setUp() public virtual override { - RouterSetup.setUp(); - s_token = new BurnMintERC677("LINK", "LNK", 18, 0); - deal(address(s_token), OWNER, type(uint256).max); - s_lockReleaseTokenPool = - new LockReleaseTokenPool(s_token, new address[](0), address(s_mockRMN), true, address(s_sourceRouter)); - - s_allowedList.push(USER_1); - s_allowedList.push(DUMMY_CONTRACT_ADDRESS); - s_lockReleaseTokenPoolWithAllowList = - new LockReleaseTokenPool(s_token, s_allowedList, address(s_mockRMN), true, address(s_sourceRouter)); - - TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); - chainUpdate[0] = TokenPool.ChainUpdate({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - remotePoolAddress: abi.encode(s_destPoolAddress), - remoteTokenAddress: abi.encode(address(2)), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - - s_lockReleaseTokenPool.applyChainUpdates(chainUpdate); - s_lockReleaseTokenPoolWithAllowList.applyChainUpdates(chainUpdate); - s_lockReleaseTokenPool.setRebalancer(OWNER); - - Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); - Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); - onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_allowedOnRamp}); - offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: s_allowedOffRamp}); - s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); - } -} - -contract LockReleaseTokenPool_setRebalancer is LockReleaseTokenPoolSetup { - function test_SetRebalancer_Success() public { - assertEq(address(s_lockReleaseTokenPool.getRebalancer()), OWNER); - s_lockReleaseTokenPool.setRebalancer(STRANGER); - assertEq(address(s_lockReleaseTokenPool.getRebalancer()), STRANGER); - } - - function test_SetRebalancer_Revert() public { - vm.startPrank(STRANGER); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_lockReleaseTokenPool.setRebalancer(STRANGER); - } -} - -contract LockReleaseTokenPool_lockOrBurn is LockReleaseTokenPoolSetup { - function test_Fuzz_LockOrBurnNoAllowList_Success( - uint256 amount - ) public { - amount = bound(amount, 1, _getOutboundRateLimiterConfig().capacity); - vm.startPrank(s_allowedOnRamp); - - vm.expectEmit(); - emit RateLimiter.TokensConsumed(amount); - vm.expectEmit(); - emit TokenPool.Locked(s_allowedOnRamp, amount); - - s_lockReleaseTokenPool.lockOrBurn( - Pool.LockOrBurnInV1({ - originalSender: STRANGER, - receiver: bytes(""), - amount: amount, - remoteChainSelector: DEST_CHAIN_SELECTOR, - localToken: address(s_token) - }) - ); - } - - function test_LockOrBurnWithAllowList_Success() public { - uint256 amount = 100; - vm.startPrank(s_allowedOnRamp); - - vm.expectEmit(); - emit RateLimiter.TokensConsumed(amount); - vm.expectEmit(); - emit TokenPool.Locked(s_allowedOnRamp, amount); - - s_lockReleaseTokenPoolWithAllowList.lockOrBurn( - Pool.LockOrBurnInV1({ - originalSender: s_allowedList[0], - receiver: bytes(""), - amount: amount, - remoteChainSelector: DEST_CHAIN_SELECTOR, - localToken: address(s_token) - }) - ); - - vm.expectEmit(); - emit TokenPool.Locked(s_allowedOnRamp, amount); - - s_lockReleaseTokenPoolWithAllowList.lockOrBurn( - Pool.LockOrBurnInV1({ - originalSender: s_allowedList[1], - receiver: bytes(""), - amount: amount, - remoteChainSelector: DEST_CHAIN_SELECTOR, - localToken: address(s_token) - }) - ); - } - - function test_LockOrBurnWithAllowList_Revert() public { - vm.startPrank(s_allowedOnRamp); - - vm.expectRevert(abi.encodeWithSelector(TokenPool.SenderNotAllowed.selector, STRANGER)); - - s_lockReleaseTokenPoolWithAllowList.lockOrBurn( - Pool.LockOrBurnInV1({ - originalSender: STRANGER, - receiver: bytes(""), - amount: 100, - remoteChainSelector: DEST_CHAIN_SELECTOR, - localToken: address(s_token) - }) - ); - } - - function test_PoolBurnRevertNotHealthy_Revert() public { - // Should not burn tokens if cursed. - s_mockRMN.setGlobalCursed(true); - uint256 before = s_token.balanceOf(address(s_lockReleaseTokenPoolWithAllowList)); - - vm.startPrank(s_allowedOnRamp); - vm.expectRevert(TokenPool.CursedByRMN.selector); - - s_lockReleaseTokenPoolWithAllowList.lockOrBurn( - Pool.LockOrBurnInV1({ - originalSender: s_allowedList[0], - receiver: bytes(""), - amount: 1e5, - remoteChainSelector: DEST_CHAIN_SELECTOR, - localToken: address(s_token) - }) - ); - - assertEq(s_token.balanceOf(address(s_lockReleaseTokenPoolWithAllowList)), before); - } -} - -contract LockReleaseTokenPool_releaseOrMint is LockReleaseTokenPoolSetup { - function setUp() public virtual override { - LockReleaseTokenPoolSetup.setUp(); - TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); - chainUpdate[0] = TokenPool.ChainUpdate({ - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - remotePoolAddress: abi.encode(s_sourcePoolAddress), - remoteTokenAddress: abi.encode(address(2)), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - - s_lockReleaseTokenPool.applyChainUpdates(chainUpdate); - s_lockReleaseTokenPoolWithAllowList.applyChainUpdates(chainUpdate); - } - - function test_ReleaseOrMint_Success() public { - vm.startPrank(s_allowedOffRamp); - - uint256 amount = 100; - deal(address(s_token), address(s_lockReleaseTokenPool), amount); - - vm.expectEmit(); - emit RateLimiter.TokensConsumed(amount); - vm.expectEmit(); - emit TokenPool.Released(s_allowedOffRamp, OWNER, amount); - - s_lockReleaseTokenPool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: bytes(""), - receiver: OWNER, - amount: amount, - localToken: address(s_token), - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - sourcePoolAddress: abi.encode(s_sourcePoolAddress), - sourcePoolData: "", - offchainTokenData: "" - }) - ); - } - - function test_Fuzz_ReleaseOrMint_Success(address recipient, uint256 amount) public { - // Since the owner already has tokens this would break the checks - vm.assume(recipient != OWNER); - vm.assume(recipient != address(0)); - vm.assume(recipient != address(s_token)); - - // Makes sure the pool always has enough funds - deal(address(s_token), address(s_lockReleaseTokenPool), amount); - vm.startPrank(s_allowedOffRamp); - - uint256 capacity = _getInboundRateLimiterConfig().capacity; - // Determine if we hit the rate limit or the txs should succeed. - if (amount > capacity) { - vm.expectRevert( - abi.encodeWithSelector(RateLimiter.TokenMaxCapacityExceeded.selector, capacity, amount, address(s_token)) - ); - } else { - // Only rate limit if the amount is >0 - if (amount > 0) { - vm.expectEmit(); - emit RateLimiter.TokensConsumed(amount); - } - - vm.expectEmit(); - emit TokenPool.Released(s_allowedOffRamp, recipient, amount); - } - - s_lockReleaseTokenPool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: bytes(""), - receiver: recipient, - amount: amount, - localToken: address(s_token), - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - sourcePoolAddress: abi.encode(s_sourcePoolAddress), - sourcePoolData: "", - offchainTokenData: "" - }) - ); - } - - function test_ChainNotAllowed_Revert() public { - address notAllowedRemotePoolAddress = address(1); - - TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); - chainUpdate[0] = TokenPool.ChainUpdate({ - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - remotePoolAddress: abi.encode(notAllowedRemotePoolAddress), - remoteTokenAddress: abi.encode(address(2)), - allowed: false, - outboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}), - inboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}) - }); - - s_lockReleaseTokenPool.applyChainUpdates(chainUpdate); - - vm.startPrank(s_allowedOffRamp); - - vm.expectRevert(abi.encodeWithSelector(TokenPool.ChainNotAllowed.selector, SOURCE_CHAIN_SELECTOR)); - s_lockReleaseTokenPool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: bytes(""), - receiver: OWNER, - amount: 1e5, - localToken: address(s_token), - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - sourcePoolAddress: abi.encode(s_sourcePoolAddress), - sourcePoolData: "", - offchainTokenData: "" - }) - ); - } - - function test_PoolMintNotHealthy_Revert() public { - // Should not mint tokens if cursed. - s_mockRMN.setGlobalCursed(true); - uint256 before = s_token.balanceOf(OWNER); - vm.startPrank(s_allowedOffRamp); - vm.expectRevert(TokenPool.CursedByRMN.selector); - s_lockReleaseTokenPool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: bytes(""), - receiver: OWNER, - amount: 1e5, - localToken: address(s_token), - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - sourcePoolAddress: _generateSourceTokenData().sourcePoolAddress, - sourcePoolData: _generateSourceTokenData().extraData, - offchainTokenData: "" - }) - ); - - assertEq(s_token.balanceOf(OWNER), before); - } -} - -contract LockReleaseTokenPool_canAcceptLiquidity is LockReleaseTokenPoolSetup { - function test_CanAcceptLiquidity_Success() public { - assertEq(true, s_lockReleaseTokenPool.canAcceptLiquidity()); - - s_lockReleaseTokenPool = - new LockReleaseTokenPool(s_token, new address[](0), address(s_mockRMN), false, address(s_sourceRouter)); - assertEq(false, s_lockReleaseTokenPool.canAcceptLiquidity()); - } -} - -contract LockReleaseTokenPool_provideLiquidity is LockReleaseTokenPoolSetup { - function test_Fuzz_ProvideLiquidity_Success( - uint256 amount - ) public { - uint256 balancePre = s_token.balanceOf(OWNER); - s_token.approve(address(s_lockReleaseTokenPool), amount); - - s_lockReleaseTokenPool.provideLiquidity(amount); - - assertEq(s_token.balanceOf(OWNER), balancePre - amount); - assertEq(s_token.balanceOf(address(s_lockReleaseTokenPool)), amount); - } - - // Reverts - - function test_Unauthorized_Revert() public { - vm.startPrank(STRANGER); - vm.expectRevert(abi.encodeWithSelector(TokenPool.Unauthorized.selector, STRANGER)); - - s_lockReleaseTokenPool.provideLiquidity(1); - } - - function test_Fuzz_ExceedsAllowance( - uint256 amount - ) public { - vm.assume(amount > 0); - vm.expectRevert("ERC20: insufficient allowance"); - s_lockReleaseTokenPool.provideLiquidity(amount); - } - - function test_LiquidityNotAccepted_Revert() public { - s_lockReleaseTokenPool = - new LockReleaseTokenPool(s_token, new address[](0), address(s_mockRMN), false, address(s_sourceRouter)); - - vm.expectRevert(LockReleaseTokenPool.LiquidityNotAccepted.selector); - s_lockReleaseTokenPool.provideLiquidity(1); - } -} - -contract LockReleaseTokenPool_withdrawalLiquidity is LockReleaseTokenPoolSetup { - function test_Fuzz_WithdrawalLiquidity_Success( - uint256 amount - ) public { - uint256 balancePre = s_token.balanceOf(OWNER); - s_token.approve(address(s_lockReleaseTokenPool), amount); - s_lockReleaseTokenPool.provideLiquidity(amount); - - s_lockReleaseTokenPool.withdrawLiquidity(amount); - - assertEq(s_token.balanceOf(OWNER), balancePre); - } - - // Reverts - - function test_Unauthorized_Revert() public { - vm.startPrank(STRANGER); - vm.expectRevert(abi.encodeWithSelector(TokenPool.Unauthorized.selector, STRANGER)); - - s_lockReleaseTokenPool.withdrawLiquidity(1); - } - - function test_InsufficientLiquidity_Revert() public { - uint256 maxUint256 = 2 ** 256 - 1; - s_token.approve(address(s_lockReleaseTokenPool), maxUint256); - s_lockReleaseTokenPool.provideLiquidity(maxUint256); - - vm.startPrank(address(s_lockReleaseTokenPool)); - s_token.transfer(OWNER, maxUint256); - vm.startPrank(OWNER); - - vm.expectRevert(LockReleaseTokenPool.InsufficientLiquidity.selector); - s_lockReleaseTokenPool.withdrawLiquidity(1); - } -} - -contract LockReleaseTokenPool_transferLiquidity is LockReleaseTokenPoolSetup { - LockReleaseTokenPool internal s_oldLockReleaseTokenPool; - uint256 internal s_amount = 100000; - - function setUp() public virtual override { - super.setUp(); - - s_oldLockReleaseTokenPool = - new LockReleaseTokenPool(s_token, new address[](0), address(s_mockRMN), true, address(s_sourceRouter)); - - deal(address(s_token), address(s_oldLockReleaseTokenPool), s_amount); - } - - function test_transferLiquidity_Success() public { - uint256 balancePre = s_token.balanceOf(address(s_lockReleaseTokenPool)); - - s_oldLockReleaseTokenPool.setRebalancer(address(s_lockReleaseTokenPool)); - - vm.expectEmit(); - emit LockReleaseTokenPool.LiquidityTransferred(address(s_oldLockReleaseTokenPool), s_amount); - - s_lockReleaseTokenPool.transferLiquidity(address(s_oldLockReleaseTokenPool), s_amount); - - assertEq(s_token.balanceOf(address(s_lockReleaseTokenPool)), balancePre + s_amount); - } - - function test_transferLiquidity_transferTooMuch_Revert() public { - uint256 balancePre = s_token.balanceOf(address(s_lockReleaseTokenPool)); - - s_oldLockReleaseTokenPool.setRebalancer(address(s_lockReleaseTokenPool)); - - vm.expectRevert(LockReleaseTokenPool.InsufficientLiquidity.selector); - s_lockReleaseTokenPool.transferLiquidity(address(s_oldLockReleaseTokenPool), s_amount + 1); - - assertEq(s_token.balanceOf(address(s_lockReleaseTokenPool)), balancePre); - } -} - -contract LockReleaseTokenPool_supportsInterface is LockReleaseTokenPoolSetup { - function test_SupportsInterface_Success() public view { - assertTrue(s_lockReleaseTokenPool.supportsInterface(type(IPoolV1).interfaceId)); - assertTrue(s_lockReleaseTokenPool.supportsInterface(type(IERC165).interfaceId)); - } -} diff --git a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.canAcceptLiquidity.t.sol b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.canAcceptLiquidity.t.sol new file mode 100644 index 00000000000..9a124572f96 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.canAcceptLiquidity.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {LockReleaseTokenPool} from "../../../pools/LockReleaseTokenPool.sol"; +import {LockReleaseTokenPoolSetup} from "./LockReleaseTokenPoolSetup.t.sol"; + +contract LockReleaseTokenPool_canAcceptLiquidity is LockReleaseTokenPoolSetup { + function test_CanAcceptLiquidity_Success() public { + assertEq(true, s_lockReleaseTokenPool.canAcceptLiquidity()); + + s_lockReleaseTokenPool = + new LockReleaseTokenPool(s_token, new address[](0), address(s_mockRMN), false, address(s_sourceRouter)); + assertEq(false, s_lockReleaseTokenPool.canAcceptLiquidity()); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.lockOrBurn.t.sol b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.lockOrBurn.t.sol new file mode 100644 index 00000000000..667386ae7e0 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.lockOrBurn.t.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Pool} from "../../../libraries/Pool.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; + +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {LockReleaseTokenPoolSetup} from "./LockReleaseTokenPoolSetup.t.sol"; + +contract LockReleaseTokenPool_lockOrBurn is LockReleaseTokenPoolSetup { + function test_Fuzz_LockOrBurnNoAllowList_Success( + uint256 amount + ) public { + amount = bound(amount, 1, _getOutboundRateLimiterConfig().capacity); + vm.startPrank(s_allowedOnRamp); + + vm.expectEmit(); + emit RateLimiter.TokensConsumed(amount); + vm.expectEmit(); + emit TokenPool.Locked(s_allowedOnRamp, amount); + + s_lockReleaseTokenPool.lockOrBurn( + Pool.LockOrBurnInV1({ + originalSender: STRANGER, + receiver: bytes(""), + amount: amount, + remoteChainSelector: DEST_CHAIN_SELECTOR, + localToken: address(s_token) + }) + ); + } + + function test_LockOrBurnWithAllowList_Success() public { + uint256 amount = 100; + vm.startPrank(s_allowedOnRamp); + + vm.expectEmit(); + emit RateLimiter.TokensConsumed(amount); + vm.expectEmit(); + emit TokenPool.Locked(s_allowedOnRamp, amount); + + s_lockReleaseTokenPoolWithAllowList.lockOrBurn( + Pool.LockOrBurnInV1({ + originalSender: s_allowedList[0], + receiver: bytes(""), + amount: amount, + remoteChainSelector: DEST_CHAIN_SELECTOR, + localToken: address(s_token) + }) + ); + + vm.expectEmit(); + emit TokenPool.Locked(s_allowedOnRamp, amount); + + s_lockReleaseTokenPoolWithAllowList.lockOrBurn( + Pool.LockOrBurnInV1({ + originalSender: s_allowedList[1], + receiver: bytes(""), + amount: amount, + remoteChainSelector: DEST_CHAIN_SELECTOR, + localToken: address(s_token) + }) + ); + } + + function test_LockOrBurnWithAllowList_Revert() public { + vm.startPrank(s_allowedOnRamp); + + vm.expectRevert(abi.encodeWithSelector(TokenPool.SenderNotAllowed.selector, STRANGER)); + + s_lockReleaseTokenPoolWithAllowList.lockOrBurn( + Pool.LockOrBurnInV1({ + originalSender: STRANGER, + receiver: bytes(""), + amount: 100, + remoteChainSelector: DEST_CHAIN_SELECTOR, + localToken: address(s_token) + }) + ); + } + + function test_PoolBurnRevertNotHealthy_Revert() public { + // Should not burn tokens if cursed. + s_mockRMN.setGlobalCursed(true); + uint256 before = s_token.balanceOf(address(s_lockReleaseTokenPoolWithAllowList)); + + vm.startPrank(s_allowedOnRamp); + vm.expectRevert(TokenPool.CursedByRMN.selector); + + s_lockReleaseTokenPoolWithAllowList.lockOrBurn( + Pool.LockOrBurnInV1({ + originalSender: s_allowedList[0], + receiver: bytes(""), + amount: 1e5, + remoteChainSelector: DEST_CHAIN_SELECTOR, + localToken: address(s_token) + }) + ); + + assertEq(s_token.balanceOf(address(s_lockReleaseTokenPoolWithAllowList)), before); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.provideLiquidity.t.sol b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.provideLiquidity.t.sol new file mode 100644 index 00000000000..664d9526063 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.provideLiquidity.t.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {LockReleaseTokenPool} from "../../../pools/LockReleaseTokenPool.sol"; + +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {LockReleaseTokenPoolSetup} from "./LockReleaseTokenPoolSetup.t.sol"; + +contract LockReleaseTokenPool_provideLiquidity is LockReleaseTokenPoolSetup { + function test_Fuzz_ProvideLiquidity_Success( + uint256 amount + ) public { + uint256 balancePre = s_token.balanceOf(OWNER); + s_token.approve(address(s_lockReleaseTokenPool), amount); + + s_lockReleaseTokenPool.provideLiquidity(amount); + + assertEq(s_token.balanceOf(OWNER), balancePre - amount); + assertEq(s_token.balanceOf(address(s_lockReleaseTokenPool)), amount); + } + + // Reverts + + function test_Unauthorized_Revert() public { + vm.startPrank(STRANGER); + vm.expectRevert(abi.encodeWithSelector(TokenPool.Unauthorized.selector, STRANGER)); + + s_lockReleaseTokenPool.provideLiquidity(1); + } + + function test_Fuzz_ExceedsAllowance( + uint256 amount + ) public { + vm.assume(amount > 0); + vm.expectRevert("ERC20: insufficient allowance"); + s_lockReleaseTokenPool.provideLiquidity(amount); + } + + function test_LiquidityNotAccepted_Revert() public { + s_lockReleaseTokenPool = + new LockReleaseTokenPool(s_token, new address[](0), address(s_mockRMN), false, address(s_sourceRouter)); + + vm.expectRevert(LockReleaseTokenPool.LiquidityNotAccepted.selector); + s_lockReleaseTokenPool.provideLiquidity(1); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.releaseOrMint.t.sol b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.releaseOrMint.t.sol new file mode 100644 index 00000000000..06ccfc38065 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.releaseOrMint.t.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Pool} from "../../../libraries/Pool.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; + +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {LockReleaseTokenPoolSetup} from "./LockReleaseTokenPoolSetup.t.sol"; + +contract LockReleaseTokenPool_releaseOrMint is LockReleaseTokenPoolSetup { + function setUp() public virtual override { + LockReleaseTokenPoolSetup.setUp(); + TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); + chainUpdate[0] = TokenPool.ChainUpdate({ + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(s_sourcePoolAddress), + remoteTokenAddress: abi.encode(address(2)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + + s_lockReleaseTokenPool.applyChainUpdates(chainUpdate); + s_lockReleaseTokenPoolWithAllowList.applyChainUpdates(chainUpdate); + } + + function test_ReleaseOrMint_Success() public { + vm.startPrank(s_allowedOffRamp); + + uint256 amount = 100; + deal(address(s_token), address(s_lockReleaseTokenPool), amount); + + vm.expectEmit(); + emit RateLimiter.TokensConsumed(amount); + vm.expectEmit(); + emit TokenPool.Released(s_allowedOffRamp, OWNER, amount); + + s_lockReleaseTokenPool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: bytes(""), + receiver: OWNER, + amount: amount, + localToken: address(s_token), + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + sourcePoolAddress: abi.encode(s_sourcePoolAddress), + sourcePoolData: "", + offchainTokenData: "" + }) + ); + } + + function test_Fuzz_ReleaseOrMint_Success(address recipient, uint256 amount) public { + // Since the owner already has tokens this would break the checks + vm.assume(recipient != OWNER); + vm.assume(recipient != address(0)); + vm.assume(recipient != address(s_token)); + + // Makes sure the pool always has enough funds + deal(address(s_token), address(s_lockReleaseTokenPool), amount); + vm.startPrank(s_allowedOffRamp); + + uint256 capacity = _getInboundRateLimiterConfig().capacity; + // Determine if we hit the rate limit or the txs should succeed. + if (amount > capacity) { + vm.expectRevert( + abi.encodeWithSelector(RateLimiter.TokenMaxCapacityExceeded.selector, capacity, amount, address(s_token)) + ); + } else { + // Only rate limit if the amount is >0 + if (amount > 0) { + vm.expectEmit(); + emit RateLimiter.TokensConsumed(amount); + } + + vm.expectEmit(); + emit TokenPool.Released(s_allowedOffRamp, recipient, amount); + } + + s_lockReleaseTokenPool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: bytes(""), + receiver: recipient, + amount: amount, + localToken: address(s_token), + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + sourcePoolAddress: abi.encode(s_sourcePoolAddress), + sourcePoolData: "", + offchainTokenData: "" + }) + ); + } + + function test_ChainNotAllowed_Revert() public { + address notAllowedRemotePoolAddress = address(1); + + TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); + chainUpdate[0] = TokenPool.ChainUpdate({ + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(notAllowedRemotePoolAddress), + remoteTokenAddress: abi.encode(address(2)), + allowed: false, + outboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}), + inboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}) + }); + + s_lockReleaseTokenPool.applyChainUpdates(chainUpdate); + + vm.startPrank(s_allowedOffRamp); + + vm.expectRevert(abi.encodeWithSelector(TokenPool.ChainNotAllowed.selector, SOURCE_CHAIN_SELECTOR)); + s_lockReleaseTokenPool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: bytes(""), + receiver: OWNER, + amount: 1e5, + localToken: address(s_token), + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + sourcePoolAddress: abi.encode(s_sourcePoolAddress), + sourcePoolData: "", + offchainTokenData: "" + }) + ); + } + + function test_PoolMintNotHealthy_Revert() public { + // Should not mint tokens if cursed. + s_mockRMN.setGlobalCursed(true); + uint256 before = s_token.balanceOf(OWNER); + vm.startPrank(s_allowedOffRamp); + vm.expectRevert(TokenPool.CursedByRMN.selector); + s_lockReleaseTokenPool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: bytes(""), + receiver: OWNER, + amount: 1e5, + localToken: address(s_token), + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + sourcePoolAddress: _generateSourceTokenData().sourcePoolAddress, + sourcePoolData: _generateSourceTokenData().extraData, + offchainTokenData: "" + }) + ); + + assertEq(s_token.balanceOf(OWNER), before); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.setRebalancer.t.sol b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.setRebalancer.t.sol new file mode 100644 index 00000000000..25286c1a376 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.setRebalancer.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {LockReleaseTokenPoolSetup} from "./LockReleaseTokenPoolSetup.t.sol"; + +contract LockReleaseTokenPool_setRebalancer is LockReleaseTokenPoolSetup { + function test_SetRebalancer_Success() public { + assertEq(address(s_lockReleaseTokenPool.getRebalancer()), OWNER); + s_lockReleaseTokenPool.setRebalancer(STRANGER); + assertEq(address(s_lockReleaseTokenPool.getRebalancer()), STRANGER); + } + + function test_SetRebalancer_Revert() public { + vm.startPrank(STRANGER); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + s_lockReleaseTokenPool.setRebalancer(STRANGER); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.supportsInterface.t.sol b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.supportsInterface.t.sol new file mode 100644 index 00000000000..9a08fc38c96 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.supportsInterface.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IPoolV1} from "../../../interfaces/IPool.sol"; + +import {LockReleaseTokenPoolSetup} from "./LockReleaseTokenPoolSetup.t.sol"; + +import {IERC165} from "../../../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/introspection/IERC165.sol"; + +contract LockReleaseTokenPool_supportsInterface is LockReleaseTokenPoolSetup { + function test_SupportsInterface_Success() public view { + assertTrue(s_lockReleaseTokenPool.supportsInterface(type(IPoolV1).interfaceId)); + assertTrue(s_lockReleaseTokenPool.supportsInterface(type(IERC165).interfaceId)); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.transferLiquidity.t.sol b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.transferLiquidity.t.sol new file mode 100644 index 00000000000..bd2636dbb98 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.transferLiquidity.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {LockReleaseTokenPool} from "../../../pools/LockReleaseTokenPool.sol"; +import {LockReleaseTokenPoolSetup} from "./LockReleaseTokenPoolSetup.t.sol"; + +contract LockReleaseTokenPool_transferLiquidity is LockReleaseTokenPoolSetup { + LockReleaseTokenPool internal s_oldLockReleaseTokenPool; + uint256 internal s_amount = 100000; + + function setUp() public virtual override { + super.setUp(); + + s_oldLockReleaseTokenPool = + new LockReleaseTokenPool(s_token, new address[](0), address(s_mockRMN), true, address(s_sourceRouter)); + + deal(address(s_token), address(s_oldLockReleaseTokenPool), s_amount); + } + + function test_transferLiquidity_Success() public { + uint256 balancePre = s_token.balanceOf(address(s_lockReleaseTokenPool)); + + s_oldLockReleaseTokenPool.setRebalancer(address(s_lockReleaseTokenPool)); + + vm.expectEmit(); + emit LockReleaseTokenPool.LiquidityTransferred(address(s_oldLockReleaseTokenPool), s_amount); + + s_lockReleaseTokenPool.transferLiquidity(address(s_oldLockReleaseTokenPool), s_amount); + + assertEq(s_token.balanceOf(address(s_lockReleaseTokenPool)), balancePre + s_amount); + } + + function test_transferLiquidity_transferTooMuch_Revert() public { + uint256 balancePre = s_token.balanceOf(address(s_lockReleaseTokenPool)); + + s_oldLockReleaseTokenPool.setRebalancer(address(s_lockReleaseTokenPool)); + + vm.expectRevert(LockReleaseTokenPool.InsufficientLiquidity.selector); + s_lockReleaseTokenPool.transferLiquidity(address(s_oldLockReleaseTokenPool), s_amount + 1); + + assertEq(s_token.balanceOf(address(s_lockReleaseTokenPool)), balancePre); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.withdrawalLiquidity.t.sol b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.withdrawalLiquidity.t.sol new file mode 100644 index 00000000000..0a2b7b28b0f --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPool.withdrawalLiquidity.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {LockReleaseTokenPool} from "../../../pools/LockReleaseTokenPool.sol"; + +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {LockReleaseTokenPoolSetup} from "./LockReleaseTokenPoolSetup.t.sol"; + +contract LockReleaseTokenPool_withdrawalLiquidity is LockReleaseTokenPoolSetup { + function test_Fuzz_WithdrawalLiquidity_Success( + uint256 amount + ) public { + uint256 balancePre = s_token.balanceOf(OWNER); + s_token.approve(address(s_lockReleaseTokenPool), amount); + s_lockReleaseTokenPool.provideLiquidity(amount); + + s_lockReleaseTokenPool.withdrawLiquidity(amount); + + assertEq(s_token.balanceOf(OWNER), balancePre); + } + + // Reverts + function test_Unauthorized_Revert() public { + vm.startPrank(STRANGER); + vm.expectRevert(abi.encodeWithSelector(TokenPool.Unauthorized.selector, STRANGER)); + + s_lockReleaseTokenPool.withdrawLiquidity(1); + } + + function test_InsufficientLiquidity_Revert() public { + uint256 maxUint256 = 2 ** 256 - 1; + s_token.approve(address(s_lockReleaseTokenPool), maxUint256); + s_lockReleaseTokenPool.provideLiquidity(maxUint256); + + vm.startPrank(address(s_lockReleaseTokenPool)); + s_token.transfer(OWNER, maxUint256); + vm.startPrank(OWNER); + + vm.expectRevert(LockReleaseTokenPool.InsufficientLiquidity.selector); + s_lockReleaseTokenPool.withdrawLiquidity(1); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol new file mode 100644 index 00000000000..98ef26aeb08 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {BurnMintERC677} from "../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {Router} from "../../../Router.sol"; +import {LockReleaseTokenPool} from "../../../pools/LockReleaseTokenPool.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {RouterSetup} from "../../router/RouterSetup.t.sol"; + +contract LockReleaseTokenPoolSetup is RouterSetup { + IERC20 internal s_token; + LockReleaseTokenPool internal s_lockReleaseTokenPool; + LockReleaseTokenPool internal s_lockReleaseTokenPoolWithAllowList; + address[] internal s_allowedList; + + address internal s_allowedOnRamp = address(123); + address internal s_allowedOffRamp = address(234); + + address internal s_destPoolAddress = address(2736782345); + address internal s_sourcePoolAddress = address(53852352095); + + function setUp() public virtual override { + RouterSetup.setUp(); + s_token = new BurnMintERC677("LINK", "LNK", 18, 0); + deal(address(s_token), OWNER, type(uint256).max); + s_lockReleaseTokenPool = + new LockReleaseTokenPool(s_token, new address[](0), address(s_mockRMN), true, address(s_sourceRouter)); + + s_allowedList.push(USER_1); + s_allowedList.push(DUMMY_CONTRACT_ADDRESS); + s_lockReleaseTokenPoolWithAllowList = + new LockReleaseTokenPool(s_token, s_allowedList, address(s_mockRMN), true, address(s_sourceRouter)); + + TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); + chainUpdate[0] = TokenPool.ChainUpdate({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(s_destPoolAddress), + remoteTokenAddress: abi.encode(address(2)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + + s_lockReleaseTokenPool.applyChainUpdates(chainUpdate); + s_lockReleaseTokenPoolWithAllowList.applyChainUpdates(chainUpdate); + s_lockReleaseTokenPool.setRebalancer(OWNER); + + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_allowedOnRamp}); + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: s_allowedOffRamp}); + s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool.t.sol deleted file mode 100644 index 9622f011d61..00000000000 --- a/contracts/src/v0.8/ccip/test/pools/TokenPool.t.sol +++ /dev/null @@ -1,786 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; -import {BurnMintERC677} from "../../../shared/token/ERC677/BurnMintERC677.sol"; -import {Router} from "../../Router.sol"; -import {RateLimiter} from "../../libraries/RateLimiter.sol"; -import {TokenPool} from "../../pools/TokenPool.sol"; -import {TokenPoolHelper} from "../helpers/TokenPoolHelper.sol"; -import {RouterSetup} from "../router/RouterSetup.t.sol"; - -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; - -contract TokenPoolSetup is RouterSetup { - IERC20 internal s_token; - TokenPoolHelper internal s_tokenPool; - - function setUp() public virtual override { - RouterSetup.setUp(); - s_token = new BurnMintERC677("LINK", "LNK", 18, 0); - deal(address(s_token), OWNER, type(uint256).max); - - s_tokenPool = new TokenPoolHelper(s_token, new address[](0), address(s_mockRMN), address(s_sourceRouter)); - } -} - -contract TokenPool_constructor is TokenPoolSetup { - function test_immutableFields_Success() public view { - assertEq(address(s_token), address(s_tokenPool.getToken())); - assertEq(address(s_mockRMN), s_tokenPool.getRmnProxy()); - assertEq(false, s_tokenPool.getAllowListEnabled()); - assertEq(address(s_sourceRouter), s_tokenPool.getRouter()); - } - - // Reverts - function test_ZeroAddressNotAllowed_Revert() public { - vm.expectRevert(TokenPool.ZeroAddressNotAllowed.selector); - - s_tokenPool = new TokenPoolHelper(IERC20(address(0)), new address[](0), address(s_mockRMN), address(s_sourceRouter)); - } -} - -contract TokenPool_getRemotePool is TokenPoolSetup { - function test_getRemotePool_Success() public { - uint64 chainSelector = 123124; - address remotePool = makeAddr("remotePool"); - address remoteToken = makeAddr("remoteToken"); - - // Zero indicates nothing is set - assertEq(0, s_tokenPool.getRemotePool(chainSelector).length); - - TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); - chainUpdates[0] = TokenPool.ChainUpdate({ - remoteChainSelector: chainSelector, - remotePoolAddress: abi.encode(remotePool), - remoteTokenAddress: abi.encode(remoteToken), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - s_tokenPool.applyChainUpdates(chainUpdates); - - assertEq(remotePool, abi.decode(s_tokenPool.getRemotePool(chainSelector), (address))); - } -} - -contract TokenPool_setRemotePool is TokenPoolSetup { - function test_setRemotePool_Success() public { - uint64 chainSelector = DEST_CHAIN_SELECTOR; - address initialPool = makeAddr("remotePool"); - address remoteToken = makeAddr("remoteToken"); - // The new pool is a non-evm pool, as it doesn't fit in the normal 160 bits - bytes memory newPool = abi.encode(type(uint256).max); - - TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); - chainUpdates[0] = TokenPool.ChainUpdate({ - remoteChainSelector: chainSelector, - remotePoolAddress: abi.encode(initialPool), - remoteTokenAddress: abi.encode(remoteToken), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - s_tokenPool.applyChainUpdates(chainUpdates); - - vm.expectEmit(); - emit TokenPool.RemotePoolSet(chainSelector, abi.encode(initialPool), newPool); - - s_tokenPool.setRemotePool(chainSelector, newPool); - - assertEq(keccak256(newPool), keccak256(s_tokenPool.getRemotePool(chainSelector))); - } - - // Reverts - - function test_setRemotePool_NonExistentChain_Reverts() public { - uint64 chainSelector = 123124; - bytes memory remotePool = abi.encode(makeAddr("remotePool")); - - vm.expectRevert(abi.encodeWithSelector(TokenPool.NonExistentChain.selector, chainSelector)); - s_tokenPool.setRemotePool(chainSelector, remotePool); - } - - function test_setRemotePool_OnlyOwner_Reverts() public { - vm.startPrank(STRANGER); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_tokenPool.setRemotePool(123124, abi.encode(makeAddr("remotePool"))); - } -} - -contract TokenPool_applyChainUpdates is TokenPoolSetup { - function assertState( - TokenPool.ChainUpdate[] memory chainUpdates - ) public view { - uint64[] memory chainSelectors = s_tokenPool.getSupportedChains(); - for (uint256 i = 0; i < chainUpdates.length; i++) { - assertEq(chainUpdates[i].remoteChainSelector, chainSelectors[i]); - } - - for (uint256 i = 0; i < chainUpdates.length; ++i) { - assertTrue(s_tokenPool.isSupportedChain(chainUpdates[i].remoteChainSelector)); - RateLimiter.TokenBucket memory bkt = - s_tokenPool.getCurrentOutboundRateLimiterState(chainUpdates[i].remoteChainSelector); - assertEq(bkt.capacity, chainUpdates[i].outboundRateLimiterConfig.capacity); - assertEq(bkt.rate, chainUpdates[i].outboundRateLimiterConfig.rate); - assertEq(bkt.isEnabled, chainUpdates[i].outboundRateLimiterConfig.isEnabled); - - bkt = s_tokenPool.getCurrentInboundRateLimiterState(chainUpdates[i].remoteChainSelector); - assertEq(bkt.capacity, chainUpdates[i].inboundRateLimiterConfig.capacity); - assertEq(bkt.rate, chainUpdates[i].inboundRateLimiterConfig.rate); - assertEq(bkt.isEnabled, chainUpdates[i].inboundRateLimiterConfig.isEnabled); - } - } - - function test_applyChainUpdates_Success() public { - RateLimiter.Config memory outboundRateLimit1 = RateLimiter.Config({isEnabled: true, capacity: 100e28, rate: 1e18}); - RateLimiter.Config memory inboundRateLimit1 = RateLimiter.Config({isEnabled: true, capacity: 100e29, rate: 1e19}); - RateLimiter.Config memory outboundRateLimit2 = RateLimiter.Config({isEnabled: true, capacity: 100e26, rate: 1e16}); - RateLimiter.Config memory inboundRateLimit2 = RateLimiter.Config({isEnabled: true, capacity: 100e27, rate: 1e17}); - - // EVM chain, which uses the 160 bit evm address space - uint64 evmChainSelector = 1; - bytes memory evmRemotePool = abi.encode(makeAddr("evm_remote_pool")); - bytes memory evmRemoteToken = abi.encode(makeAddr("evm_remote_token")); - - // Non EVM chain, which uses the full 256 bits - uint64 nonEvmChainSelector = type(uint64).max; - bytes memory nonEvmRemotePool = abi.encode(keccak256("non_evm_remote_pool")); - bytes memory nonEvmRemoteToken = abi.encode(keccak256("non_evm_remote_token")); - - TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](2); - chainUpdates[0] = TokenPool.ChainUpdate({ - remoteChainSelector: evmChainSelector, - remotePoolAddress: evmRemotePool, - remoteTokenAddress: evmRemoteToken, - allowed: true, - outboundRateLimiterConfig: outboundRateLimit1, - inboundRateLimiterConfig: inboundRateLimit1 - }); - chainUpdates[1] = TokenPool.ChainUpdate({ - remoteChainSelector: nonEvmChainSelector, - remotePoolAddress: nonEvmRemotePool, - remoteTokenAddress: nonEvmRemoteToken, - allowed: true, - outboundRateLimiterConfig: outboundRateLimit2, - inboundRateLimiterConfig: inboundRateLimit2 - }); - - // Assert configuration is applied - vm.expectEmit(); - emit TokenPool.ChainAdded( - chainUpdates[0].remoteChainSelector, - chainUpdates[0].remoteTokenAddress, - chainUpdates[0].outboundRateLimiterConfig, - chainUpdates[0].inboundRateLimiterConfig - ); - vm.expectEmit(); - emit TokenPool.ChainAdded( - chainUpdates[1].remoteChainSelector, - chainUpdates[1].remoteTokenAddress, - chainUpdates[1].outboundRateLimiterConfig, - chainUpdates[1].inboundRateLimiterConfig - ); - s_tokenPool.applyChainUpdates(chainUpdates); - // on1: rateLimit1, on2: rateLimit2, off1: rateLimit1, off2: rateLimit3 - assertState(chainUpdates); - - // Removing an non-existent chain should revert - TokenPool.ChainUpdate[] memory chainRemoves = new TokenPool.ChainUpdate[](1); - uint64 strangerChainSelector = 120938; - chainRemoves[0] = TokenPool.ChainUpdate({ - remoteChainSelector: strangerChainSelector, - remotePoolAddress: evmRemotePool, - remoteTokenAddress: evmRemoteToken, - allowed: false, - outboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}), - inboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}) - }); - vm.expectRevert(abi.encodeWithSelector(TokenPool.NonExistentChain.selector, strangerChainSelector)); - s_tokenPool.applyChainUpdates(chainRemoves); - // State remains - assertState(chainUpdates); - - // Can remove a chain - chainRemoves[0].remoteChainSelector = evmChainSelector; - - vm.expectEmit(); - emit TokenPool.ChainRemoved(chainRemoves[0].remoteChainSelector); - - s_tokenPool.applyChainUpdates(chainRemoves); - - // State updated, only chain 2 remains - TokenPool.ChainUpdate[] memory singleChainConfigured = new TokenPool.ChainUpdate[](1); - singleChainConfigured[0] = chainUpdates[1]; - assertState(singleChainConfigured); - - // Cannot reset already configured ramp - vm.expectRevert( - abi.encodeWithSelector(TokenPool.ChainAlreadyExists.selector, singleChainConfigured[0].remoteChainSelector) - ); - s_tokenPool.applyChainUpdates(singleChainConfigured); - } - - // Reverts - - function test_applyChainUpdates_OnlyCallableByOwner_Revert() public { - vm.startPrank(STRANGER); - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_tokenPool.applyChainUpdates(new TokenPool.ChainUpdate[](0)); - } - - function test_applyChainUpdates_ZeroAddressNotAllowed_Revert() public { - TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); - chainUpdates[0] = TokenPool.ChainUpdate({ - remoteChainSelector: 1, - remotePoolAddress: "", - remoteTokenAddress: abi.encode(address(2)), - allowed: true, - outboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100e28, rate: 1e18}), - inboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100e28, rate: 1e18}) - }); - - vm.expectRevert(TokenPool.ZeroAddressNotAllowed.selector); - s_tokenPool.applyChainUpdates(chainUpdates); - - chainUpdates = new TokenPool.ChainUpdate[](1); - chainUpdates[0] = TokenPool.ChainUpdate({ - remoteChainSelector: 1, - remotePoolAddress: abi.encode(address(2)), - remoteTokenAddress: "", - allowed: true, - outboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100e28, rate: 1e18}), - inboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100e28, rate: 1e18}) - }); - - vm.expectRevert(TokenPool.ZeroAddressNotAllowed.selector); - s_tokenPool.applyChainUpdates(chainUpdates); - } - - function test_applyChainUpdates_DisabledNonZeroRateLimit_Revert() public { - RateLimiter.Config memory outboundRateLimit = RateLimiter.Config({isEnabled: true, capacity: 100e28, rate: 1e18}); - RateLimiter.Config memory inboundRateLimit = RateLimiter.Config({isEnabled: true, capacity: 100e22, rate: 1e12}); - TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); - chainUpdates[0] = TokenPool.ChainUpdate({ - remoteChainSelector: 1, - remotePoolAddress: abi.encode(address(1)), - remoteTokenAddress: abi.encode(address(2)), - allowed: true, - outboundRateLimiterConfig: outboundRateLimit, - inboundRateLimiterConfig: inboundRateLimit - }); - - s_tokenPool.applyChainUpdates(chainUpdates); - - chainUpdates[0].allowed = false; - chainUpdates[0].outboundRateLimiterConfig = RateLimiter.Config({isEnabled: false, capacity: 10, rate: 1}); - chainUpdates[0].inboundRateLimiterConfig = RateLimiter.Config({isEnabled: false, capacity: 10, rate: 1}); - - vm.expectRevert( - abi.encodeWithSelector(RateLimiter.DisabledNonZeroRateLimit.selector, chainUpdates[0].outboundRateLimiterConfig) - ); - s_tokenPool.applyChainUpdates(chainUpdates); - } - - function test_applyChainUpdates_NonExistentChain_Revert() public { - RateLimiter.Config memory outboundRateLimit = RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}); - RateLimiter.Config memory inboundRateLimit = RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}); - TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); - chainUpdates[0] = TokenPool.ChainUpdate({ - remoteChainSelector: 1, - remotePoolAddress: abi.encode(address(1)), - remoteTokenAddress: abi.encode(address(2)), - allowed: false, - outboundRateLimiterConfig: outboundRateLimit, - inboundRateLimiterConfig: inboundRateLimit - }); - - vm.expectRevert(abi.encodeWithSelector(TokenPool.NonExistentChain.selector, chainUpdates[0].remoteChainSelector)); - s_tokenPool.applyChainUpdates(chainUpdates); - } - - function test_applyChainUpdates_InvalidRateLimitRate_Revert() public { - TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); - chainUpdates[0] = TokenPool.ChainUpdate({ - remoteChainSelector: 1, - remotePoolAddress: abi.encode(address(1)), - remoteTokenAddress: abi.encode(address(2)), - allowed: true, - outboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 0, rate: 0}), - inboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100e22, rate: 1e12}) - }); - - // Outbound - - vm.expectRevert( - abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, chainUpdates[0].outboundRateLimiterConfig) - ); - s_tokenPool.applyChainUpdates(chainUpdates); - - chainUpdates[0].outboundRateLimiterConfig.rate = 100; - - vm.expectRevert( - abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, chainUpdates[0].outboundRateLimiterConfig) - ); - s_tokenPool.applyChainUpdates(chainUpdates); - - chainUpdates[0].outboundRateLimiterConfig.capacity = 100; - - vm.expectRevert( - abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, chainUpdates[0].outboundRateLimiterConfig) - ); - s_tokenPool.applyChainUpdates(chainUpdates); - - chainUpdates[0].outboundRateLimiterConfig.capacity = 101; - - s_tokenPool.applyChainUpdates(chainUpdates); - - // Change the chain selector as adding the same one would revert - chainUpdates[0].remoteChainSelector = 2; - - // Inbound - - chainUpdates[0].inboundRateLimiterConfig.capacity = 0; - chainUpdates[0].inboundRateLimiterConfig.rate = 0; - - vm.expectRevert( - abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, chainUpdates[0].inboundRateLimiterConfig) - ); - s_tokenPool.applyChainUpdates(chainUpdates); - - chainUpdates[0].inboundRateLimiterConfig.rate = 100; - - vm.expectRevert( - abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, chainUpdates[0].inboundRateLimiterConfig) - ); - s_tokenPool.applyChainUpdates(chainUpdates); - - chainUpdates[0].inboundRateLimiterConfig.capacity = 100; - - vm.expectRevert( - abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, chainUpdates[0].inboundRateLimiterConfig) - ); - s_tokenPool.applyChainUpdates(chainUpdates); - - chainUpdates[0].inboundRateLimiterConfig.capacity = 101; - - s_tokenPool.applyChainUpdates(chainUpdates); - } -} - -contract TokenPool_setChainRateLimiterConfig is TokenPoolSetup { - uint64 internal s_remoteChainSelector; - - function setUp() public virtual override { - TokenPoolSetup.setUp(); - TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); - s_remoteChainSelector = 123124; - chainUpdates[0] = TokenPool.ChainUpdate({ - remoteChainSelector: s_remoteChainSelector, - remotePoolAddress: abi.encode(address(2)), - remoteTokenAddress: abi.encode(address(3)), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - s_tokenPool.applyChainUpdates(chainUpdates); - } - - function test_Fuzz_SetChainRateLimiterConfig_Success(uint128 capacity, uint128 rate, uint32 newTime) public { - // Cap the lower bound to 4 so 4/2 is still >= 2 - vm.assume(capacity >= 4); - // Cap the lower bound to 2 so 2/2 is still >= 1 - rate = uint128(bound(rate, 2, capacity - 2)); - // Bucket updates only work on increasing time - newTime = uint32(bound(newTime, block.timestamp + 1, type(uint32).max)); - vm.warp(newTime); - - uint256 oldOutboundTokens = s_tokenPool.getCurrentOutboundRateLimiterState(s_remoteChainSelector).tokens; - uint256 oldInboundTokens = s_tokenPool.getCurrentInboundRateLimiterState(s_remoteChainSelector).tokens; - - RateLimiter.Config memory newOutboundConfig = RateLimiter.Config({isEnabled: true, capacity: capacity, rate: rate}); - RateLimiter.Config memory newInboundConfig = - RateLimiter.Config({isEnabled: true, capacity: capacity / 2, rate: rate / 2}); - - vm.expectEmit(); - emit RateLimiter.ConfigChanged(newOutboundConfig); - vm.expectEmit(); - emit RateLimiter.ConfigChanged(newInboundConfig); - vm.expectEmit(); - emit TokenPool.ChainConfigured(s_remoteChainSelector, newOutboundConfig, newInboundConfig); - - s_tokenPool.setChainRateLimiterConfig(s_remoteChainSelector, newOutboundConfig, newInboundConfig); - - uint256 expectedTokens = RateLimiter._min(newOutboundConfig.capacity, oldOutboundTokens); - - RateLimiter.TokenBucket memory bucket = s_tokenPool.getCurrentOutboundRateLimiterState(s_remoteChainSelector); - assertEq(bucket.capacity, newOutboundConfig.capacity); - assertEq(bucket.rate, newOutboundConfig.rate); - assertEq(bucket.tokens, expectedTokens); - assertEq(bucket.lastUpdated, newTime); - - expectedTokens = RateLimiter._min(newInboundConfig.capacity, oldInboundTokens); - - bucket = s_tokenPool.getCurrentInboundRateLimiterState(s_remoteChainSelector); - assertEq(bucket.capacity, newInboundConfig.capacity); - assertEq(bucket.rate, newInboundConfig.rate); - assertEq(bucket.tokens, expectedTokens); - assertEq(bucket.lastUpdated, newTime); - } - - // Reverts - - function test_OnlyOwnerOrRateLimitAdmin_Revert() public { - vm.startPrank(STRANGER); - - vm.expectRevert(abi.encodeWithSelector(TokenPool.Unauthorized.selector, STRANGER)); - s_tokenPool.setChainRateLimiterConfig( - s_remoteChainSelector, _getOutboundRateLimiterConfig(), _getInboundRateLimiterConfig() - ); - } - - function test_NonExistentChain_Revert() public { - uint64 wrongChainSelector = 9084102894; - - vm.expectRevert(abi.encodeWithSelector(TokenPool.NonExistentChain.selector, wrongChainSelector)); - s_tokenPool.setChainRateLimiterConfig( - wrongChainSelector, _getOutboundRateLimiterConfig(), _getInboundRateLimiterConfig() - ); - } -} - -contract LockRelease_setRateLimitAdmin is TokenPoolSetup { - function test_SetRateLimitAdmin_Success() public { - assertEq(address(0), s_tokenPool.getRateLimitAdmin()); - s_tokenPool.setRateLimitAdmin(OWNER); - assertEq(OWNER, s_tokenPool.getRateLimitAdmin()); - } - - // Reverts - - function test_SetRateLimitAdmin_Revert() public { - vm.startPrank(STRANGER); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_tokenPool.setRateLimitAdmin(STRANGER); - } -} - -contract TokenPool_onlyOnRamp is TokenPoolSetup { - function test_onlyOnRamp_Success() public { - uint64 chainSelector = 13377; - address onRamp = makeAddr("onRamp"); - - TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); - chainUpdate[0] = TokenPool.ChainUpdate({ - remoteChainSelector: chainSelector, - remotePoolAddress: abi.encode(address(1)), - remoteTokenAddress: abi.encode(address(2)), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - s_tokenPool.applyChainUpdates(chainUpdate); - - Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); - onRampUpdates[0] = Router.OnRamp({destChainSelector: chainSelector, onRamp: onRamp}); - s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), new Router.OffRamp[](0)); - - vm.startPrank(onRamp); - - s_tokenPool.onlyOnRampModifier(chainSelector); - } - - function test_ChainNotAllowed_Revert() public { - uint64 chainSelector = 13377; - address onRamp = makeAddr("onRamp"); - - vm.startPrank(onRamp); - - vm.expectRevert(abi.encodeWithSelector(TokenPool.ChainNotAllowed.selector, chainSelector)); - s_tokenPool.onlyOnRampModifier(chainSelector); - - vm.startPrank(OWNER); - - TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); - chainUpdate[0] = TokenPool.ChainUpdate({ - remoteChainSelector: chainSelector, - remotePoolAddress: abi.encode(address(1)), - remoteTokenAddress: abi.encode(address(2)), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - s_tokenPool.applyChainUpdates(chainUpdate); - - Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); - onRampUpdates[0] = Router.OnRamp({destChainSelector: chainSelector, onRamp: onRamp}); - s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), new Router.OffRamp[](0)); - - vm.startPrank(onRamp); - // Should succeed now that we've added the chain - s_tokenPool.onlyOnRampModifier(chainSelector); - - chainUpdate[0] = TokenPool.ChainUpdate({ - remoteChainSelector: chainSelector, - remotePoolAddress: abi.encode(address(1)), - remoteTokenAddress: abi.encode(address(2)), - allowed: false, - outboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}), - inboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}) - }); - - vm.startPrank(OWNER); - s_tokenPool.applyChainUpdates(chainUpdate); - - vm.startPrank(onRamp); - - vm.expectRevert(abi.encodeWithSelector(TokenPool.ChainNotAllowed.selector, chainSelector)); - s_tokenPool.onlyOffRampModifier(chainSelector); - } - - function test_CallerIsNotARampOnRouter_Revert() public { - uint64 chainSelector = 13377; - address onRamp = makeAddr("onRamp"); - - TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); - chainUpdate[0] = TokenPool.ChainUpdate({ - remoteChainSelector: chainSelector, - remotePoolAddress: abi.encode(address(1)), - remoteTokenAddress: abi.encode(address(2)), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - s_tokenPool.applyChainUpdates(chainUpdate); - - vm.startPrank(onRamp); - - vm.expectRevert(abi.encodeWithSelector(TokenPool.CallerIsNotARampOnRouter.selector, onRamp)); - - s_tokenPool.onlyOnRampModifier(chainSelector); - } -} - -contract TokenPool_onlyOffRamp is TokenPoolSetup { - function test_onlyOffRamp_Success() public { - uint64 chainSelector = 13377; - address offRamp = makeAddr("onRamp"); - - TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); - chainUpdate[0] = TokenPool.ChainUpdate({ - remoteChainSelector: chainSelector, - remotePoolAddress: abi.encode(address(1)), - remoteTokenAddress: abi.encode(address(2)), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - s_tokenPool.applyChainUpdates(chainUpdate); - - Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); - offRampUpdates[0] = Router.OffRamp({sourceChainSelector: chainSelector, offRamp: offRamp}); - s_sourceRouter.applyRampUpdates(new Router.OnRamp[](0), new Router.OffRamp[](0), offRampUpdates); - - vm.startPrank(offRamp); - - s_tokenPool.onlyOffRampModifier(chainSelector); - } - - function test_ChainNotAllowed_Revert() public { - uint64 chainSelector = 13377; - address offRamp = makeAddr("onRamp"); - - vm.startPrank(offRamp); - - vm.expectRevert(abi.encodeWithSelector(TokenPool.ChainNotAllowed.selector, chainSelector)); - s_tokenPool.onlyOffRampModifier(chainSelector); - - vm.startPrank(OWNER); - - TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); - chainUpdate[0] = TokenPool.ChainUpdate({ - remoteChainSelector: chainSelector, - remotePoolAddress: abi.encode(address(1)), - remoteTokenAddress: abi.encode(address(2)), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - s_tokenPool.applyChainUpdates(chainUpdate); - - Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); - offRampUpdates[0] = Router.OffRamp({sourceChainSelector: chainSelector, offRamp: offRamp}); - s_sourceRouter.applyRampUpdates(new Router.OnRamp[](0), new Router.OffRamp[](0), offRampUpdates); - - vm.startPrank(offRamp); - // Should succeed now that we've added the chain - s_tokenPool.onlyOffRampModifier(chainSelector); - - chainUpdate[0] = TokenPool.ChainUpdate({ - remoteChainSelector: chainSelector, - remotePoolAddress: abi.encode(address(1)), - remoteTokenAddress: abi.encode(address(2)), - allowed: false, - outboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}), - inboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}) - }); - - vm.startPrank(OWNER); - s_tokenPool.applyChainUpdates(chainUpdate); - - vm.startPrank(offRamp); - - vm.expectRevert(abi.encodeWithSelector(TokenPool.ChainNotAllowed.selector, chainSelector)); - s_tokenPool.onlyOffRampModifier(chainSelector); - } - - function test_CallerIsNotARampOnRouter_Revert() public { - uint64 chainSelector = 13377; - address offRamp = makeAddr("offRamp"); - - TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); - chainUpdate[0] = TokenPool.ChainUpdate({ - remoteChainSelector: chainSelector, - remotePoolAddress: abi.encode(address(1)), - remoteTokenAddress: abi.encode(address(2)), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - s_tokenPool.applyChainUpdates(chainUpdate); - - vm.startPrank(offRamp); - - vm.expectRevert(abi.encodeWithSelector(TokenPool.CallerIsNotARampOnRouter.selector, offRamp)); - - s_tokenPool.onlyOffRampModifier(chainSelector); - } -} - -contract TokenPoolWithAllowListSetup is TokenPoolSetup { - address[] internal s_allowedSenders; - - function setUp() public virtual override { - TokenPoolSetup.setUp(); - - s_allowedSenders.push(STRANGER); - s_allowedSenders.push(DUMMY_CONTRACT_ADDRESS); - - s_tokenPool = new TokenPoolHelper(s_token, s_allowedSenders, address(s_mockRMN), address(s_sourceRouter)); - } -} - -contract TokenPoolWithAllowList_getAllowListEnabled is TokenPoolWithAllowListSetup { - function test_GetAllowListEnabled_Success() public view { - assertTrue(s_tokenPool.getAllowListEnabled()); - } -} - -contract TokenPoolWithAllowList_setRouter is TokenPoolWithAllowListSetup { - function test_SetRouter_Success() public { - assertEq(address(s_sourceRouter), s_tokenPool.getRouter()); - - address newRouter = makeAddr("newRouter"); - - vm.expectEmit(); - emit TokenPool.RouterUpdated(address(s_sourceRouter), newRouter); - - s_tokenPool.setRouter(newRouter); - - assertEq(newRouter, s_tokenPool.getRouter()); - } -} - -contract TokenPoolWithAllowList_getAllowList is TokenPoolWithAllowListSetup { - function test_GetAllowList_Success() public view { - address[] memory setAddresses = s_tokenPool.getAllowList(); - assertEq(2, setAddresses.length); - assertEq(s_allowedSenders[0], setAddresses[0]); - assertEq(s_allowedSenders[1], setAddresses[1]); - } -} - -contract TokenPoolWithAllowList_applyAllowListUpdates is TokenPoolWithAllowListSetup { - function test_SetAllowList_Success() public { - address[] memory newAddresses = new address[](2); - newAddresses[0] = address(1); - newAddresses[1] = address(2); - - for (uint256 i = 0; i < 2; ++i) { - vm.expectEmit(); - emit TokenPool.AllowListAdd(newAddresses[i]); - } - - s_tokenPool.applyAllowListUpdates(new address[](0), newAddresses); - address[] memory setAddresses = s_tokenPool.getAllowList(); - - assertEq(s_allowedSenders[0], setAddresses[0]); - assertEq(s_allowedSenders[1], setAddresses[1]); - assertEq(address(1), setAddresses[2]); - assertEq(address(2), setAddresses[3]); - - // address(2) exists noop, add address(3), remove address(1) - newAddresses = new address[](2); - newAddresses[0] = address(2); - newAddresses[1] = address(3); - - address[] memory removeAddresses = new address[](1); - removeAddresses[0] = address(1); - - vm.expectEmit(); - emit TokenPool.AllowListRemove(address(1)); - - vm.expectEmit(); - emit TokenPool.AllowListAdd(address(3)); - - s_tokenPool.applyAllowListUpdates(removeAddresses, newAddresses); - setAddresses = s_tokenPool.getAllowList(); - - assertEq(s_allowedSenders[0], setAddresses[0]); - assertEq(s_allowedSenders[1], setAddresses[1]); - assertEq(address(2), setAddresses[2]); - assertEq(address(3), setAddresses[3]); - - // remove all from allowlist - for (uint256 i = 0; i < setAddresses.length; ++i) { - vm.expectEmit(); - emit TokenPool.AllowListRemove(setAddresses[i]); - } - - s_tokenPool.applyAllowListUpdates(setAddresses, new address[](0)); - setAddresses = s_tokenPool.getAllowList(); - - assertEq(0, setAddresses.length); - } - - function test_SetAllowListSkipsZero_Success() public { - uint256 setAddressesLength = s_tokenPool.getAllowList().length; - - address[] memory newAddresses = new address[](1); - newAddresses[0] = address(0); - - s_tokenPool.applyAllowListUpdates(new address[](0), newAddresses); - address[] memory setAddresses = s_tokenPool.getAllowList(); - - assertEq(setAddresses.length, setAddressesLength); - } - - // Reverts - - function test_OnlyOwner_Revert() public { - vm.stopPrank(); - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - address[] memory newAddresses = new address[](2); - s_tokenPool.applyAllowListUpdates(new address[](0), newAddresses); - } - - function test_AllowListNotEnabled_Revert() public { - s_tokenPool = new TokenPoolHelper(s_token, new address[](0), address(s_mockRMN), address(s_sourceRouter)); - - vm.expectRevert(TokenPool.AllowListNotEnabled.selector); - - s_tokenPool.applyAllowListUpdates(new address[](0), new address[](2)); - } -} diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.applyAllowListUpdates.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.applyAllowListUpdates.t.sol new file mode 100644 index 00000000000..2862b8c71ae --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.applyAllowListUpdates.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {TokenPoolHelper} from "../../helpers/TokenPoolHelper.sol"; + +import {TokenPoolWithAllowListSetup} from "./TokenPoolWithAllowListSetup.t.sol"; + +contract TokenPoolWithAllowList_applyAllowListUpdates is TokenPoolWithAllowListSetup { + function test_SetAllowList_Success() public { + address[] memory newAddresses = new address[](2); + newAddresses[0] = address(1); + newAddresses[1] = address(2); + + for (uint256 i = 0; i < 2; ++i) { + vm.expectEmit(); + emit TokenPool.AllowListAdd(newAddresses[i]); + } + + s_tokenPool.applyAllowListUpdates(new address[](0), newAddresses); + address[] memory setAddresses = s_tokenPool.getAllowList(); + + assertEq(s_allowedSenders[0], setAddresses[0]); + assertEq(s_allowedSenders[1], setAddresses[1]); + assertEq(address(1), setAddresses[2]); + assertEq(address(2), setAddresses[3]); + + // address(2) exists noop, add address(3), remove address(1) + newAddresses = new address[](2); + newAddresses[0] = address(2); + newAddresses[1] = address(3); + + address[] memory removeAddresses = new address[](1); + removeAddresses[0] = address(1); + + vm.expectEmit(); + emit TokenPool.AllowListRemove(address(1)); + + vm.expectEmit(); + emit TokenPool.AllowListAdd(address(3)); + + s_tokenPool.applyAllowListUpdates(removeAddresses, newAddresses); + setAddresses = s_tokenPool.getAllowList(); + + assertEq(s_allowedSenders[0], setAddresses[0]); + assertEq(s_allowedSenders[1], setAddresses[1]); + assertEq(address(2), setAddresses[2]); + assertEq(address(3), setAddresses[3]); + + // remove all from allowlist + for (uint256 i = 0; i < setAddresses.length; ++i) { + vm.expectEmit(); + emit TokenPool.AllowListRemove(setAddresses[i]); + } + + s_tokenPool.applyAllowListUpdates(setAddresses, new address[](0)); + setAddresses = s_tokenPool.getAllowList(); + + assertEq(0, setAddresses.length); + } + + function test_SetAllowListSkipsZero_Success() public { + uint256 setAddressesLength = s_tokenPool.getAllowList().length; + + address[] memory newAddresses = new address[](1); + newAddresses[0] = address(0); + + s_tokenPool.applyAllowListUpdates(new address[](0), newAddresses); + address[] memory setAddresses = s_tokenPool.getAllowList(); + + assertEq(setAddresses.length, setAddressesLength); + } + + // Reverts + + function test_OnlyOwner_Revert() public { + vm.stopPrank(); + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + address[] memory newAddresses = new address[](2); + s_tokenPool.applyAllowListUpdates(new address[](0), newAddresses); + } + + function test_AllowListNotEnabled_Revert() public { + s_tokenPool = new TokenPoolHelper(s_token, new address[](0), address(s_mockRMN), address(s_sourceRouter)); + + vm.expectRevert(TokenPool.AllowListNotEnabled.selector); + + s_tokenPool.applyAllowListUpdates(new address[](0), new address[](2)); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.applyChainUpdates.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.applyChainUpdates.t.sol new file mode 100644 index 00000000000..a24fa3d0e8a --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.applyChainUpdates.t.sol @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {TokenPoolSetup} from "./TokenPoolSetup.t.sol"; + +contract TokenPool_applyChainUpdates is TokenPoolSetup { + function assertState( + TokenPool.ChainUpdate[] memory chainUpdates + ) public view { + uint64[] memory chainSelectors = s_tokenPool.getSupportedChains(); + for (uint256 i = 0; i < chainUpdates.length; i++) { + assertEq(chainUpdates[i].remoteChainSelector, chainSelectors[i]); + } + + for (uint256 i = 0; i < chainUpdates.length; ++i) { + assertTrue(s_tokenPool.isSupportedChain(chainUpdates[i].remoteChainSelector)); + RateLimiter.TokenBucket memory bkt = + s_tokenPool.getCurrentOutboundRateLimiterState(chainUpdates[i].remoteChainSelector); + assertEq(bkt.capacity, chainUpdates[i].outboundRateLimiterConfig.capacity); + assertEq(bkt.rate, chainUpdates[i].outboundRateLimiterConfig.rate); + assertEq(bkt.isEnabled, chainUpdates[i].outboundRateLimiterConfig.isEnabled); + + bkt = s_tokenPool.getCurrentInboundRateLimiterState(chainUpdates[i].remoteChainSelector); + assertEq(bkt.capacity, chainUpdates[i].inboundRateLimiterConfig.capacity); + assertEq(bkt.rate, chainUpdates[i].inboundRateLimiterConfig.rate); + assertEq(bkt.isEnabled, chainUpdates[i].inboundRateLimiterConfig.isEnabled); + } + } + + function test_applyChainUpdates_Success() public { + RateLimiter.Config memory outboundRateLimit1 = RateLimiter.Config({isEnabled: true, capacity: 100e28, rate: 1e18}); + RateLimiter.Config memory inboundRateLimit1 = RateLimiter.Config({isEnabled: true, capacity: 100e29, rate: 1e19}); + RateLimiter.Config memory outboundRateLimit2 = RateLimiter.Config({isEnabled: true, capacity: 100e26, rate: 1e16}); + RateLimiter.Config memory inboundRateLimit2 = RateLimiter.Config({isEnabled: true, capacity: 100e27, rate: 1e17}); + + // EVM chain, which uses the 160 bit evm address space + uint64 evmChainSelector = 1; + bytes memory evmRemotePool = abi.encode(makeAddr("evm_remote_pool")); + bytes memory evmRemoteToken = abi.encode(makeAddr("evm_remote_token")); + + // Non EVM chain, which uses the full 256 bits + uint64 nonEvmChainSelector = type(uint64).max; + bytes memory nonEvmRemotePool = abi.encode(keccak256("non_evm_remote_pool")); + bytes memory nonEvmRemoteToken = abi.encode(keccak256("non_evm_remote_token")); + + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](2); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: evmChainSelector, + remotePoolAddress: evmRemotePool, + remoteTokenAddress: evmRemoteToken, + allowed: true, + outboundRateLimiterConfig: outboundRateLimit1, + inboundRateLimiterConfig: inboundRateLimit1 + }); + chainUpdates[1] = TokenPool.ChainUpdate({ + remoteChainSelector: nonEvmChainSelector, + remotePoolAddress: nonEvmRemotePool, + remoteTokenAddress: nonEvmRemoteToken, + allowed: true, + outboundRateLimiterConfig: outboundRateLimit2, + inboundRateLimiterConfig: inboundRateLimit2 + }); + + // Assert configuration is applied + vm.expectEmit(); + emit TokenPool.ChainAdded( + chainUpdates[0].remoteChainSelector, + chainUpdates[0].remoteTokenAddress, + chainUpdates[0].outboundRateLimiterConfig, + chainUpdates[0].inboundRateLimiterConfig + ); + vm.expectEmit(); + emit TokenPool.ChainAdded( + chainUpdates[1].remoteChainSelector, + chainUpdates[1].remoteTokenAddress, + chainUpdates[1].outboundRateLimiterConfig, + chainUpdates[1].inboundRateLimiterConfig + ); + s_tokenPool.applyChainUpdates(chainUpdates); + // on1: rateLimit1, on2: rateLimit2, off1: rateLimit1, off2: rateLimit3 + assertState(chainUpdates); + + // Removing an non-existent chain should revert + TokenPool.ChainUpdate[] memory chainRemoves = new TokenPool.ChainUpdate[](1); + uint64 strangerChainSelector = 120938; + chainRemoves[0] = TokenPool.ChainUpdate({ + remoteChainSelector: strangerChainSelector, + remotePoolAddress: evmRemotePool, + remoteTokenAddress: evmRemoteToken, + allowed: false, + outboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}), + inboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}) + }); + vm.expectRevert(abi.encodeWithSelector(TokenPool.NonExistentChain.selector, strangerChainSelector)); + s_tokenPool.applyChainUpdates(chainRemoves); + // State remains + assertState(chainUpdates); + + // Can remove a chain + chainRemoves[0].remoteChainSelector = evmChainSelector; + + vm.expectEmit(); + emit TokenPool.ChainRemoved(chainRemoves[0].remoteChainSelector); + + s_tokenPool.applyChainUpdates(chainRemoves); + + // State updated, only chain 2 remains + TokenPool.ChainUpdate[] memory singleChainConfigured = new TokenPool.ChainUpdate[](1); + singleChainConfigured[0] = chainUpdates[1]; + assertState(singleChainConfigured); + + // Cannot reset already configured ramp + vm.expectRevert( + abi.encodeWithSelector(TokenPool.ChainAlreadyExists.selector, singleChainConfigured[0].remoteChainSelector) + ); + s_tokenPool.applyChainUpdates(singleChainConfigured); + } + + // Reverts + + function test_applyChainUpdates_OnlyCallableByOwner_Revert() public { + vm.startPrank(STRANGER); + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + s_tokenPool.applyChainUpdates(new TokenPool.ChainUpdate[](0)); + } + + function test_applyChainUpdates_ZeroAddressNotAllowed_Revert() public { + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: 1, + remotePoolAddress: "", + remoteTokenAddress: abi.encode(address(2)), + allowed: true, + outboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100e28, rate: 1e18}), + inboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100e28, rate: 1e18}) + }); + + vm.expectRevert(TokenPool.ZeroAddressNotAllowed.selector); + s_tokenPool.applyChainUpdates(chainUpdates); + + chainUpdates = new TokenPool.ChainUpdate[](1); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: 1, + remotePoolAddress: abi.encode(address(2)), + remoteTokenAddress: "", + allowed: true, + outboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100e28, rate: 1e18}), + inboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100e28, rate: 1e18}) + }); + + vm.expectRevert(TokenPool.ZeroAddressNotAllowed.selector); + s_tokenPool.applyChainUpdates(chainUpdates); + } + + function test_applyChainUpdates_DisabledNonZeroRateLimit_Revert() public { + RateLimiter.Config memory outboundRateLimit = RateLimiter.Config({isEnabled: true, capacity: 100e28, rate: 1e18}); + RateLimiter.Config memory inboundRateLimit = RateLimiter.Config({isEnabled: true, capacity: 100e22, rate: 1e12}); + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: 1, + remotePoolAddress: abi.encode(address(1)), + remoteTokenAddress: abi.encode(address(2)), + allowed: true, + outboundRateLimiterConfig: outboundRateLimit, + inboundRateLimiterConfig: inboundRateLimit + }); + + s_tokenPool.applyChainUpdates(chainUpdates); + + chainUpdates[0].allowed = false; + chainUpdates[0].outboundRateLimiterConfig = RateLimiter.Config({isEnabled: false, capacity: 10, rate: 1}); + chainUpdates[0].inboundRateLimiterConfig = RateLimiter.Config({isEnabled: false, capacity: 10, rate: 1}); + + vm.expectRevert( + abi.encodeWithSelector(RateLimiter.DisabledNonZeroRateLimit.selector, chainUpdates[0].outboundRateLimiterConfig) + ); + s_tokenPool.applyChainUpdates(chainUpdates); + } + + function test_applyChainUpdates_NonExistentChain_Revert() public { + RateLimiter.Config memory outboundRateLimit = RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}); + RateLimiter.Config memory inboundRateLimit = RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}); + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: 1, + remotePoolAddress: abi.encode(address(1)), + remoteTokenAddress: abi.encode(address(2)), + allowed: false, + outboundRateLimiterConfig: outboundRateLimit, + inboundRateLimiterConfig: inboundRateLimit + }); + + vm.expectRevert(abi.encodeWithSelector(TokenPool.NonExistentChain.selector, chainUpdates[0].remoteChainSelector)); + s_tokenPool.applyChainUpdates(chainUpdates); + } + + function test_applyChainUpdates_InvalidRateLimitRate_Revert() public { + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: 1, + remotePoolAddress: abi.encode(address(1)), + remoteTokenAddress: abi.encode(address(2)), + allowed: true, + outboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 0, rate: 0}), + inboundRateLimiterConfig: RateLimiter.Config({isEnabled: true, capacity: 100e22, rate: 1e12}) + }); + + // Outbound + + vm.expectRevert( + abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, chainUpdates[0].outboundRateLimiterConfig) + ); + s_tokenPool.applyChainUpdates(chainUpdates); + + chainUpdates[0].outboundRateLimiterConfig.rate = 100; + + vm.expectRevert( + abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, chainUpdates[0].outboundRateLimiterConfig) + ); + s_tokenPool.applyChainUpdates(chainUpdates); + + chainUpdates[0].outboundRateLimiterConfig.capacity = 100; + + vm.expectRevert( + abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, chainUpdates[0].outboundRateLimiterConfig) + ); + s_tokenPool.applyChainUpdates(chainUpdates); + + chainUpdates[0].outboundRateLimiterConfig.capacity = 101; + + s_tokenPool.applyChainUpdates(chainUpdates); + + // Change the chain selector as adding the same one would revert + chainUpdates[0].remoteChainSelector = 2; + + // Inbound + + chainUpdates[0].inboundRateLimiterConfig.capacity = 0; + chainUpdates[0].inboundRateLimiterConfig.rate = 0; + + vm.expectRevert( + abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, chainUpdates[0].inboundRateLimiterConfig) + ); + s_tokenPool.applyChainUpdates(chainUpdates); + + chainUpdates[0].inboundRateLimiterConfig.rate = 100; + + vm.expectRevert( + abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, chainUpdates[0].inboundRateLimiterConfig) + ); + s_tokenPool.applyChainUpdates(chainUpdates); + + chainUpdates[0].inboundRateLimiterConfig.capacity = 100; + + vm.expectRevert( + abi.encodeWithSelector(RateLimiter.InvalidRateLimitRate.selector, chainUpdates[0].inboundRateLimiterConfig) + ); + s_tokenPool.applyChainUpdates(chainUpdates); + + chainUpdates[0].inboundRateLimiterConfig.capacity = 101; + + s_tokenPool.applyChainUpdates(chainUpdates); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.constructor.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.constructor.t.sol new file mode 100644 index 00000000000..cfa0d5b9394 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.constructor.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {TokenPoolHelper} from "../../helpers/TokenPoolHelper.sol"; +import {TokenPoolSetup} from "./TokenPoolSetup.t.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +contract TokenPool_constructor is TokenPoolSetup { + function test_immutableFields_Success() public view { + assertEq(address(s_token), address(s_tokenPool.getToken())); + assertEq(address(s_mockRMN), s_tokenPool.getRmnProxy()); + assertEq(false, s_tokenPool.getAllowListEnabled()); + assertEq(address(s_sourceRouter), s_tokenPool.getRouter()); + } + + // Reverts + function test_ZeroAddressNotAllowed_Revert() public { + vm.expectRevert(TokenPool.ZeroAddressNotAllowed.selector); + + s_tokenPool = new TokenPoolHelper(IERC20(address(0)), new address[](0), address(s_mockRMN), address(s_sourceRouter)); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.getAllowList.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.getAllowList.t.sol new file mode 100644 index 00000000000..8d4256d3479 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.getAllowList.t.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {TokenPoolWithAllowListSetup} from "./TokenPoolWithAllowListSetup.t.sol"; + +contract TokenPoolWithAllowList_getAllowList is TokenPoolWithAllowListSetup { + function test_GetAllowList_Success() public view { + address[] memory setAddresses = s_tokenPool.getAllowList(); + assertEq(2, setAddresses.length); + assertEq(s_allowedSenders[0], setAddresses[0]); + assertEq(s_allowedSenders[1], setAddresses[1]); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.getAllowListEnabled.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.getAllowListEnabled.t.sol new file mode 100644 index 00000000000..2a36a846999 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.getAllowListEnabled.t.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {TokenPoolWithAllowListSetup} from "./TokenPoolWithAllowListSetup.t.sol"; + +contract TokenPoolWithAllowList_getAllowListEnabled is TokenPoolWithAllowListSetup { + function test_GetAllowListEnabled_Success() public view { + assertTrue(s_tokenPool.getAllowListEnabled()); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.getRemotePool.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.getRemotePool.t.sol new file mode 100644 index 00000000000..a3acd8f2690 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.getRemotePool.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {TokenPoolSetup} from "./TokenPoolSetup.t.sol"; + +contract TokenPool_getRemotePool is TokenPoolSetup { + function test_getRemotePool_Success() public { + uint64 chainSelector = 123124; + address remotePool = makeAddr("remotePool"); + address remoteToken = makeAddr("remoteToken"); + + // Zero indicates nothing is set + assertEq(0, s_tokenPool.getRemotePool(chainSelector).length); + + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: chainSelector, + remotePoolAddress: abi.encode(remotePool), + remoteTokenAddress: abi.encode(remoteToken), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + s_tokenPool.applyChainUpdates(chainUpdates); + + assertEq(remotePool, abi.decode(s_tokenPool.getRemotePool(chainSelector), (address))); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.onlyOffRamp.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.onlyOffRamp.t.sol new file mode 100644 index 00000000000..c514b343d62 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.onlyOffRamp.t.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Router} from "../../../Router.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {TokenPoolSetup} from "./TokenPoolSetup.t.sol"; + +contract TokenPool_onlyOffRamp is TokenPoolSetup { + function test_onlyOffRamp_Success() public { + uint64 chainSelector = 13377; + address offRamp = makeAddr("onRamp"); + + TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); + chainUpdate[0] = TokenPool.ChainUpdate({ + remoteChainSelector: chainSelector, + remotePoolAddress: abi.encode(address(1)), + remoteTokenAddress: abi.encode(address(2)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + s_tokenPool.applyChainUpdates(chainUpdate); + + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: chainSelector, offRamp: offRamp}); + s_sourceRouter.applyRampUpdates(new Router.OnRamp[](0), new Router.OffRamp[](0), offRampUpdates); + + vm.startPrank(offRamp); + + s_tokenPool.onlyOffRampModifier(chainSelector); + } + + function test_ChainNotAllowed_Revert() public { + uint64 chainSelector = 13377; + address offRamp = makeAddr("onRamp"); + + vm.startPrank(offRamp); + + vm.expectRevert(abi.encodeWithSelector(TokenPool.ChainNotAllowed.selector, chainSelector)); + s_tokenPool.onlyOffRampModifier(chainSelector); + + vm.startPrank(OWNER); + + TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); + chainUpdate[0] = TokenPool.ChainUpdate({ + remoteChainSelector: chainSelector, + remotePoolAddress: abi.encode(address(1)), + remoteTokenAddress: abi.encode(address(2)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + s_tokenPool.applyChainUpdates(chainUpdate); + + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: chainSelector, offRamp: offRamp}); + s_sourceRouter.applyRampUpdates(new Router.OnRamp[](0), new Router.OffRamp[](0), offRampUpdates); + + vm.startPrank(offRamp); + // Should succeed now that we've added the chain + s_tokenPool.onlyOffRampModifier(chainSelector); + + chainUpdate[0] = TokenPool.ChainUpdate({ + remoteChainSelector: chainSelector, + remotePoolAddress: abi.encode(address(1)), + remoteTokenAddress: abi.encode(address(2)), + allowed: false, + outboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}), + inboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}) + }); + + vm.startPrank(OWNER); + s_tokenPool.applyChainUpdates(chainUpdate); + + vm.startPrank(offRamp); + + vm.expectRevert(abi.encodeWithSelector(TokenPool.ChainNotAllowed.selector, chainSelector)); + s_tokenPool.onlyOffRampModifier(chainSelector); + } + + function test_CallerIsNotARampOnRouter_Revert() public { + uint64 chainSelector = 13377; + address offRamp = makeAddr("offRamp"); + + TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); + chainUpdate[0] = TokenPool.ChainUpdate({ + remoteChainSelector: chainSelector, + remotePoolAddress: abi.encode(address(1)), + remoteTokenAddress: abi.encode(address(2)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + s_tokenPool.applyChainUpdates(chainUpdate); + + vm.startPrank(offRamp); + + vm.expectRevert(abi.encodeWithSelector(TokenPool.CallerIsNotARampOnRouter.selector, offRamp)); + + s_tokenPool.onlyOffRampModifier(chainSelector); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.onlyOnRamp.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.onlyOnRamp.t.sol new file mode 100644 index 00000000000..5e3e6e31c12 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.onlyOnRamp.t.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Router} from "../../../Router.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {TokenPoolSetup} from "./TokenPoolSetup.t.sol"; + +contract TokenPool_onlyOnRamp is TokenPoolSetup { + function test_onlyOnRamp_Success() public { + uint64 chainSelector = 13377; + address onRamp = makeAddr("onRamp"); + + TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); + chainUpdate[0] = TokenPool.ChainUpdate({ + remoteChainSelector: chainSelector, + remotePoolAddress: abi.encode(address(1)), + remoteTokenAddress: abi.encode(address(2)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + s_tokenPool.applyChainUpdates(chainUpdate); + + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + onRampUpdates[0] = Router.OnRamp({destChainSelector: chainSelector, onRamp: onRamp}); + s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), new Router.OffRamp[](0)); + + vm.startPrank(onRamp); + + s_tokenPool.onlyOnRampModifier(chainSelector); + } + + function test_ChainNotAllowed_Revert() public { + uint64 chainSelector = 13377; + address onRamp = makeAddr("onRamp"); + + vm.startPrank(onRamp); + + vm.expectRevert(abi.encodeWithSelector(TokenPool.ChainNotAllowed.selector, chainSelector)); + s_tokenPool.onlyOnRampModifier(chainSelector); + + vm.startPrank(OWNER); + + TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); + chainUpdate[0] = TokenPool.ChainUpdate({ + remoteChainSelector: chainSelector, + remotePoolAddress: abi.encode(address(1)), + remoteTokenAddress: abi.encode(address(2)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + s_tokenPool.applyChainUpdates(chainUpdate); + + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + onRampUpdates[0] = Router.OnRamp({destChainSelector: chainSelector, onRamp: onRamp}); + s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), new Router.OffRamp[](0)); + + vm.startPrank(onRamp); + // Should succeed now that we've added the chain + s_tokenPool.onlyOnRampModifier(chainSelector); + + chainUpdate[0] = TokenPool.ChainUpdate({ + remoteChainSelector: chainSelector, + remotePoolAddress: abi.encode(address(1)), + remoteTokenAddress: abi.encode(address(2)), + allowed: false, + outboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}), + inboundRateLimiterConfig: RateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}) + }); + + vm.startPrank(OWNER); + s_tokenPool.applyChainUpdates(chainUpdate); + + vm.startPrank(onRamp); + + vm.expectRevert(abi.encodeWithSelector(TokenPool.ChainNotAllowed.selector, chainSelector)); + s_tokenPool.onlyOffRampModifier(chainSelector); + } + + function test_CallerIsNotARampOnRouter_Revert() public { + uint64 chainSelector = 13377; + address onRamp = makeAddr("onRamp"); + + TokenPool.ChainUpdate[] memory chainUpdate = new TokenPool.ChainUpdate[](1); + chainUpdate[0] = TokenPool.ChainUpdate({ + remoteChainSelector: chainSelector, + remotePoolAddress: abi.encode(address(1)), + remoteTokenAddress: abi.encode(address(2)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + s_tokenPool.applyChainUpdates(chainUpdate); + + vm.startPrank(onRamp); + + vm.expectRevert(abi.encodeWithSelector(TokenPool.CallerIsNotARampOnRouter.selector, onRamp)); + + s_tokenPool.onlyOnRampModifier(chainSelector); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setChainRateLimiterConfig.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setChainRateLimiterConfig.t.sol new file mode 100644 index 00000000000..bee2218a7ff --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setChainRateLimiterConfig.t.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {TokenPoolSetup} from "./TokenPoolSetup.t.sol"; + +contract TokenPool_setChainRateLimiterConfig is TokenPoolSetup { + uint64 internal s_remoteChainSelector; + + function setUp() public virtual override { + TokenPoolSetup.setUp(); + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); + s_remoteChainSelector = 123124; + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: s_remoteChainSelector, + remotePoolAddress: abi.encode(address(2)), + remoteTokenAddress: abi.encode(address(3)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + s_tokenPool.applyChainUpdates(chainUpdates); + } + + function test_Fuzz_SetChainRateLimiterConfig_Success(uint128 capacity, uint128 rate, uint32 newTime) public { + // Cap the lower bound to 4 so 4/2 is still >= 2 + vm.assume(capacity >= 4); + // Cap the lower bound to 2 so 2/2 is still >= 1 + rate = uint128(bound(rate, 2, capacity - 2)); + // Bucket updates only work on increasing time + newTime = uint32(bound(newTime, block.timestamp + 1, type(uint32).max)); + vm.warp(newTime); + + uint256 oldOutboundTokens = s_tokenPool.getCurrentOutboundRateLimiterState(s_remoteChainSelector).tokens; + uint256 oldInboundTokens = s_tokenPool.getCurrentInboundRateLimiterState(s_remoteChainSelector).tokens; + + RateLimiter.Config memory newOutboundConfig = RateLimiter.Config({isEnabled: true, capacity: capacity, rate: rate}); + RateLimiter.Config memory newInboundConfig = + RateLimiter.Config({isEnabled: true, capacity: capacity / 2, rate: rate / 2}); + + vm.expectEmit(); + emit RateLimiter.ConfigChanged(newOutboundConfig); + vm.expectEmit(); + emit RateLimiter.ConfigChanged(newInboundConfig); + vm.expectEmit(); + emit TokenPool.ChainConfigured(s_remoteChainSelector, newOutboundConfig, newInboundConfig); + + s_tokenPool.setChainRateLimiterConfig(s_remoteChainSelector, newOutboundConfig, newInboundConfig); + + uint256 expectedTokens = RateLimiter._min(newOutboundConfig.capacity, oldOutboundTokens); + + RateLimiter.TokenBucket memory bucket = s_tokenPool.getCurrentOutboundRateLimiterState(s_remoteChainSelector); + assertEq(bucket.capacity, newOutboundConfig.capacity); + assertEq(bucket.rate, newOutboundConfig.rate); + assertEq(bucket.tokens, expectedTokens); + assertEq(bucket.lastUpdated, newTime); + + expectedTokens = RateLimiter._min(newInboundConfig.capacity, oldInboundTokens); + + bucket = s_tokenPool.getCurrentInboundRateLimiterState(s_remoteChainSelector); + assertEq(bucket.capacity, newInboundConfig.capacity); + assertEq(bucket.rate, newInboundConfig.rate); + assertEq(bucket.tokens, expectedTokens); + assertEq(bucket.lastUpdated, newTime); + } + + // Reverts + + function test_OnlyOwnerOrRateLimitAdmin_Revert() public { + vm.startPrank(STRANGER); + + vm.expectRevert(abi.encodeWithSelector(TokenPool.Unauthorized.selector, STRANGER)); + s_tokenPool.setChainRateLimiterConfig( + s_remoteChainSelector, _getOutboundRateLimiterConfig(), _getInboundRateLimiterConfig() + ); + } + + function test_NonExistentChain_Revert() public { + uint64 wrongChainSelector = 9084102894; + + vm.expectRevert(abi.encodeWithSelector(TokenPool.NonExistentChain.selector, wrongChainSelector)); + s_tokenPool.setChainRateLimiterConfig( + wrongChainSelector, _getOutboundRateLimiterConfig(), _getInboundRateLimiterConfig() + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setRateLimitAdmin.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setRateLimitAdmin.t.sol new file mode 100644 index 00000000000..e654dc15e77 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setRateLimitAdmin.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {TokenPoolSetup} from "./TokenPoolSetup.t.sol"; + +contract TokenPool_setRateLimitAdmin is TokenPoolSetup { + function test_SetRateLimitAdmin_Success() public { + assertEq(address(0), s_tokenPool.getRateLimitAdmin()); + s_tokenPool.setRateLimitAdmin(OWNER); + assertEq(OWNER, s_tokenPool.getRateLimitAdmin()); + } + + // Reverts + + function test_SetRateLimitAdmin_Revert() public { + vm.startPrank(STRANGER); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + s_tokenPool.setRateLimitAdmin(STRANGER); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setRemotePool.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setRemotePool.t.sol new file mode 100644 index 00000000000..d305e131793 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setRemotePool.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {TokenPoolSetup} from "./TokenPoolSetup.t.sol"; + +contract TokenPool_setRemotePool is TokenPoolSetup { + function test_setRemotePool_Success() public { + uint64 chainSelector = DEST_CHAIN_SELECTOR; + address initialPool = makeAddr("remotePool"); + address remoteToken = makeAddr("remoteToken"); + // The new pool is a non-evm pool, as it doesn't fit in the normal 160 bits + bytes memory newPool = abi.encode(type(uint256).max); + + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: chainSelector, + remotePoolAddress: abi.encode(initialPool), + remoteTokenAddress: abi.encode(remoteToken), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + s_tokenPool.applyChainUpdates(chainUpdates); + + vm.expectEmit(); + emit TokenPool.RemotePoolSet(chainSelector, abi.encode(initialPool), newPool); + + s_tokenPool.setRemotePool(chainSelector, newPool); + + assertEq(keccak256(newPool), keccak256(s_tokenPool.getRemotePool(chainSelector))); + } + + // Reverts + + function test_setRemotePool_NonExistentChain_Reverts() public { + uint64 chainSelector = 123124; + bytes memory remotePool = abi.encode(makeAddr("remotePool")); + + vm.expectRevert(abi.encodeWithSelector(TokenPool.NonExistentChain.selector, chainSelector)); + s_tokenPool.setRemotePool(chainSelector, remotePool); + } + + function test_setRemotePool_OnlyOwner_Reverts() public { + vm.startPrank(STRANGER); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + s_tokenPool.setRemotePool(123124, abi.encode(makeAddr("remotePool"))); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setRouter.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setRouter.t.sol new file mode 100644 index 00000000000..288b7f7081d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPool.setRouter.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {TokenPoolWithAllowListSetup} from "./TokenPoolWithAllowListSetup.t.sol"; + +contract TokenPoolWithAllowList_setRouter is TokenPoolWithAllowListSetup { + function test_SetRouter_Success() public { + assertEq(address(s_sourceRouter), s_tokenPool.getRouter()); + + address newRouter = makeAddr("newRouter"); + + vm.expectEmit(); + emit TokenPool.RouterUpdated(address(s_sourceRouter), newRouter); + + s_tokenPool.setRouter(newRouter); + + assertEq(newRouter, s_tokenPool.getRouter()); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolSetup.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolSetup.t.sol new file mode 100644 index 00000000000..e2285c67094 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolSetup.t.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {BurnMintERC677} from "../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {TokenPoolHelper} from "../../helpers/TokenPoolHelper.sol"; +import {RouterSetup} from "../../router/RouterSetup.t.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +contract TokenPoolSetup is RouterSetup { + IERC20 internal s_token; + TokenPoolHelper internal s_tokenPool; + + function setUp() public virtual override { + RouterSetup.setUp(); + s_token = new BurnMintERC677("LINK", "LNK", 18, 0); + deal(address(s_token), OWNER, type(uint256).max); + + s_tokenPool = new TokenPoolHelper(s_token, new address[](0), address(s_mockRMN), address(s_sourceRouter)); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolWithAllowListSetup.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolWithAllowListSetup.t.sol new file mode 100644 index 00000000000..f2408af0fca --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolWithAllowListSetup.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {TokenPoolHelper} from "../../helpers/TokenPoolHelper.sol"; +import {TokenPoolSetup} from "./TokenPoolSetup.t.sol"; + +contract TokenPoolWithAllowListSetup is TokenPoolSetup { + address[] internal s_allowedSenders; + + function setUp() public virtual override { + TokenPoolSetup.setUp(); + + s_allowedSenders.push(STRANGER); + s_allowedSenders.push(DUMMY_CONTRACT_ADDRESS); + + s_tokenPool = new TokenPoolHelper(s_token, s_allowedSenders, address(s_mockRMN), address(s_sourceRouter)); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/HybridLockReleaseUSDCTokenPool/HybridLockReleaseUSDCTokenPool.lockOrBurn.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/HybridLockReleaseUSDCTokenPool/HybridLockReleaseUSDCTokenPool.lockOrBurn.t.sol new file mode 100644 index 00000000000..b3ee31deade --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/HybridLockReleaseUSDCTokenPool/HybridLockReleaseUSDCTokenPool.lockOrBurn.t.sol @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IBurnMintERC20} from "../../../../../shared/token/ERC20/IBurnMintERC20.sol"; +import {ITokenMessenger} from "../../../../pools/USDC/ITokenMessenger.sol"; + +import {BurnMintERC677} from "../../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {Router} from "../../../../Router.sol"; +import {Pool} from "../../../../libraries/Pool.sol"; +import {RateLimiter} from "../../../../libraries/RateLimiter.sol"; + +import {TokenPool} from "../../../../pools/TokenPool.sol"; +import {HybridLockReleaseUSDCTokenPool} from "../../../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; +import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol"; +import {BaseTest} from "../../../BaseTest.t.sol"; +import {MockE2EUSDCTransmitter} from "../../../mocks/MockE2EUSDCTransmitter.sol"; +import {MockUSDCTokenMessenger} from "../../../mocks/MockUSDCTokenMessenger.sol"; + +contract USDCTokenPoolSetup is BaseTest { + IBurnMintERC20 internal s_token; + MockUSDCTokenMessenger internal s_mockUSDC; + MockE2EUSDCTransmitter internal s_mockUSDCTransmitter; + uint32 internal constant USDC_DEST_TOKEN_GAS = 150_000; + + struct USDCMessage { + uint32 version; + uint32 sourceDomain; + uint32 destinationDomain; + uint64 nonce; + bytes32 sender; + bytes32 recipient; + bytes32 destinationCaller; + bytes messageBody; + } + + uint32 internal constant SOURCE_DOMAIN_IDENTIFIER = 0x02020202; + uint32 internal constant DEST_DOMAIN_IDENTIFIER = 0; + + bytes32 internal constant SOURCE_CHAIN_TOKEN_SENDER = bytes32(uint256(uint160(0x01111111221))); + address internal constant SOURCE_CHAIN_USDC_POOL = address(0x23789765456789); + address internal constant DEST_CHAIN_USDC_POOL = address(0x987384873458734); + address internal constant DEST_CHAIN_USDC_TOKEN = address(0x23598918358198766); + + address internal s_routerAllowedOnRamp = address(3456); + address internal s_routerAllowedOffRamp = address(234); + Router internal s_router; + + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPool; + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPoolTransferLiquidity; + address[] internal s_allowedList; + + function setUp() public virtual override { + BaseTest.setUp(); + BurnMintERC677 usdcToken = new BurnMintERC677("LINK", "LNK", 18, 0); + s_token = usdcToken; + deal(address(s_token), OWNER, type(uint256).max); + _setUpRamps(); + + s_mockUSDCTransmitter = new MockE2EUSDCTransmitter(0, DEST_DOMAIN_IDENTIFIER, address(s_token)); + s_mockUSDC = new MockUSDCTokenMessenger(0, address(s_mockUSDCTransmitter)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDCTransmitter)); + + s_usdcTokenPool = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + s_usdcTokenPoolTransferLiquidity = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDC)); + usdcToken.grantMintAndBurnRoles(address(s_usdcTokenPool)); + + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](2); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(address(s_token)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + chainUpdates[1] = TokenPool.ChainUpdate({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(DEST_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(DEST_CHAIN_USDC_TOKEN), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + + s_usdcTokenPool.applyChainUpdates(chainUpdates); + + USDCTokenPool.DomainUpdate[] memory domains = new USDCTokenPool.DomainUpdate[](1); + domains[0] = USDCTokenPool.DomainUpdate({ + destChainSelector: DEST_CHAIN_SELECTOR, + domainIdentifier: 9999, + allowedCaller: keccak256("allowedCaller"), + enabled: true + }); + + s_usdcTokenPool.setDomains(domains); + + vm.expectEmit(); + emit HybridLockReleaseUSDCTokenPool.LiquidityProviderSet(address(0), OWNER, DEST_CHAIN_SELECTOR); + + s_usdcTokenPool.setLiquidityProvider(DEST_CHAIN_SELECTOR, OWNER); + s_usdcTokenPool.setLiquidityProvider(SOURCE_CHAIN_SELECTOR, OWNER); + } + + function _setUpRamps() internal { + s_router = new Router(address(s_token), address(s_mockRMN)); + + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_routerAllowedOnRamp}); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + address[] memory offRamps = new address[](1); + offRamps[0] = s_routerAllowedOffRamp; + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: offRamps[0]}); + + s_router.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + } + + function _generateUSDCMessage( + USDCMessage memory usdcMessage + ) internal pure returns (bytes memory) { + return abi.encodePacked( + usdcMessage.version, + usdcMessage.sourceDomain, + usdcMessage.destinationDomain, + usdcMessage.nonce, + usdcMessage.sender, + usdcMessage.recipient, + usdcMessage.destinationCaller, + usdcMessage.messageBody + ); + } +} + +contract HybridLockReleaseUSDCTokenPool_lockOrBurn is USDCTokenPoolSetup { + function test_onLockReleaseMechanism_Success() public { + bytes32 receiver = bytes32(uint256(uint160(STRANGER))); + + // Mark the destination chain as supporting CCTP, so use L/R instead. + uint64[] memory destChainAdds = new uint64[](1); + destChainAdds[0] = DEST_CHAIN_SELECTOR; + + s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); + + assertTrue( + s_usdcTokenPool.shouldUseLockRelease(DEST_CHAIN_SELECTOR), + "Lock/Release mech not configured for outgoing message to DEST_CHAIN_SELECTOR" + ); + + uint256 amount = 1e6; + + s_token.transfer(address(s_usdcTokenPool), amount); + + vm.startPrank(s_routerAllowedOnRamp); + + vm.expectEmit(); + emit TokenPool.Locked(s_routerAllowedOnRamp, amount); + + s_usdcTokenPool.lockOrBurn( + Pool.LockOrBurnInV1({ + originalSender: OWNER, + receiver: abi.encodePacked(receiver), + amount: amount, + remoteChainSelector: DEST_CHAIN_SELECTOR, + localToken: address(s_token) + }) + ); + + assertEq(s_token.balanceOf(address(s_usdcTokenPool)), amount, "Incorrect token amount in the tokenPool"); + } + + function test_PrimaryMechanism_Success() public { + bytes32 receiver = bytes32(uint256(uint160(STRANGER))); + uint256 amount = 1; + + vm.startPrank(OWNER); + + s_token.transfer(address(s_usdcTokenPool), amount); + + vm.startPrank(s_routerAllowedOnRamp); + + USDCTokenPool.Domain memory expectedDomain = s_usdcTokenPool.getDomain(DEST_CHAIN_SELECTOR); + + vm.expectEmit(); + emit RateLimiter.TokensConsumed(amount); + + vm.expectEmit(); + emit ITokenMessenger.DepositForBurn( + s_mockUSDC.s_nonce(), + address(s_token), + amount, + address(s_usdcTokenPool), + receiver, + expectedDomain.domainIdentifier, + s_mockUSDC.DESTINATION_TOKEN_MESSENGER(), + expectedDomain.allowedCaller + ); + + vm.expectEmit(); + emit TokenPool.Burned(s_routerAllowedOnRamp, amount); + + Pool.LockOrBurnOutV1 memory poolReturnDataV1 = s_usdcTokenPool.lockOrBurn( + Pool.LockOrBurnInV1({ + originalSender: OWNER, + receiver: abi.encodePacked(receiver), + amount: amount, + remoteChainSelector: DEST_CHAIN_SELECTOR, + localToken: address(s_token) + }) + ); + + uint64 nonce = abi.decode(poolReturnDataV1.destPoolData, (uint64)); + assertEq(s_mockUSDC.s_nonce() - 1, nonce); + } + + function test_onLockReleaseMechanism_thenswitchToPrimary_Success() public { + // Test Enabling the LR mechanism and sending an outgoing message + test_PrimaryMechanism_Success(); + + // Disable the LR mechanism so that primary CCTP is used and then attempt to send a message + uint64[] memory destChainRemoves = new uint64[](1); + destChainRemoves[0] = DEST_CHAIN_SELECTOR; + + vm.startPrank(OWNER); + + vm.expectEmit(); + emit HybridLockReleaseUSDCTokenPool.LockReleaseDisabled(DEST_CHAIN_SELECTOR); + + s_usdcTokenPool.updateChainSelectorMechanisms(destChainRemoves, new uint64[](0)); + + // Send an outgoing message + test_PrimaryMechanism_Success(); + } + + function test_WhileMigrationPause_Revert() public { + // Mark the destination chain as supporting CCTP, so use L/R instead. + uint64[] memory destChainAdds = new uint64[](1); + destChainAdds[0] = DEST_CHAIN_SELECTOR; + + s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); + + // Create a fake migration proposal + s_usdcTokenPool.proposeCCTPMigration(DEST_CHAIN_SELECTOR); + + assertEq(s_usdcTokenPool.getCurrentProposedCCTPChainMigration(), DEST_CHAIN_SELECTOR); + + bytes32 receiver = bytes32(uint256(uint160(STRANGER))); + + assertTrue( + s_usdcTokenPool.shouldUseLockRelease(DEST_CHAIN_SELECTOR), + "Lock Release mech not configured for outgoing message to DEST_CHAIN_SELECTOR" + ); + + uint256 amount = 1e6; + + s_token.transfer(address(s_usdcTokenPool), amount); + + vm.startPrank(s_routerAllowedOnRamp); + + // Expect the lockOrBurn to fail because a pending CCTP-Migration has paused outgoing messages on CCIP + vm.expectRevert( + abi.encodeWithSelector(HybridLockReleaseUSDCTokenPool.LanePausedForCCTPMigration.selector, DEST_CHAIN_SELECTOR) + ); + + s_usdcTokenPool.lockOrBurn( + Pool.LockOrBurnInV1({ + originalSender: OWNER, + receiver: abi.encodePacked(receiver), + amount: amount, + remoteChainSelector: DEST_CHAIN_SELECTOR, + localToken: address(s_token) + }) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/HybridLockReleaseUSDCTokenPool/HybridLockReleaseUSDCTokenPool.releaseOrMint.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/HybridLockReleaseUSDCTokenPool/HybridLockReleaseUSDCTokenPool.releaseOrMint.t.sol new file mode 100644 index 00000000000..305991aa38f --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/HybridLockReleaseUSDCTokenPool/HybridLockReleaseUSDCTokenPool.releaseOrMint.t.sol @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IBurnMintERC20} from "../../../../../shared/token/ERC20/IBurnMintERC20.sol"; + +import {BurnMintERC677} from "../../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {Router} from "../../../../Router.sol"; +import {Internal} from "../../../../libraries/Internal.sol"; +import {Pool} from "../../../../libraries/Pool.sol"; +import {TokenPool} from "../../../../pools/TokenPool.sol"; +import {HybridLockReleaseUSDCTokenPool} from "../../../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; +import {LOCK_RELEASE_FLAG} from "../../../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; +import {USDCBridgeMigrator} from "../../../../pools/USDC/USDCBridgeMigrator.sol"; +import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol"; +import {BaseTest} from "../../../BaseTest.t.sol"; +import {MockE2EUSDCTransmitter} from "../../../mocks/MockE2EUSDCTransmitter.sol"; +import {MockUSDCTokenMessenger} from "../../../mocks/MockUSDCTokenMessenger.sol"; + +contract USDCTokenPoolSetup is BaseTest { + IBurnMintERC20 internal s_token; + MockUSDCTokenMessenger internal s_mockUSDC; + MockE2EUSDCTransmitter internal s_mockUSDCTransmitter; + uint32 internal constant USDC_DEST_TOKEN_GAS = 150_000; + + struct USDCMessage { + uint32 version; + uint32 sourceDomain; + uint32 destinationDomain; + uint64 nonce; + bytes32 sender; + bytes32 recipient; + bytes32 destinationCaller; + bytes messageBody; + } + + uint32 internal constant SOURCE_DOMAIN_IDENTIFIER = 0x02020202; + uint32 internal constant DEST_DOMAIN_IDENTIFIER = 0; + + bytes32 internal constant SOURCE_CHAIN_TOKEN_SENDER = bytes32(uint256(uint160(0x01111111221))); + address internal constant SOURCE_CHAIN_USDC_POOL = address(0x23789765456789); + address internal constant DEST_CHAIN_USDC_POOL = address(0x987384873458734); + address internal constant DEST_CHAIN_USDC_TOKEN = address(0x23598918358198766); + + address internal s_routerAllowedOnRamp = address(3456); + address internal s_routerAllowedOffRamp = address(234); + Router internal s_router; + + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPool; + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPoolTransferLiquidity; + address[] internal s_allowedList; + + function setUp() public virtual override { + BaseTest.setUp(); + BurnMintERC677 usdcToken = new BurnMintERC677("LINK", "LNK", 18, 0); + s_token = usdcToken; + deal(address(s_token), OWNER, type(uint256).max); + _setUpRamps(); + + s_mockUSDCTransmitter = new MockE2EUSDCTransmitter(0, DEST_DOMAIN_IDENTIFIER, address(s_token)); + s_mockUSDC = new MockUSDCTokenMessenger(0, address(s_mockUSDCTransmitter)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDCTransmitter)); + + s_usdcTokenPool = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + s_usdcTokenPoolTransferLiquidity = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDC)); + usdcToken.grantMintAndBurnRoles(address(s_usdcTokenPool)); + + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](2); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(address(s_token)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + chainUpdates[1] = TokenPool.ChainUpdate({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(DEST_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(DEST_CHAIN_USDC_TOKEN), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + + s_usdcTokenPool.applyChainUpdates(chainUpdates); + + USDCTokenPool.DomainUpdate[] memory domains = new USDCTokenPool.DomainUpdate[](1); + domains[0] = USDCTokenPool.DomainUpdate({ + destChainSelector: DEST_CHAIN_SELECTOR, + domainIdentifier: 9999, + allowedCaller: keccak256("allowedCaller"), + enabled: true + }); + + s_usdcTokenPool.setDomains(domains); + + vm.expectEmit(); + emit HybridLockReleaseUSDCTokenPool.LiquidityProviderSet(address(0), OWNER, DEST_CHAIN_SELECTOR); + + s_usdcTokenPool.setLiquidityProvider(DEST_CHAIN_SELECTOR, OWNER); + s_usdcTokenPool.setLiquidityProvider(SOURCE_CHAIN_SELECTOR, OWNER); + } + + function _setUpRamps() internal { + s_router = new Router(address(s_token), address(s_mockRMN)); + + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_routerAllowedOnRamp}); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + address[] memory offRamps = new address[](1); + offRamps[0] = s_routerAllowedOffRamp; + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: offRamps[0]}); + + s_router.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + } + + function _generateUSDCMessage( + USDCMessage memory usdcMessage + ) internal pure returns (bytes memory) { + return abi.encodePacked( + usdcMessage.version, + usdcMessage.sourceDomain, + usdcMessage.destinationDomain, + usdcMessage.nonce, + usdcMessage.sender, + usdcMessage.recipient, + usdcMessage.destinationCaller, + usdcMessage.messageBody + ); + } +} + +contract HybridLockReleaseUSDCTokenPool_releaseOrMint is USDCTokenPoolSetup { + function test_OnLockReleaseMechanism_Success() public { + address recipient = address(1234); + + // Designate the SOURCE_CHAIN as not using native-USDC, and so the L/R mechanism must be used instead + uint64[] memory destChainAdds = new uint64[](1); + destChainAdds[0] = SOURCE_CHAIN_SELECTOR; + + s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); + + assertTrue( + s_usdcTokenPool.shouldUseLockRelease(SOURCE_CHAIN_SELECTOR), + "Lock/Release mech not configured for incoming message from SOURCE_CHAIN_SELECTOR" + ); + + vm.startPrank(OWNER); + s_usdcTokenPool.setLiquidityProvider(SOURCE_CHAIN_SELECTOR, OWNER); + + // Add 1e12 liquidity so that there's enough to release + vm.startPrank(s_usdcTokenPool.getLiquidityProvider(SOURCE_CHAIN_SELECTOR)); + + s_token.approve(address(s_usdcTokenPool), type(uint256).max); + + uint256 liquidityAmount = 1e12; + s_usdcTokenPool.provideLiquidity(SOURCE_CHAIN_SELECTOR, liquidityAmount); + + Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ + sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + destTokenAddress: abi.encode(address(s_usdcTokenPool)), + extraData: abi.encode(USDCTokenPool.SourceTokenDataPayload({nonce: 1, sourceDomain: SOURCE_DOMAIN_IDENTIFIER})), + destGasAmount: USDC_DEST_TOKEN_GAS + }); + + uint256 amount = 1e6; + + vm.startPrank(s_routerAllowedOffRamp); + + vm.expectEmit(); + emit TokenPool.Released(s_routerAllowedOffRamp, recipient, amount); + + Pool.ReleaseOrMintOutV1 memory poolReturnDataV1 = s_usdcTokenPool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: abi.encode(OWNER), + receiver: recipient, + amount: amount, + localToken: address(s_token), + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + sourcePoolAddress: sourceTokenData.sourcePoolAddress, + sourcePoolData: abi.encode(LOCK_RELEASE_FLAG), + offchainTokenData: "" + }) + ); + + assertEq(poolReturnDataV1.destinationAmount, amount, "destinationAmount and actual amount transferred differ"); + + // Simulate the off-ramp forwarding tokens to the recipient on destination chain + // s_token.transfer(recipient, amount); + + assertEq( + s_token.balanceOf(address(s_usdcTokenPool)), + liquidityAmount - amount, + "Incorrect remaining liquidity in TokenPool" + ); + assertEq(s_token.balanceOf(recipient), amount, "Tokens not transferred to recipient"); + } + + // https://etherscan.io/tx/0xac9f501fe0b76df1f07a22e1db30929fd12524bc7068d74012dff948632f0883 + function test_incomingMessageWithPrimaryMechanism() public { + bytes memory encodedUsdcMessage = + hex"000000000000000300000000000000000000127a00000000000000000000000019330d10d9cc8751218eaf51e8885d058642e08a000000000000000000000000bd3fa81b58ba92a82136038b25adec7066af3155000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000004af08f56978be7dce2d1be3c65c005b41e79401c000000000000000000000000000000000000000000000000000000002057ff7a0000000000000000000000003a23f943181408eac424116af7b7790c94cb97a50000000000000000000000000000000000000000000000000000000000000000000000000000008274119237535fd659626b090f87e365ff89ebc7096bb32e8b0e85f155626b73ae7c4bb2485c184b7cc3cf7909045487890b104efb62ae74a73e32901bdcec91df1bb9ee08ccb014fcbcfe77b74d1263fd4e0b0e8de05d6c9a5913554364abfd5ea768b222f50c715908183905d74044bb2b97527c7e70ae7983c443a603557cac3b1c000000000000000000000000000000000000000000000000000000000000"; + bytes memory attestation = bytes("attestation bytes"); + + uint32 nonce = 4730; + uint32 sourceDomain = 3; + uint256 amount = 100; + + Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ + sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + destTokenAddress: abi.encode(address(s_usdcTokenPool)), + extraData: abi.encode(USDCTokenPool.SourceTokenDataPayload({nonce: nonce, sourceDomain: sourceDomain})), + destGasAmount: USDC_DEST_TOKEN_GAS + }); + + // The mocked receiver does not release the token to the pool, so we manually do it here + deal(address(s_token), address(s_usdcTokenPool), amount); + + bytes memory offchainTokenData = + abi.encode(USDCTokenPool.MessageAndAttestation({message: encodedUsdcMessage, attestation: attestation})); + + vm.expectCall( + address(s_mockUSDCTransmitter), + abi.encodeWithSelector(MockE2EUSDCTransmitter.receiveMessage.selector, encodedUsdcMessage, attestation) + ); + + vm.startPrank(s_routerAllowedOffRamp); + s_usdcTokenPool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: abi.encode(OWNER), + receiver: OWNER, + amount: amount, + localToken: address(s_token), + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + sourcePoolAddress: sourceTokenData.sourcePoolAddress, + sourcePoolData: sourceTokenData.extraData, + offchainTokenData: offchainTokenData + }) + ); + } + + function test_WhileMigrationPause_Revert() public { + address recipient = address(1234); + + // Designate the SOURCE_CHAIN as not using native-USDC, and so the L/R mechanism must be used instead + uint64[] memory destChainAdds = new uint64[](1); + destChainAdds[0] = SOURCE_CHAIN_SELECTOR; + + s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); + + assertTrue( + s_usdcTokenPool.shouldUseLockRelease(SOURCE_CHAIN_SELECTOR), + "Lock/Release mech not configured for incoming message from SOURCE_CHAIN_SELECTOR" + ); + + vm.startPrank(OWNER); + + vm.expectEmit(); + emit USDCBridgeMigrator.CCTPMigrationProposed(SOURCE_CHAIN_SELECTOR); + + // Propose the migration to CCTP + s_usdcTokenPool.proposeCCTPMigration(SOURCE_CHAIN_SELECTOR); + + Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ + sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + destTokenAddress: abi.encode(address(s_usdcTokenPool)), + extraData: abi.encode(USDCTokenPool.SourceTokenDataPayload({nonce: 1, sourceDomain: SOURCE_DOMAIN_IDENTIFIER})), + destGasAmount: USDC_DEST_TOKEN_GAS + }); + + bytes memory sourcePoolDataLockRelease = abi.encode(LOCK_RELEASE_FLAG); + + uint256 amount = 1e6; + + vm.startPrank(s_routerAllowedOffRamp); + + // Expect revert because the lane is paused and no incoming messages should be allowed + vm.expectRevert( + abi.encodeWithSelector(HybridLockReleaseUSDCTokenPool.LanePausedForCCTPMigration.selector, SOURCE_CHAIN_SELECTOR) + ); + + s_usdcTokenPool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: abi.encode(OWNER), + receiver: recipient, + amount: amount, + localToken: address(s_token), + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + sourcePoolAddress: sourceTokenData.sourcePoolAddress, + sourcePoolData: sourcePoolDataLockRelease, + offchainTokenData: "" + }) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/HybridLockReleaseUSDCTokenPool/HybridLockReleaseUSDCTokenPool.transferLiquidity.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/HybridLockReleaseUSDCTokenPool/HybridLockReleaseUSDCTokenPool.transferLiquidity.t.sol new file mode 100644 index 00000000000..07eeadf486a --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/HybridLockReleaseUSDCTokenPool/HybridLockReleaseUSDCTokenPool.transferLiquidity.t.sol @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {ILiquidityContainer} from "../../../../../liquiditymanager/interfaces/ILiquidityContainer.sol"; +import {IBurnMintERC20} from "../../../../../shared/token/ERC20/IBurnMintERC20.sol"; + +import {BurnMintERC677} from "../../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {Router} from "../../../../Router.sol"; + +import {TokenPool} from "../../../../pools/TokenPool.sol"; +import {HybridLockReleaseUSDCTokenPool} from "../../../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; +import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol"; +import {BaseTest} from "../../../BaseTest.t.sol"; +import {MockE2EUSDCTransmitter} from "../../../mocks/MockE2EUSDCTransmitter.sol"; +import {MockUSDCTokenMessenger} from "../../../mocks/MockUSDCTokenMessenger.sol"; + +contract USDCTokenPoolSetup is BaseTest { + IBurnMintERC20 internal s_token; + MockUSDCTokenMessenger internal s_mockUSDC; + MockE2EUSDCTransmitter internal s_mockUSDCTransmitter; + uint32 internal constant USDC_DEST_TOKEN_GAS = 150_000; + + struct USDCMessage { + uint32 version; + uint32 sourceDomain; + uint32 destinationDomain; + uint64 nonce; + bytes32 sender; + bytes32 recipient; + bytes32 destinationCaller; + bytes messageBody; + } + + uint32 internal constant SOURCE_DOMAIN_IDENTIFIER = 0x02020202; + uint32 internal constant DEST_DOMAIN_IDENTIFIER = 0; + + bytes32 internal constant SOURCE_CHAIN_TOKEN_SENDER = bytes32(uint256(uint160(0x01111111221))); + address internal constant SOURCE_CHAIN_USDC_POOL = address(0x23789765456789); + address internal constant DEST_CHAIN_USDC_POOL = address(0x987384873458734); + address internal constant DEST_CHAIN_USDC_TOKEN = address(0x23598918358198766); + + address internal s_routerAllowedOnRamp = address(3456); + address internal s_routerAllowedOffRamp = address(234); + Router internal s_router; + + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPool; + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPoolTransferLiquidity; + address[] internal s_allowedList; + + function setUp() public virtual override { + BaseTest.setUp(); + BurnMintERC677 usdcToken = new BurnMintERC677("LINK", "LNK", 18, 0); + s_token = usdcToken; + deal(address(s_token), OWNER, type(uint256).max); + _setUpRamps(); + + s_mockUSDCTransmitter = new MockE2EUSDCTransmitter(0, DEST_DOMAIN_IDENTIFIER, address(s_token)); + s_mockUSDC = new MockUSDCTokenMessenger(0, address(s_mockUSDCTransmitter)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDCTransmitter)); + + s_usdcTokenPool = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + s_usdcTokenPoolTransferLiquidity = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDC)); + usdcToken.grantMintAndBurnRoles(address(s_usdcTokenPool)); + + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](2); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(address(s_token)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + chainUpdates[1] = TokenPool.ChainUpdate({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(DEST_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(DEST_CHAIN_USDC_TOKEN), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + + s_usdcTokenPool.applyChainUpdates(chainUpdates); + + USDCTokenPool.DomainUpdate[] memory domains = new USDCTokenPool.DomainUpdate[](1); + domains[0] = USDCTokenPool.DomainUpdate({ + destChainSelector: DEST_CHAIN_SELECTOR, + domainIdentifier: 9999, + allowedCaller: keccak256("allowedCaller"), + enabled: true + }); + + s_usdcTokenPool.setDomains(domains); + + vm.expectEmit(); + emit HybridLockReleaseUSDCTokenPool.LiquidityProviderSet(address(0), OWNER, DEST_CHAIN_SELECTOR); + + s_usdcTokenPool.setLiquidityProvider(DEST_CHAIN_SELECTOR, OWNER); + s_usdcTokenPool.setLiquidityProvider(SOURCE_CHAIN_SELECTOR, OWNER); + } + + function _setUpRamps() internal { + s_router = new Router(address(s_token), address(s_mockRMN)); + + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_routerAllowedOnRamp}); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + address[] memory offRamps = new address[](1); + offRamps[0] = s_routerAllowedOffRamp; + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: offRamps[0]}); + + s_router.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + } + + function _generateUSDCMessage( + USDCMessage memory usdcMessage + ) internal pure returns (bytes memory) { + return abi.encodePacked( + usdcMessage.version, + usdcMessage.sourceDomain, + usdcMessage.destinationDomain, + usdcMessage.nonce, + usdcMessage.sender, + usdcMessage.recipient, + usdcMessage.destinationCaller, + usdcMessage.messageBody + ); + } +} + +contract HybridLockReleaseUSDCTokenPool_TransferLiquidity is USDCTokenPoolSetup { + function test_transferLiquidity_Success() public { + // Set as the OWNER so we can provide liquidity + vm.startPrank(OWNER); + + s_usdcTokenPool.setLiquidityProvider(DEST_CHAIN_SELECTOR, OWNER); + s_token.approve(address(s_usdcTokenPool), type(uint256).max); + + uint256 liquidityAmount = 1e9; + + // Provide some liquidity to the pool + s_usdcTokenPool.provideLiquidity(DEST_CHAIN_SELECTOR, liquidityAmount); + + // Set the new token pool as the rebalancer + s_usdcTokenPool.transferOwnership(address(s_usdcTokenPoolTransferLiquidity)); + + vm.expectEmit(); + emit ILiquidityContainer.LiquidityRemoved(address(s_usdcTokenPoolTransferLiquidity), liquidityAmount); + + vm.expectEmit(); + emit HybridLockReleaseUSDCTokenPool.LiquidityTransferred( + address(s_usdcTokenPool), DEST_CHAIN_SELECTOR, liquidityAmount + ); + + s_usdcTokenPoolTransferLiquidity.transferLiquidity(address(s_usdcTokenPool), DEST_CHAIN_SELECTOR); + + assertEq( + s_usdcTokenPool.owner(), + address(s_usdcTokenPoolTransferLiquidity), + "Ownership of the old pool should be transferred to the new pool" + ); + + assertEq( + s_usdcTokenPoolTransferLiquidity.getLockedTokensForChain(DEST_CHAIN_SELECTOR), + liquidityAmount, + "Tokens locked for dest chain doesn't match expected amount in storage" + ); + + assertEq( + s_usdcTokenPool.getLockedTokensForChain(DEST_CHAIN_SELECTOR), + 0, + "Tokens locked for dest chain in old token pool doesn't match expected amount in storage" + ); + + assertEq( + s_token.balanceOf(address(s_usdcTokenPoolTransferLiquidity)), + liquidityAmount, + "Liquidity amount of tokens should be new in new pool, but aren't" + ); + + assertEq( + s_token.balanceOf(address(s_usdcTokenPool)), + 0, + "Liquidity amount of tokens should be zero in old pool, but aren't" + ); + } + + function test_cannotTransferLiquidityDuringPendingMigration_Revert() public { + // Set as the OWNER so we can provide liquidity + vm.startPrank(OWNER); + + // Mark the destination chain as supporting CCTP, so use L/R instead. + uint64[] memory destChainAdds = new uint64[](1); + destChainAdds[0] = DEST_CHAIN_SELECTOR; + + s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); + + s_usdcTokenPool.setLiquidityProvider(DEST_CHAIN_SELECTOR, OWNER); + s_token.approve(address(s_usdcTokenPool), type(uint256).max); + + uint256 liquidityAmount = 1e9; + + // Provide some liquidity to the pool + s_usdcTokenPool.provideLiquidity(DEST_CHAIN_SELECTOR, liquidityAmount); + + // Set the new token pool as the rebalancer + s_usdcTokenPool.transferOwnership(address(s_usdcTokenPoolTransferLiquidity)); + + s_usdcTokenPool.proposeCCTPMigration(DEST_CHAIN_SELECTOR); + + vm.expectRevert( + abi.encodeWithSelector(HybridLockReleaseUSDCTokenPool.LanePausedForCCTPMigration.selector, DEST_CHAIN_SELECTOR) + ); + + s_usdcTokenPoolTransferLiquidity.transferLiquidity(address(s_usdcTokenPool), DEST_CHAIN_SELECTOR); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.burnLockedUSDC.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.burnLockedUSDC.t.sol new file mode 100644 index 00000000000..b95d821bb88 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.burnLockedUSDC.t.sol @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IBurnMintERC20} from "../../../../../shared/token/ERC20/IBurnMintERC20.sol"; + +import {BurnMintERC677} from "../../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {Router} from "../../../../Router.sol"; +import {Pool} from "../../../../libraries/Pool.sol"; + +import {TokenPool} from "../../../../pools/TokenPool.sol"; +import {HybridLockReleaseUSDCTokenPool} from "../../../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; +import {USDCBridgeMigrator} from "../../../../pools/USDC/USDCBridgeMigrator.sol"; +import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol"; +import {BaseTest} from "../../../BaseTest.t.sol"; +import {MockE2EUSDCTransmitter} from "../../../mocks/MockE2EUSDCTransmitter.sol"; +import {MockUSDCTokenMessenger} from "../../../mocks/MockUSDCTokenMessenger.sol"; +import {HybridLockReleaseUSDCTokenPool_lockOrBurn} from + "../HybridLockReleaseUSDCTokenPool/HybridLockReleaseUSDCTokenPool.lockOrBurn.t.sol"; + +contract USDCTokenPoolSetup is BaseTest { + IBurnMintERC20 internal s_token; + MockUSDCTokenMessenger internal s_mockUSDC; + MockE2EUSDCTransmitter internal s_mockUSDCTransmitter; + uint32 internal constant USDC_DEST_TOKEN_GAS = 150_000; + + struct USDCMessage { + uint32 version; + uint32 sourceDomain; + uint32 destinationDomain; + uint64 nonce; + bytes32 sender; + bytes32 recipient; + bytes32 destinationCaller; + bytes messageBody; + } + + uint32 internal constant SOURCE_DOMAIN_IDENTIFIER = 0x02020202; + uint32 internal constant DEST_DOMAIN_IDENTIFIER = 0; + + bytes32 internal constant SOURCE_CHAIN_TOKEN_SENDER = bytes32(uint256(uint160(0x01111111221))); + address internal constant SOURCE_CHAIN_USDC_POOL = address(0x23789765456789); + address internal constant DEST_CHAIN_USDC_POOL = address(0x987384873458734); + address internal constant DEST_CHAIN_USDC_TOKEN = address(0x23598918358198766); + + address internal s_routerAllowedOnRamp = address(3456); + address internal s_routerAllowedOffRamp = address(234); + Router internal s_router; + + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPool; + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPoolTransferLiquidity; + address[] internal s_allowedList; + + function setUp() public virtual override { + BaseTest.setUp(); + BurnMintERC677 usdcToken = new BurnMintERC677("LINK", "LNK", 18, 0); + s_token = usdcToken; + deal(address(s_token), OWNER, type(uint256).max); + _setUpRamps(); + + s_mockUSDCTransmitter = new MockE2EUSDCTransmitter(0, DEST_DOMAIN_IDENTIFIER, address(s_token)); + s_mockUSDC = new MockUSDCTokenMessenger(0, address(s_mockUSDCTransmitter)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDCTransmitter)); + + s_usdcTokenPool = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + s_usdcTokenPoolTransferLiquidity = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDC)); + usdcToken.grantMintAndBurnRoles(address(s_usdcTokenPool)); + + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](2); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(address(s_token)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + chainUpdates[1] = TokenPool.ChainUpdate({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(DEST_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(DEST_CHAIN_USDC_TOKEN), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + + s_usdcTokenPool.applyChainUpdates(chainUpdates); + + USDCTokenPool.DomainUpdate[] memory domains = new USDCTokenPool.DomainUpdate[](1); + domains[0] = USDCTokenPool.DomainUpdate({ + destChainSelector: DEST_CHAIN_SELECTOR, + domainIdentifier: 9999, + allowedCaller: keccak256("allowedCaller"), + enabled: true + }); + + s_usdcTokenPool.setDomains(domains); + + vm.expectEmit(); + emit HybridLockReleaseUSDCTokenPool.LiquidityProviderSet(address(0), OWNER, DEST_CHAIN_SELECTOR); + + s_usdcTokenPool.setLiquidityProvider(DEST_CHAIN_SELECTOR, OWNER); + s_usdcTokenPool.setLiquidityProvider(SOURCE_CHAIN_SELECTOR, OWNER); + } + + function _setUpRamps() internal { + s_router = new Router(address(s_token), address(s_mockRMN)); + + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_routerAllowedOnRamp}); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + address[] memory offRamps = new address[](1); + offRamps[0] = s_routerAllowedOffRamp; + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: offRamps[0]}); + + s_router.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + } + + function _generateUSDCMessage( + USDCMessage memory usdcMessage + ) internal pure returns (bytes memory) { + return abi.encodePacked( + usdcMessage.version, + usdcMessage.sourceDomain, + usdcMessage.destinationDomain, + usdcMessage.nonce, + usdcMessage.sender, + usdcMessage.recipient, + usdcMessage.destinationCaller, + usdcMessage.messageBody + ); + } +} + +contract USDCBridgeMigrator_BurnLockedUSDC is HybridLockReleaseUSDCTokenPool_lockOrBurn { + function test_lockOrBurn_then_BurnInCCTPMigration_Success() public { + bytes32 receiver = bytes32(uint256(uint160(STRANGER))); + address CIRCLE = makeAddr("CIRCLE CCTP Migrator"); + + // Mark the destination chain as supporting CCTP, so use L/R instead. + uint64[] memory destChainAdds = new uint64[](1); + destChainAdds[0] = DEST_CHAIN_SELECTOR; + + s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); + + assertTrue( + s_usdcTokenPool.shouldUseLockRelease(DEST_CHAIN_SELECTOR), + "Lock/Release mech not configured for outgoing message to DEST_CHAIN_SELECTOR" + ); + + uint256 amount = 1e6; + + s_token.transfer(address(s_usdcTokenPool), amount); + + vm.startPrank(s_routerAllowedOnRamp); + + vm.expectEmit(); + emit TokenPool.Locked(s_routerAllowedOnRamp, amount); + + s_usdcTokenPool.lockOrBurn( + Pool.LockOrBurnInV1({ + originalSender: OWNER, + receiver: abi.encodePacked(receiver), + amount: amount, + remoteChainSelector: DEST_CHAIN_SELECTOR, + localToken: address(s_token) + }) + ); + + // Ensure that the tokens are properly locked + assertEq(s_token.balanceOf(address(s_usdcTokenPool)), amount, "Incorrect token amount in the tokenPool"); + + assertEq( + s_usdcTokenPool.getLockedTokensForChain(DEST_CHAIN_SELECTOR), + amount, + "Internal locked token accounting is incorrect" + ); + + vm.startPrank(OWNER); + + vm.expectEmit(); + emit USDCBridgeMigrator.CircleMigratorAddressSet(CIRCLE); + + s_usdcTokenPool.setCircleMigratorAddress(CIRCLE); + + vm.expectEmit(); + emit USDCBridgeMigrator.CCTPMigrationProposed(DEST_CHAIN_SELECTOR); + + // Propose the migration to CCTP + s_usdcTokenPool.proposeCCTPMigration(DEST_CHAIN_SELECTOR); + + assertEq( + s_usdcTokenPool.getCurrentProposedCCTPChainMigration(), + DEST_CHAIN_SELECTOR, + "Current proposed chain migration does not match expected for DEST_CHAIN_SELECTOR" + ); + + // Impersonate the set circle address and execute the proposal + vm.startPrank(CIRCLE); + + vm.expectEmit(); + emit USDCBridgeMigrator.CCTPMigrationExecuted(DEST_CHAIN_SELECTOR, amount); + + // Ensure the call to the burn function is properly + vm.expectCall(address(s_token), abi.encodeWithSelector(bytes4(keccak256("burn(uint256)")), amount)); + + s_usdcTokenPool.burnLockedUSDC(); + + // Assert that the tokens were actually burned + assertEq(s_token.balanceOf(address(s_usdcTokenPool)), 0, "Tokens were not burned out of the tokenPool"); + + // Ensure the proposal slot was cleared and there's no tokens locked for the destination chain anymore + assertEq(s_usdcTokenPool.getCurrentProposedCCTPChainMigration(), 0, "Proposal Slot should be empty"); + assertEq( + s_usdcTokenPool.getLockedTokensForChain(DEST_CHAIN_SELECTOR), + 0, + "No tokens should be locked for DEST_CHAIN_SELECTOR after CCTP-approved burn" + ); + + assertFalse( + s_usdcTokenPool.shouldUseLockRelease(DEST_CHAIN_SELECTOR), "Lock/Release mech should be disabled after a burn" + ); + + test_PrimaryMechanism_Success(); + } + + function test_invalidPermissions_Revert() public { + address CIRCLE = makeAddr("CIRCLE"); + + vm.startPrank(OWNER); + + // Set the circle migrator address for later, but don't start pranking as it yet + s_usdcTokenPool.setCircleMigratorAddress(CIRCLE); + + vm.expectRevert(abi.encodeWithSelector(USDCBridgeMigrator.onlyCircle.selector)); + + // Should fail because only Circle can call this function + s_usdcTokenPool.burnLockedUSDC(); + + vm.startPrank(CIRCLE); + + vm.expectRevert(abi.encodeWithSelector(USDCBridgeMigrator.NoMigrationProposalPending.selector)); + s_usdcTokenPool.burnLockedUSDC(); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.cancelMigrationProposal.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.cancelMigrationProposal.t.sol new file mode 100644 index 00000000000..513361f096c --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.cancelMigrationProposal.t.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IBurnMintERC20} from "../../../../../shared/token/ERC20/IBurnMintERC20.sol"; + +import {BurnMintERC677} from "../../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {Router} from "../../../../Router.sol"; + +import {TokenPool} from "../../../../pools/TokenPool.sol"; +import {HybridLockReleaseUSDCTokenPool} from "../../../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; +import {USDCBridgeMigrator} from "../../../../pools/USDC/USDCBridgeMigrator.sol"; +import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol"; +import {BaseTest} from "../../../BaseTest.t.sol"; +import {MockE2EUSDCTransmitter} from "../../../mocks/MockE2EUSDCTransmitter.sol"; +import {MockUSDCTokenMessenger} from "../../../mocks/MockUSDCTokenMessenger.sol"; + +contract USDCTokenPoolSetup is BaseTest { + IBurnMintERC20 internal s_token; + MockUSDCTokenMessenger internal s_mockUSDC; + MockE2EUSDCTransmitter internal s_mockUSDCTransmitter; + uint32 internal constant USDC_DEST_TOKEN_GAS = 150_000; + + struct USDCMessage { + uint32 version; + uint32 sourceDomain; + uint32 destinationDomain; + uint64 nonce; + bytes32 sender; + bytes32 recipient; + bytes32 destinationCaller; + bytes messageBody; + } + + uint32 internal constant SOURCE_DOMAIN_IDENTIFIER = 0x02020202; + uint32 internal constant DEST_DOMAIN_IDENTIFIER = 0; + + bytes32 internal constant SOURCE_CHAIN_TOKEN_SENDER = bytes32(uint256(uint160(0x01111111221))); + address internal constant SOURCE_CHAIN_USDC_POOL = address(0x23789765456789); + address internal constant DEST_CHAIN_USDC_POOL = address(0x987384873458734); + address internal constant DEST_CHAIN_USDC_TOKEN = address(0x23598918358198766); + + address internal s_routerAllowedOnRamp = address(3456); + address internal s_routerAllowedOffRamp = address(234); + Router internal s_router; + + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPool; + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPoolTransferLiquidity; + address[] internal s_allowedList; + + function setUp() public virtual override { + BaseTest.setUp(); + BurnMintERC677 usdcToken = new BurnMintERC677("LINK", "LNK", 18, 0); + s_token = usdcToken; + deal(address(s_token), OWNER, type(uint256).max); + _setUpRamps(); + + s_mockUSDCTransmitter = new MockE2EUSDCTransmitter(0, DEST_DOMAIN_IDENTIFIER, address(s_token)); + s_mockUSDC = new MockUSDCTokenMessenger(0, address(s_mockUSDCTransmitter)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDCTransmitter)); + + s_usdcTokenPool = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + s_usdcTokenPoolTransferLiquidity = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDC)); + usdcToken.grantMintAndBurnRoles(address(s_usdcTokenPool)); + + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](2); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(address(s_token)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + chainUpdates[1] = TokenPool.ChainUpdate({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(DEST_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(DEST_CHAIN_USDC_TOKEN), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + + s_usdcTokenPool.applyChainUpdates(chainUpdates); + + USDCTokenPool.DomainUpdate[] memory domains = new USDCTokenPool.DomainUpdate[](1); + domains[0] = USDCTokenPool.DomainUpdate({ + destChainSelector: DEST_CHAIN_SELECTOR, + domainIdentifier: 9999, + allowedCaller: keccak256("allowedCaller"), + enabled: true + }); + + s_usdcTokenPool.setDomains(domains); + + vm.expectEmit(); + emit HybridLockReleaseUSDCTokenPool.LiquidityProviderSet(address(0), OWNER, DEST_CHAIN_SELECTOR); + + s_usdcTokenPool.setLiquidityProvider(DEST_CHAIN_SELECTOR, OWNER); + s_usdcTokenPool.setLiquidityProvider(SOURCE_CHAIN_SELECTOR, OWNER); + } + + function _setUpRamps() internal { + s_router = new Router(address(s_token), address(s_mockRMN)); + + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_routerAllowedOnRamp}); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + address[] memory offRamps = new address[](1); + offRamps[0] = s_routerAllowedOffRamp; + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: offRamps[0]}); + + s_router.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + } + + function _generateUSDCMessage( + USDCMessage memory usdcMessage + ) internal pure returns (bytes memory) { + return abi.encodePacked( + usdcMessage.version, + usdcMessage.sourceDomain, + usdcMessage.destinationDomain, + usdcMessage.nonce, + usdcMessage.sender, + usdcMessage.recipient, + usdcMessage.destinationCaller, + usdcMessage.messageBody + ); + } +} + +contract USDCBridgeMigrator_cancelMigrationProposal is USDCTokenPoolSetup { + function test_cancelExistingCCTPMigrationProposal_Success() public { + vm.startPrank(OWNER); + + // Mark the destination chain as supporting CCTP, so use L/R instead. + uint64[] memory destChainAdds = new uint64[](1); + destChainAdds[0] = DEST_CHAIN_SELECTOR; + + s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); + + vm.expectEmit(); + emit USDCBridgeMigrator.CCTPMigrationProposed(DEST_CHAIN_SELECTOR); + + s_usdcTokenPool.proposeCCTPMigration(DEST_CHAIN_SELECTOR); + + assertEq( + s_usdcTokenPool.getCurrentProposedCCTPChainMigration(), + DEST_CHAIN_SELECTOR, + "migration proposal should exist, but doesn't" + ); + + vm.expectEmit(); + emit USDCBridgeMigrator.CCTPMigrationCancelled(DEST_CHAIN_SELECTOR); + + s_usdcTokenPool.cancelExistingCCTPMigrationProposal(); + + assertEq( + s_usdcTokenPool.getCurrentProposedCCTPChainMigration(), + 0, + "migration proposal exists, but shouldn't after being cancelled" + ); + + vm.expectRevert(USDCBridgeMigrator.NoMigrationProposalPending.selector); + s_usdcTokenPool.cancelExistingCCTPMigrationProposal(); + } + + function test_cannotCancelANonExistentMigrationProposal_Revert() public { + vm.expectRevert(USDCBridgeMigrator.NoMigrationProposalPending.selector); + + // Proposal to migrate doesn't exist, and so the chain selector is zero, and therefore should revert + s_usdcTokenPool.cancelExistingCCTPMigrationProposal(); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.excludeTokensFromBurn.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.excludeTokensFromBurn.t.sol new file mode 100644 index 00000000000..11cffd0e03d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.excludeTokensFromBurn.t.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IBurnMintERC20} from "../../../../../shared/token/ERC20/IBurnMintERC20.sol"; + +import {BurnMintERC677} from "../../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {Router} from "../../../../Router.sol"; + +import {TokenPool} from "../../../../pools/TokenPool.sol"; +import {HybridLockReleaseUSDCTokenPool} from "../../../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; +import {USDCBridgeMigrator} from "../../../../pools/USDC/USDCBridgeMigrator.sol"; +import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol"; +import {BaseTest} from "../../../BaseTest.t.sol"; +import {MockE2EUSDCTransmitter} from "../../../mocks/MockE2EUSDCTransmitter.sol"; +import {MockUSDCTokenMessenger} from "../../../mocks/MockUSDCTokenMessenger.sol"; + +contract USDCTokenPoolSetup is BaseTest { + IBurnMintERC20 internal s_token; + MockUSDCTokenMessenger internal s_mockUSDC; + MockE2EUSDCTransmitter internal s_mockUSDCTransmitter; + uint32 internal constant USDC_DEST_TOKEN_GAS = 150_000; + + struct USDCMessage { + uint32 version; + uint32 sourceDomain; + uint32 destinationDomain; + uint64 nonce; + bytes32 sender; + bytes32 recipient; + bytes32 destinationCaller; + bytes messageBody; + } + + uint32 internal constant SOURCE_DOMAIN_IDENTIFIER = 0x02020202; + uint32 internal constant DEST_DOMAIN_IDENTIFIER = 0; + + bytes32 internal constant SOURCE_CHAIN_TOKEN_SENDER = bytes32(uint256(uint160(0x01111111221))); + address internal constant SOURCE_CHAIN_USDC_POOL = address(0x23789765456789); + address internal constant DEST_CHAIN_USDC_POOL = address(0x987384873458734); + address internal constant DEST_CHAIN_USDC_TOKEN = address(0x23598918358198766); + + address internal s_routerAllowedOnRamp = address(3456); + address internal s_routerAllowedOffRamp = address(234); + Router internal s_router; + + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPool; + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPoolTransferLiquidity; + address[] internal s_allowedList; + + function setUp() public virtual override { + BaseTest.setUp(); + BurnMintERC677 usdcToken = new BurnMintERC677("LINK", "LNK", 18, 0); + s_token = usdcToken; + deal(address(s_token), OWNER, type(uint256).max); + _setUpRamps(); + + s_mockUSDCTransmitter = new MockE2EUSDCTransmitter(0, DEST_DOMAIN_IDENTIFIER, address(s_token)); + s_mockUSDC = new MockUSDCTokenMessenger(0, address(s_mockUSDCTransmitter)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDCTransmitter)); + + s_usdcTokenPool = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + s_usdcTokenPoolTransferLiquidity = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDC)); + usdcToken.grantMintAndBurnRoles(address(s_usdcTokenPool)); + + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](2); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(address(s_token)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + chainUpdates[1] = TokenPool.ChainUpdate({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(DEST_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(DEST_CHAIN_USDC_TOKEN), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + + s_usdcTokenPool.applyChainUpdates(chainUpdates); + + USDCTokenPool.DomainUpdate[] memory domains = new USDCTokenPool.DomainUpdate[](1); + domains[0] = USDCTokenPool.DomainUpdate({ + destChainSelector: DEST_CHAIN_SELECTOR, + domainIdentifier: 9999, + allowedCaller: keccak256("allowedCaller"), + enabled: true + }); + + s_usdcTokenPool.setDomains(domains); + + vm.expectEmit(); + emit HybridLockReleaseUSDCTokenPool.LiquidityProviderSet(address(0), OWNER, DEST_CHAIN_SELECTOR); + + s_usdcTokenPool.setLiquidityProvider(DEST_CHAIN_SELECTOR, OWNER); + s_usdcTokenPool.setLiquidityProvider(SOURCE_CHAIN_SELECTOR, OWNER); + } + + function _setUpRamps() internal { + s_router = new Router(address(s_token), address(s_mockRMN)); + + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_routerAllowedOnRamp}); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + address[] memory offRamps = new address[](1); + offRamps[0] = s_routerAllowedOffRamp; + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: offRamps[0]}); + + s_router.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + } + + function _generateUSDCMessage( + USDCMessage memory usdcMessage + ) internal pure returns (bytes memory) { + return abi.encodePacked( + usdcMessage.version, + usdcMessage.sourceDomain, + usdcMessage.destinationDomain, + usdcMessage.nonce, + usdcMessage.sender, + usdcMessage.recipient, + usdcMessage.destinationCaller, + usdcMessage.messageBody + ); + } +} + +contract USDCBridgeMigrator_excludeTokensFromBurn is USDCTokenPoolSetup { + function test_excludeTokensWhenNoMigrationProposalPending_Revert() public { + vm.expectRevert(abi.encodeWithSelector(USDCBridgeMigrator.NoMigrationProposalPending.selector)); + + vm.startPrank(OWNER); + + s_usdcTokenPool.excludeTokensFromBurn(SOURCE_CHAIN_SELECTOR, 1e6); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.proposeMigration.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.proposeMigration.t.sol new file mode 100644 index 00000000000..d445cbac896 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.proposeMigration.t.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IBurnMintERC20} from "../../../../../shared/token/ERC20/IBurnMintERC20.sol"; + +import {BurnMintERC677} from "../../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {Router} from "../../../../Router.sol"; + +import {TokenPool} from "../../../../pools/TokenPool.sol"; +import {HybridLockReleaseUSDCTokenPool} from "../../../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; +import {USDCBridgeMigrator} from "../../../../pools/USDC/USDCBridgeMigrator.sol"; +import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol"; +import {BaseTest} from "../../../BaseTest.t.sol"; +import {MockE2EUSDCTransmitter} from "../../../mocks/MockE2EUSDCTransmitter.sol"; +import {MockUSDCTokenMessenger} from "../../../mocks/MockUSDCTokenMessenger.sol"; + +contract USDCTokenPoolSetup is BaseTest { + IBurnMintERC20 internal s_token; + MockUSDCTokenMessenger internal s_mockUSDC; + MockE2EUSDCTransmitter internal s_mockUSDCTransmitter; + uint32 internal constant USDC_DEST_TOKEN_GAS = 150_000; + + struct USDCMessage { + uint32 version; + uint32 sourceDomain; + uint32 destinationDomain; + uint64 nonce; + bytes32 sender; + bytes32 recipient; + bytes32 destinationCaller; + bytes messageBody; + } + + uint32 internal constant SOURCE_DOMAIN_IDENTIFIER = 0x02020202; + uint32 internal constant DEST_DOMAIN_IDENTIFIER = 0; + + bytes32 internal constant SOURCE_CHAIN_TOKEN_SENDER = bytes32(uint256(uint160(0x01111111221))); + address internal constant SOURCE_CHAIN_USDC_POOL = address(0x23789765456789); + address internal constant DEST_CHAIN_USDC_POOL = address(0x987384873458734); + address internal constant DEST_CHAIN_USDC_TOKEN = address(0x23598918358198766); + + address internal s_routerAllowedOnRamp = address(3456); + address internal s_routerAllowedOffRamp = address(234); + Router internal s_router; + + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPool; + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPoolTransferLiquidity; + address[] internal s_allowedList; + + function setUp() public virtual override { + BaseTest.setUp(); + BurnMintERC677 usdcToken = new BurnMintERC677("LINK", "LNK", 18, 0); + s_token = usdcToken; + deal(address(s_token), OWNER, type(uint256).max); + _setUpRamps(); + + s_mockUSDCTransmitter = new MockE2EUSDCTransmitter(0, DEST_DOMAIN_IDENTIFIER, address(s_token)); + s_mockUSDC = new MockUSDCTokenMessenger(0, address(s_mockUSDCTransmitter)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDCTransmitter)); + + s_usdcTokenPool = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + s_usdcTokenPoolTransferLiquidity = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDC)); + usdcToken.grantMintAndBurnRoles(address(s_usdcTokenPool)); + + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](2); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(address(s_token)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + chainUpdates[1] = TokenPool.ChainUpdate({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(DEST_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(DEST_CHAIN_USDC_TOKEN), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + + s_usdcTokenPool.applyChainUpdates(chainUpdates); + + USDCTokenPool.DomainUpdate[] memory domains = new USDCTokenPool.DomainUpdate[](1); + domains[0] = USDCTokenPool.DomainUpdate({ + destChainSelector: DEST_CHAIN_SELECTOR, + domainIdentifier: 9999, + allowedCaller: keccak256("allowedCaller"), + enabled: true + }); + + s_usdcTokenPool.setDomains(domains); + + vm.expectEmit(); + emit HybridLockReleaseUSDCTokenPool.LiquidityProviderSet(address(0), OWNER, DEST_CHAIN_SELECTOR); + + s_usdcTokenPool.setLiquidityProvider(DEST_CHAIN_SELECTOR, OWNER); + s_usdcTokenPool.setLiquidityProvider(SOURCE_CHAIN_SELECTOR, OWNER); + } + + function _setUpRamps() internal { + s_router = new Router(address(s_token), address(s_mockRMN)); + + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_routerAllowedOnRamp}); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + address[] memory offRamps = new address[](1); + offRamps[0] = s_routerAllowedOffRamp; + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: offRamps[0]}); + + s_router.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + } + + function _generateUSDCMessage( + USDCMessage memory usdcMessage + ) internal pure returns (bytes memory) { + return abi.encodePacked( + usdcMessage.version, + usdcMessage.sourceDomain, + usdcMessage.destinationDomain, + usdcMessage.nonce, + usdcMessage.sender, + usdcMessage.recipient, + usdcMessage.destinationCaller, + usdcMessage.messageBody + ); + } +} + +contract USDCBridgeMigrator_proposeMigration is USDCTokenPoolSetup { + function test_ChainNotUsingLockRelease_Revert() public { + vm.expectRevert(abi.encodeWithSelector(USDCBridgeMigrator.InvalidChainSelector.selector)); + + vm.startPrank(OWNER); + + s_usdcTokenPool.proposeCCTPMigration(0x98765); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.provideLiquidity.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.provideLiquidity.t.sol new file mode 100644 index 00000000000..a94cd4df348 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.provideLiquidity.t.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IBurnMintERC20} from "../../../../../shared/token/ERC20/IBurnMintERC20.sol"; + +import {BurnMintERC677} from "../../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {Router} from "../../../../Router.sol"; + +import {TokenPool} from "../../../../pools/TokenPool.sol"; +import {HybridLockReleaseUSDCTokenPool} from "../../../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; +import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol"; +import {BaseTest} from "../../../BaseTest.t.sol"; +import {MockE2EUSDCTransmitter} from "../../../mocks/MockE2EUSDCTransmitter.sol"; +import {MockUSDCTokenMessenger} from "../../../mocks/MockUSDCTokenMessenger.sol"; +import {USDCBridgeMigrator_BurnLockedUSDC} from "./USDCBridgeMigrator.burnLockedUSDC.t.sol"; + +contract USDCTokenPoolSetup is BaseTest { + IBurnMintERC20 internal s_token; + MockUSDCTokenMessenger internal s_mockUSDC; + MockE2EUSDCTransmitter internal s_mockUSDCTransmitter; + uint32 internal constant USDC_DEST_TOKEN_GAS = 150_000; + + struct USDCMessage { + uint32 version; + uint32 sourceDomain; + uint32 destinationDomain; + uint64 nonce; + bytes32 sender; + bytes32 recipient; + bytes32 destinationCaller; + bytes messageBody; + } + + uint32 internal constant SOURCE_DOMAIN_IDENTIFIER = 0x02020202; + uint32 internal constant DEST_DOMAIN_IDENTIFIER = 0; + + bytes32 internal constant SOURCE_CHAIN_TOKEN_SENDER = bytes32(uint256(uint160(0x01111111221))); + address internal constant SOURCE_CHAIN_USDC_POOL = address(0x23789765456789); + address internal constant DEST_CHAIN_USDC_POOL = address(0x987384873458734); + address internal constant DEST_CHAIN_USDC_TOKEN = address(0x23598918358198766); + + address internal s_routerAllowedOnRamp = address(3456); + address internal s_routerAllowedOffRamp = address(234); + Router internal s_router; + + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPool; + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPoolTransferLiquidity; + address[] internal s_allowedList; + + function setUp() public virtual override { + BaseTest.setUp(); + BurnMintERC677 usdcToken = new BurnMintERC677("LINK", "LNK", 18, 0); + s_token = usdcToken; + deal(address(s_token), OWNER, type(uint256).max); + _setUpRamps(); + + s_mockUSDCTransmitter = new MockE2EUSDCTransmitter(0, DEST_DOMAIN_IDENTIFIER, address(s_token)); + s_mockUSDC = new MockUSDCTokenMessenger(0, address(s_mockUSDCTransmitter)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDCTransmitter)); + + s_usdcTokenPool = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + s_usdcTokenPoolTransferLiquidity = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDC)); + usdcToken.grantMintAndBurnRoles(address(s_usdcTokenPool)); + + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](2); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(address(s_token)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + chainUpdates[1] = TokenPool.ChainUpdate({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(DEST_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(DEST_CHAIN_USDC_TOKEN), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + + s_usdcTokenPool.applyChainUpdates(chainUpdates); + + USDCTokenPool.DomainUpdate[] memory domains = new USDCTokenPool.DomainUpdate[](1); + domains[0] = USDCTokenPool.DomainUpdate({ + destChainSelector: DEST_CHAIN_SELECTOR, + domainIdentifier: 9999, + allowedCaller: keccak256("allowedCaller"), + enabled: true + }); + + s_usdcTokenPool.setDomains(domains); + + vm.expectEmit(); + emit HybridLockReleaseUSDCTokenPool.LiquidityProviderSet(address(0), OWNER, DEST_CHAIN_SELECTOR); + + s_usdcTokenPool.setLiquidityProvider(DEST_CHAIN_SELECTOR, OWNER); + s_usdcTokenPool.setLiquidityProvider(SOURCE_CHAIN_SELECTOR, OWNER); + } + + function _setUpRamps() internal { + s_router = new Router(address(s_token), address(s_mockRMN)); + + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_routerAllowedOnRamp}); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + address[] memory offRamps = new address[](1); + offRamps[0] = s_routerAllowedOffRamp; + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: offRamps[0]}); + + s_router.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + } + + function _generateUSDCMessage( + USDCMessage memory usdcMessage + ) internal pure returns (bytes memory) { + return abi.encodePacked( + usdcMessage.version, + usdcMessage.sourceDomain, + usdcMessage.destinationDomain, + usdcMessage.nonce, + usdcMessage.sender, + usdcMessage.recipient, + usdcMessage.destinationCaller, + usdcMessage.messageBody + ); + } +} + +contract USDCBridgeMigrator_provideLiquidity is USDCBridgeMigrator_BurnLockedUSDC { + function test_cannotModifyLiquidityWithoutPermissions_Revert() public { + address randomAddr = makeAddr("RANDOM"); + + vm.startPrank(randomAddr); + + vm.expectRevert(abi.encodeWithSelector(TokenPool.Unauthorized.selector, randomAddr)); + + // Revert because there's insufficient permissions for the DEST_CHAIN_SELECTOR to provide liquidity + s_usdcTokenPool.provideLiquidity(DEST_CHAIN_SELECTOR, 1e6); + } + + function test_cannotProvideLiquidity_AfterMigration_Revert() public { + test_lockOrBurn_then_BurnInCCTPMigration_Success(); + + vm.startPrank(OWNER); + + vm.expectRevert( + abi.encodeWithSelector( + HybridLockReleaseUSDCTokenPool.TokenLockingNotAllowedAfterMigration.selector, DEST_CHAIN_SELECTOR + ) + ); + + s_usdcTokenPool.provideLiquidity(DEST_CHAIN_SELECTOR, 1e6); + } + + function test_cannotProvideLiquidityWhenMigrationProposalPending_Revert() public { + vm.startPrank(OWNER); + + // Mark the destination chain as supporting CCTP, so use L/R instead. + uint64[] memory destChainAdds = new uint64[](1); + destChainAdds[0] = DEST_CHAIN_SELECTOR; + + s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); + + s_usdcTokenPool.proposeCCTPMigration(DEST_CHAIN_SELECTOR); + + vm.expectRevert( + abi.encodeWithSelector(HybridLockReleaseUSDCTokenPool.LanePausedForCCTPMigration.selector, DEST_CHAIN_SELECTOR) + ); + s_usdcTokenPool.provideLiquidity(DEST_CHAIN_SELECTOR, 1e6); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.releaseOrMint.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.releaseOrMint.t.sol new file mode 100644 index 00000000000..9976adf64ea --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.releaseOrMint.t.sol @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IBurnMintERC20} from "../../../../../shared/token/ERC20/IBurnMintERC20.sol"; + +import {BurnMintERC677} from "../../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {Router} from "../../../../Router.sol"; +import {Internal} from "../../../../libraries/Internal.sol"; +import {Pool} from "../../../../libraries/Pool.sol"; + +import {TokenPool} from "../../../../pools/TokenPool.sol"; +import {HybridLockReleaseUSDCTokenPool} from "../../../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; +import {LOCK_RELEASE_FLAG} from "../../../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; +import {USDCBridgeMigrator} from "../../../../pools/USDC/USDCBridgeMigrator.sol"; +import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol"; +import {BaseTest} from "../../../BaseTest.t.sol"; +import {MockE2EUSDCTransmitter} from "../../../mocks/MockE2EUSDCTransmitter.sol"; +import {MockUSDCTokenMessenger} from "../../../mocks/MockUSDCTokenMessenger.sol"; +import {HybridLockReleaseUSDCTokenPool_releaseOrMint} from + "../HybridLockReleaseUSDCTokenPool/HybridLockReleaseUSDCTokenPool.releaseOrMint.t.sol"; + +contract USDCTokenPoolSetup is BaseTest { + IBurnMintERC20 internal s_token; + MockUSDCTokenMessenger internal s_mockUSDC; + MockE2EUSDCTransmitter internal s_mockUSDCTransmitter; + uint32 internal constant USDC_DEST_TOKEN_GAS = 150_000; + + struct USDCMessage { + uint32 version; + uint32 sourceDomain; + uint32 destinationDomain; + uint64 nonce; + bytes32 sender; + bytes32 recipient; + bytes32 destinationCaller; + bytes messageBody; + } + + uint32 internal constant SOURCE_DOMAIN_IDENTIFIER = 0x02020202; + uint32 internal constant DEST_DOMAIN_IDENTIFIER = 0; + + bytes32 internal constant SOURCE_CHAIN_TOKEN_SENDER = bytes32(uint256(uint160(0x01111111221))); + address internal constant SOURCE_CHAIN_USDC_POOL = address(0x23789765456789); + address internal constant DEST_CHAIN_USDC_POOL = address(0x987384873458734); + address internal constant DEST_CHAIN_USDC_TOKEN = address(0x23598918358198766); + + address internal s_routerAllowedOnRamp = address(3456); + address internal s_routerAllowedOffRamp = address(234); + Router internal s_router; + + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPool; + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPoolTransferLiquidity; + address[] internal s_allowedList; + + function setUp() public virtual override { + BaseTest.setUp(); + BurnMintERC677 usdcToken = new BurnMintERC677("LINK", "LNK", 18, 0); + s_token = usdcToken; + deal(address(s_token), OWNER, type(uint256).max); + _setUpRamps(); + + s_mockUSDCTransmitter = new MockE2EUSDCTransmitter(0, DEST_DOMAIN_IDENTIFIER, address(s_token)); + s_mockUSDC = new MockUSDCTokenMessenger(0, address(s_mockUSDCTransmitter)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDCTransmitter)); + + s_usdcTokenPool = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + s_usdcTokenPoolTransferLiquidity = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDC)); + usdcToken.grantMintAndBurnRoles(address(s_usdcTokenPool)); + + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](2); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(address(s_token)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + chainUpdates[1] = TokenPool.ChainUpdate({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(DEST_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(DEST_CHAIN_USDC_TOKEN), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + + s_usdcTokenPool.applyChainUpdates(chainUpdates); + + USDCTokenPool.DomainUpdate[] memory domains = new USDCTokenPool.DomainUpdate[](1); + domains[0] = USDCTokenPool.DomainUpdate({ + destChainSelector: DEST_CHAIN_SELECTOR, + domainIdentifier: 9999, + allowedCaller: keccak256("allowedCaller"), + enabled: true + }); + + s_usdcTokenPool.setDomains(domains); + + vm.expectEmit(); + emit HybridLockReleaseUSDCTokenPool.LiquidityProviderSet(address(0), OWNER, DEST_CHAIN_SELECTOR); + + s_usdcTokenPool.setLiquidityProvider(DEST_CHAIN_SELECTOR, OWNER); + s_usdcTokenPool.setLiquidityProvider(SOURCE_CHAIN_SELECTOR, OWNER); + } + + function _setUpRamps() internal { + s_router = new Router(address(s_token), address(s_mockRMN)); + + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_routerAllowedOnRamp}); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + address[] memory offRamps = new address[](1); + offRamps[0] = s_routerAllowedOffRamp; + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: offRamps[0]}); + + s_router.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + } + + function _generateUSDCMessage( + USDCMessage memory usdcMessage + ) internal pure returns (bytes memory) { + return abi.encodePacked( + usdcMessage.version, + usdcMessage.sourceDomain, + usdcMessage.destinationDomain, + usdcMessage.nonce, + usdcMessage.sender, + usdcMessage.recipient, + usdcMessage.destinationCaller, + usdcMessage.messageBody + ); + } +} + +contract USDCBridgeMigrator_releaseOrMint is HybridLockReleaseUSDCTokenPool_releaseOrMint { + function test_unstickManualTxAfterMigration_destChain_Success() public { + address recipient = address(1234); + // Test the edge case where a tx is stuck in the manual tx queue and the destination chain is the one that + // should process is after a migration. I.E the message will have the Lock-Release flag set in the OffChainData, + // which should tell it to use the lock-release mechanism with the tokens provided. + + // We want the released amount to be 1e6, so to simulate the workflow, we sent those tokens to the contract as + // liquidity + uint256 amount = 1e6; + // Add 1e12 liquidity so that there's enough to release + vm.startPrank(s_usdcTokenPool.getLiquidityProvider(SOURCE_CHAIN_SELECTOR)); + + s_token.approve(address(s_usdcTokenPool), type(uint256).max); + s_usdcTokenPool.provideLiquidity(SOURCE_CHAIN_SELECTOR, amount); + + // By Default, the source chain will be indicated as use-CCTP so we need to change that. We create a message + // that will use the Lock-Release flag in the offchain data to indicate that the tokens should be released + // instead of minted since there's no attestation for us to use. + + vm.startPrank(s_routerAllowedOffRamp); + + vm.expectEmit(); + emit TokenPool.Released(s_routerAllowedOffRamp, recipient, amount); + + Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ + sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + destTokenAddress: abi.encode(address(s_usdcTokenPool)), + extraData: abi.encode(USDCTokenPool.SourceTokenDataPayload({nonce: 1, sourceDomain: SOURCE_DOMAIN_IDENTIFIER})), + destGasAmount: USDC_DEST_TOKEN_GAS + }); + + Pool.ReleaseOrMintOutV1 memory poolReturnDataV1 = s_usdcTokenPool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: abi.encode(OWNER), + receiver: recipient, + amount: amount, + localToken: address(s_token), + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + sourcePoolAddress: sourceTokenData.sourcePoolAddress, + sourcePoolData: abi.encode(LOCK_RELEASE_FLAG), + offchainTokenData: "" + }) + ); + + // By this point, the tx should have executed, with the Lock-Release taking over, and being forwaded to the + // recipient + + assertEq(poolReturnDataV1.destinationAmount, amount, "destinationAmount and actual amount transferred differ"); + assertEq(s_token.balanceOf(address(s_usdcTokenPool)), 0, "Tokens should be transferred out of the pool"); + assertEq(s_token.balanceOf(recipient), amount, "Tokens should be transferred to the recipient"); + + // We also want to check that the system uses CCTP Burn/Mint for all other messages that don't have that flag + // which after a migration will mean all new messages. + + // The message should fail without an error because it failed to decode a non-existent attestation which would + // revert without an error + vm.expectRevert(); + + s_usdcTokenPool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: abi.encode(OWNER), + receiver: recipient, + amount: amount, + localToken: address(s_token), + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + sourcePoolAddress: sourceTokenData.sourcePoolAddress, + sourcePoolData: "", + offchainTokenData: "" + }) + ); + } + + function test_unstickManualTxAfterMigration_homeChain_Success() public { + address CIRCLE = makeAddr("CIRCLE"); + address recipient = address(1234); + + // Mark the destination chain as supporting CCTP, so use L/R instead. + uint64[] memory destChainAdds = new uint64[](1); + destChainAdds[0] = SOURCE_CHAIN_SELECTOR; + + s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); + + // Test the edge case where a tx is stuck in the manual tx queue and the source chain (mainnet) needs unsticking + // In this test we want 1e6 worth of tokens to be stuck, so first we provide liquidity to the pool >1e6 + + uint256 amount = 1e6; + // Add 1e12 liquidity so that there's enough to release + vm.startPrank(s_usdcTokenPool.getLiquidityProvider(SOURCE_CHAIN_SELECTOR)); + + s_token.approve(address(s_usdcTokenPool), type(uint256).max); + + // I picked 3x the amount to be stuck so that we can have enough to release with a buffer + s_usdcTokenPool.provideLiquidity(SOURCE_CHAIN_SELECTOR, amount * 3); + + // At this point in the process, the router will lock new messages, so we want to simulate excluding tokens + // stuck coming back from the destination, to the home chain. This way they can be released and not minted + // since there's no corresponding attestation to use for minting. + vm.startPrank(OWNER); + + s_usdcTokenPool.proposeCCTPMigration(SOURCE_CHAIN_SELECTOR); + + // Exclude the tokens from being burned and check for the event + vm.expectEmit(); + emit USDCBridgeMigrator.TokensExcludedFromBurn(SOURCE_CHAIN_SELECTOR, amount, (amount * 3) - amount); + + s_usdcTokenPool.excludeTokensFromBurn(SOURCE_CHAIN_SELECTOR, amount); + + assertEq( + s_usdcTokenPool.getLockedTokensForChain(SOURCE_CHAIN_SELECTOR), + (amount * 3), + "Tokens locked minus ones excluded from the burn should be 2e6" + ); + + assertEq( + s_usdcTokenPool.getExcludedTokensByChain(SOURCE_CHAIN_SELECTOR), + 1e6, + "1e6 tokens should be excluded from the burn" + ); + + s_usdcTokenPool.setCircleMigratorAddress(CIRCLE); + + vm.startPrank(CIRCLE); + + s_usdcTokenPool.burnLockedUSDC(); + + assertEq( + s_usdcTokenPool.getLockedTokensForChain(SOURCE_CHAIN_SELECTOR), 0, "All tokens should be burned out of the pool" + ); + + assertEq( + s_usdcTokenPool.getExcludedTokensByChain(SOURCE_CHAIN_SELECTOR), + 1e6, + "There should still be 1e6 tokens excluded from the burn" + ); + + assertEq(s_token.balanceOf(address(s_usdcTokenPool)), 1e6, "All tokens minus the excluded should be in the pool"); + + // Now that the burn is successful, we can release the tokens that were excluded from the burn + vm.startPrank(s_routerAllowedOffRamp); + + vm.expectEmit(); + emit TokenPool.Released(s_routerAllowedOffRamp, recipient, amount); + + Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ + sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + destTokenAddress: abi.encode(address(s_usdcTokenPool)), + extraData: abi.encode(USDCTokenPool.SourceTokenDataPayload({nonce: 1, sourceDomain: SOURCE_DOMAIN_IDENTIFIER})), + destGasAmount: USDC_DEST_TOKEN_GAS + }); + + Pool.ReleaseOrMintOutV1 memory poolReturnDataV1 = s_usdcTokenPool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: abi.encode(OWNER), + receiver: recipient, + amount: amount, + localToken: address(s_token), + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + sourcePoolAddress: sourceTokenData.sourcePoolAddress, + sourcePoolData: abi.encode(LOCK_RELEASE_FLAG), + offchainTokenData: "" + }) + ); + + assertEq(poolReturnDataV1.destinationAmount, amount, "destinationAmount and actual amount transferred differ"); + assertEq(s_token.balanceOf(address(s_usdcTokenPool)), 0, "Tokens should be transferred out of the pool"); + assertEq(s_token.balanceOf(recipient), amount, "Tokens should be transferred to the recipient"); + assertEq( + s_usdcTokenPool.getExcludedTokensByChain(SOURCE_CHAIN_SELECTOR), + 0, + "All tokens should be released from the exclusion list" + ); + + // We also want to check that the system uses CCTP Burn/Mint for all other messages that don't have that flag + test_incomingMessageWithPrimaryMechanism(); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.updateChainSelectorMechanism.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.updateChainSelectorMechanism.t.sol new file mode 100644 index 00000000000..da3e15bc8ad --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/USDCBridgeMigrator/USDCBridgeMigrator.updateChainSelectorMechanism.t.sol @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IBurnMintERC20} from "../../../../../shared/token/ERC20/IBurnMintERC20.sol"; + +import {BurnMintERC677} from "../../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {Router} from "../../../../Router.sol"; + +import {TokenPool} from "../../../../pools/TokenPool.sol"; +import {HybridLockReleaseUSDCTokenPool} from "../../../../pools/USDC/HybridLockReleaseUSDCTokenPool.sol"; +import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol"; +import {BaseTest} from "../../../BaseTest.t.sol"; +import {MockE2EUSDCTransmitter} from "../../../mocks/MockE2EUSDCTransmitter.sol"; +import {MockUSDCTokenMessenger} from "../../../mocks/MockUSDCTokenMessenger.sol"; +import {USDCBridgeMigrator_BurnLockedUSDC} from "./USDCBridgeMigrator.burnLockedUSDC.t.sol"; + +contract USDCTokenPoolSetup is BaseTest { + IBurnMintERC20 internal s_token; + MockUSDCTokenMessenger internal s_mockUSDC; + MockE2EUSDCTransmitter internal s_mockUSDCTransmitter; + uint32 internal constant USDC_DEST_TOKEN_GAS = 150_000; + + struct USDCMessage { + uint32 version; + uint32 sourceDomain; + uint32 destinationDomain; + uint64 nonce; + bytes32 sender; + bytes32 recipient; + bytes32 destinationCaller; + bytes messageBody; + } + + uint32 internal constant SOURCE_DOMAIN_IDENTIFIER = 0x02020202; + uint32 internal constant DEST_DOMAIN_IDENTIFIER = 0; + + bytes32 internal constant SOURCE_CHAIN_TOKEN_SENDER = bytes32(uint256(uint160(0x01111111221))); + address internal constant SOURCE_CHAIN_USDC_POOL = address(0x23789765456789); + address internal constant DEST_CHAIN_USDC_POOL = address(0x987384873458734); + address internal constant DEST_CHAIN_USDC_TOKEN = address(0x23598918358198766); + + address internal s_routerAllowedOnRamp = address(3456); + address internal s_routerAllowedOffRamp = address(234); + Router internal s_router; + + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPool; + HybridLockReleaseUSDCTokenPool internal s_usdcTokenPoolTransferLiquidity; + address[] internal s_allowedList; + + function setUp() public virtual override { + BaseTest.setUp(); + BurnMintERC677 usdcToken = new BurnMintERC677("LINK", "LNK", 18, 0); + s_token = usdcToken; + deal(address(s_token), OWNER, type(uint256).max); + _setUpRamps(); + + s_mockUSDCTransmitter = new MockE2EUSDCTransmitter(0, DEST_DOMAIN_IDENTIFIER, address(s_token)); + s_mockUSDC = new MockUSDCTokenMessenger(0, address(s_mockUSDCTransmitter)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDCTransmitter)); + + s_usdcTokenPool = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + s_usdcTokenPoolTransferLiquidity = + new HybridLockReleaseUSDCTokenPool(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDC)); + usdcToken.grantMintAndBurnRoles(address(s_usdcTokenPool)); + + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](2); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(address(s_token)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + chainUpdates[1] = TokenPool.ChainUpdate({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(DEST_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(DEST_CHAIN_USDC_TOKEN), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + + s_usdcTokenPool.applyChainUpdates(chainUpdates); + + USDCTokenPool.DomainUpdate[] memory domains = new USDCTokenPool.DomainUpdate[](1); + domains[0] = USDCTokenPool.DomainUpdate({ + destChainSelector: DEST_CHAIN_SELECTOR, + domainIdentifier: 9999, + allowedCaller: keccak256("allowedCaller"), + enabled: true + }); + + s_usdcTokenPool.setDomains(domains); + + vm.expectEmit(); + emit HybridLockReleaseUSDCTokenPool.LiquidityProviderSet(address(0), OWNER, DEST_CHAIN_SELECTOR); + + s_usdcTokenPool.setLiquidityProvider(DEST_CHAIN_SELECTOR, OWNER); + s_usdcTokenPool.setLiquidityProvider(SOURCE_CHAIN_SELECTOR, OWNER); + } + + function _setUpRamps() internal { + s_router = new Router(address(s_token), address(s_mockRMN)); + + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_routerAllowedOnRamp}); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + address[] memory offRamps = new address[](1); + offRamps[0] = s_routerAllowedOffRamp; + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: offRamps[0]}); + + s_router.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + } + + function _generateUSDCMessage( + USDCMessage memory usdcMessage + ) internal pure returns (bytes memory) { + return abi.encodePacked( + usdcMessage.version, + usdcMessage.sourceDomain, + usdcMessage.destinationDomain, + usdcMessage.nonce, + usdcMessage.sender, + usdcMessage.recipient, + usdcMessage.destinationCaller, + usdcMessage.messageBody + ); + } +} + +contract USDCBridgeMigrator_updateChainSelectorMechanism is USDCBridgeMigrator_BurnLockedUSDC { + function test_cannotRevertChainMechanism_afterMigration_Revert() public { + test_lockOrBurn_then_BurnInCCTPMigration_Success(); + + vm.startPrank(OWNER); + + // Mark the destination chain as supporting CCTP, so use L/R instead. + uint64[] memory destChainAdds = new uint64[](1); + destChainAdds[0] = DEST_CHAIN_SELECTOR; + + vm.expectRevert( + abi.encodeWithSelector( + HybridLockReleaseUSDCTokenPool.TokenLockingNotAllowedAfterMigration.selector, DEST_CHAIN_SELECTOR + ) + ); + + s_usdcTokenPool.updateChainSelectorMechanisms(new uint64[](0), destChainAdds); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.lockOrBurn.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.lockOrBurn.t.sol new file mode 100644 index 00000000000..2ca33ad4f5f --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.lockOrBurn.t.sol @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {ITokenMessenger} from "../../../../pools/USDC/ITokenMessenger.sol"; + +import {Router} from "../../../../Router.sol"; +import {Pool} from "../../../../libraries/Pool.sol"; +import {RateLimiter} from "../../../../libraries/RateLimiter.sol"; +import {TokenPool} from "../../../../pools/TokenPool.sol"; +import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol"; +import {USDCTokenPoolSetup} from "./USDCTokenPoolSetup.t.sol"; + +contract USDCTokenPool_lockOrBurn is USDCTokenPoolSetup { + // Base test case, included for PR gas comparisons as fuzz tests are excluded from forge snapshot due to being flaky. + function test_LockOrBurn_Success() public { + bytes32 receiver = bytes32(uint256(uint160(STRANGER))); + uint256 amount = 1; + s_token.transfer(address(s_usdcTokenPool), amount); + vm.startPrank(s_routerAllowedOnRamp); + + USDCTokenPool.Domain memory expectedDomain = s_usdcTokenPool.getDomain(DEST_CHAIN_SELECTOR); + + vm.expectEmit(); + emit RateLimiter.TokensConsumed(amount); + + vm.expectEmit(); + emit ITokenMessenger.DepositForBurn( + s_mockUSDC.s_nonce(), + address(s_token), + amount, + address(s_usdcTokenPool), + receiver, + expectedDomain.domainIdentifier, + s_mockUSDC.DESTINATION_TOKEN_MESSENGER(), + expectedDomain.allowedCaller + ); + + vm.expectEmit(); + emit TokenPool.Burned(s_routerAllowedOnRamp, amount); + + Pool.LockOrBurnOutV1 memory poolReturnDataV1 = s_usdcTokenPool.lockOrBurn( + Pool.LockOrBurnInV1({ + originalSender: OWNER, + receiver: abi.encodePacked(receiver), + amount: amount, + remoteChainSelector: DEST_CHAIN_SELECTOR, + localToken: address(s_token) + }) + ); + + uint64 nonce = abi.decode(poolReturnDataV1.destPoolData, (uint64)); + assertEq(s_mockUSDC.s_nonce() - 1, nonce); + } + + function test_Fuzz_LockOrBurn_Success(bytes32 destinationReceiver, uint256 amount) public { + vm.assume(destinationReceiver != bytes32(0)); + amount = bound(amount, 1, _getOutboundRateLimiterConfig().capacity); + s_token.transfer(address(s_usdcTokenPool), amount); + vm.startPrank(s_routerAllowedOnRamp); + + USDCTokenPool.Domain memory expectedDomain = s_usdcTokenPool.getDomain(DEST_CHAIN_SELECTOR); + + vm.expectEmit(); + emit RateLimiter.TokensConsumed(amount); + + vm.expectEmit(); + emit ITokenMessenger.DepositForBurn( + s_mockUSDC.s_nonce(), + address(s_token), + amount, + address(s_usdcTokenPool), + destinationReceiver, + expectedDomain.domainIdentifier, + s_mockUSDC.DESTINATION_TOKEN_MESSENGER(), + expectedDomain.allowedCaller + ); + + vm.expectEmit(); + emit TokenPool.Burned(s_routerAllowedOnRamp, amount); + + Pool.LockOrBurnOutV1 memory poolReturnDataV1 = s_usdcTokenPool.lockOrBurn( + Pool.LockOrBurnInV1({ + originalSender: OWNER, + receiver: abi.encodePacked(destinationReceiver), + amount: amount, + remoteChainSelector: DEST_CHAIN_SELECTOR, + localToken: address(s_token) + }) + ); + + uint64 nonce = abi.decode(poolReturnDataV1.destPoolData, (uint64)); + assertEq(s_mockUSDC.s_nonce() - 1, nonce); + assertEq(poolReturnDataV1.destTokenAddress, abi.encode(DEST_CHAIN_USDC_TOKEN)); + } + + function test_Fuzz_LockOrBurnWithAllowList_Success(bytes32 destinationReceiver, uint256 amount) public { + vm.assume(destinationReceiver != bytes32(0)); + amount = bound(amount, 1, _getOutboundRateLimiterConfig().capacity); + s_token.transfer(address(s_usdcTokenPoolWithAllowList), amount); + vm.startPrank(s_routerAllowedOnRamp); + + USDCTokenPool.Domain memory expectedDomain = s_usdcTokenPoolWithAllowList.getDomain(DEST_CHAIN_SELECTOR); + + vm.expectEmit(); + emit RateLimiter.TokensConsumed(amount); + vm.expectEmit(); + emit ITokenMessenger.DepositForBurn( + s_mockUSDC.s_nonce(), + address(s_token), + amount, + address(s_usdcTokenPoolWithAllowList), + destinationReceiver, + expectedDomain.domainIdentifier, + s_mockUSDC.DESTINATION_TOKEN_MESSENGER(), + expectedDomain.allowedCaller + ); + vm.expectEmit(); + emit TokenPool.Burned(s_routerAllowedOnRamp, amount); + + Pool.LockOrBurnOutV1 memory poolReturnDataV1 = s_usdcTokenPoolWithAllowList.lockOrBurn( + Pool.LockOrBurnInV1({ + originalSender: s_allowedList[0], + receiver: abi.encodePacked(destinationReceiver), + amount: amount, + remoteChainSelector: DEST_CHAIN_SELECTOR, + localToken: address(s_token) + }) + ); + uint64 nonce = abi.decode(poolReturnDataV1.destPoolData, (uint64)); + assertEq(s_mockUSDC.s_nonce() - 1, nonce); + assertEq(poolReturnDataV1.destTokenAddress, abi.encode(DEST_CHAIN_USDC_TOKEN)); + } + + // Reverts + function test_UnknownDomain_Revert() public { + uint64 wrongDomain = DEST_CHAIN_SELECTOR + 1; + // We need to setup the wrong chainSelector so it reaches the domain check + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + onRampUpdates[0] = Router.OnRamp({destChainSelector: wrongDomain, onRamp: s_routerAllowedOnRamp}); + s_router.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), new Router.OffRamp[](0)); + + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: wrongDomain, + remotePoolAddress: abi.encode(address(1)), + remoteTokenAddress: abi.encode(address(2)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + + s_usdcTokenPool.applyChainUpdates(chainUpdates); + + uint256 amount = 1000; + vm.startPrank(s_routerAllowedOnRamp); + deal(address(s_token), s_routerAllowedOnRamp, amount); + s_token.approve(address(s_usdcTokenPool), amount); + + vm.expectRevert(abi.encodeWithSelector(USDCTokenPool.UnknownDomain.selector, wrongDomain)); + + s_usdcTokenPool.lockOrBurn( + Pool.LockOrBurnInV1({ + originalSender: OWNER, + receiver: abi.encodePacked(address(0)), + amount: amount, + remoteChainSelector: wrongDomain, + localToken: address(s_token) + }) + ); + } + + function test_CallerIsNotARampOnRouter_Revert() public { + vm.expectRevert(abi.encodeWithSelector(TokenPool.CallerIsNotARampOnRouter.selector, OWNER)); + + s_usdcTokenPool.lockOrBurn( + Pool.LockOrBurnInV1({ + originalSender: OWNER, + receiver: abi.encodePacked(address(0)), + amount: 0, + remoteChainSelector: DEST_CHAIN_SELECTOR, + localToken: address(s_token) + }) + ); + } + + function test_LockOrBurnWithAllowList_Revert() public { + vm.startPrank(s_routerAllowedOnRamp); + + vm.expectRevert(abi.encodeWithSelector(TokenPool.SenderNotAllowed.selector, STRANGER)); + + s_usdcTokenPoolWithAllowList.lockOrBurn( + Pool.LockOrBurnInV1({ + originalSender: STRANGER, + receiver: abi.encodePacked(address(0)), + amount: 1000, + remoteChainSelector: DEST_CHAIN_SELECTOR, + localToken: address(s_token) + }) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.releaseOrMint.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.releaseOrMint.t.sol new file mode 100644 index 00000000000..f4ffde6c82c --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.releaseOrMint.t.sol @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Internal} from "../../../../libraries/Internal.sol"; +import {Pool} from "../../../../libraries/Pool.sol"; +import {RateLimiter} from "../../../../libraries/RateLimiter.sol"; +import {TokenPool} from "../../../../pools/TokenPool.sol"; +import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol"; +import {MockE2EUSDCTransmitter} from "../../../mocks/MockE2EUSDCTransmitter.sol"; +import {USDCTokenPoolSetup} from "./USDCTokenPoolSetup.t.sol"; + +contract USDCTokenPool_releaseOrMint is USDCTokenPoolSetup { + // From https://github.com/circlefin/evm-cctp-contracts/blob/377c9bd813fb86a42d900ae4003599d82aef635a/src/messages/BurnMessage.sol#L57 + function _formatMessage( + uint32 _version, + bytes32 _burnToken, + bytes32 _mintRecipient, + uint256 _amount, + bytes32 _messageSender + ) internal pure returns (bytes memory) { + return abi.encodePacked(_version, _burnToken, _mintRecipient, _amount, _messageSender); + } + + function test_Fuzz_ReleaseOrMint_Success(address recipient, uint256 amount) public { + vm.assume(recipient != address(0) && recipient != address(s_token)); + amount = bound(amount, 0, _getInboundRateLimiterConfig().capacity); + + USDCMessage memory usdcMessage = USDCMessage({ + version: 0, + sourceDomain: SOURCE_DOMAIN_IDENTIFIER, + destinationDomain: DEST_DOMAIN_IDENTIFIER, + nonce: 0x060606060606, + sender: SOURCE_CHAIN_TOKEN_SENDER, + recipient: bytes32(uint256(uint160(recipient))), + destinationCaller: bytes32(uint256(uint160(address(s_usdcTokenPool)))), + messageBody: _formatMessage( + 0, + bytes32(uint256(uint160(address(s_token)))), + bytes32(uint256(uint160(recipient))), + amount, + bytes32(uint256(uint160(OWNER))) + ) + }); + + bytes memory message = _generateUSDCMessage(usdcMessage); + bytes memory attestation = bytes("attestation bytes"); + + Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ + sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + destTokenAddress: abi.encode(address(s_usdcTokenPool)), + extraData: abi.encode( + USDCTokenPool.SourceTokenDataPayload({nonce: usdcMessage.nonce, sourceDomain: SOURCE_DOMAIN_IDENTIFIER}) + ), + destGasAmount: USDC_DEST_TOKEN_GAS + }); + + bytes memory offchainTokenData = + abi.encode(USDCTokenPool.MessageAndAttestation({message: message, attestation: attestation})); + + // The mocked receiver does not release the token to the pool, so we manually do it here + deal(address(s_token), address(s_usdcTokenPool), amount); + + vm.expectEmit(); + emit TokenPool.Minted(s_routerAllowedOffRamp, recipient, amount); + + vm.expectCall( + address(s_mockUSDCTransmitter), + abi.encodeWithSelector(MockE2EUSDCTransmitter.receiveMessage.selector, message, attestation) + ); + + vm.startPrank(s_routerAllowedOffRamp); + s_usdcTokenPool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: abi.encode(OWNER), + receiver: recipient, + amount: amount, + localToken: address(s_token), + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + sourcePoolAddress: sourceTokenData.sourcePoolAddress, + sourcePoolData: sourceTokenData.extraData, + offchainTokenData: offchainTokenData + }) + ); + } + + // https://etherscan.io/tx/0xac9f501fe0b76df1f07a22e1db30929fd12524bc7068d74012dff948632f0883 + function test_ReleaseOrMintRealTx_Success() public { + bytes memory encodedUsdcMessage = + hex"000000000000000300000000000000000000127a00000000000000000000000019330d10d9cc8751218eaf51e8885d058642e08a000000000000000000000000bd3fa81b58ba92a82136038b25adec7066af3155000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000004af08f56978be7dce2d1be3c65c005b41e79401c000000000000000000000000000000000000000000000000000000002057ff7a0000000000000000000000003a23f943181408eac424116af7b7790c94cb97a50000000000000000000000000000000000000000000000000000000000000000000000000000008274119237535fd659626b090f87e365ff89ebc7096bb32e8b0e85f155626b73ae7c4bb2485c184b7cc3cf7909045487890b104efb62ae74a73e32901bdcec91df1bb9ee08ccb014fcbcfe77b74d1263fd4e0b0e8de05d6c9a5913554364abfd5ea768b222f50c715908183905d74044bb2b97527c7e70ae7983c443a603557cac3b1c000000000000000000000000000000000000000000000000000000000000"; + bytes memory attestation = bytes("attestation bytes"); + + uint32 nonce = 4730; + uint32 sourceDomain = 3; + uint256 amount = 100; + + Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ + sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + destTokenAddress: abi.encode(address(s_usdcTokenPool)), + extraData: abi.encode(USDCTokenPool.SourceTokenDataPayload({nonce: nonce, sourceDomain: sourceDomain})), + destGasAmount: USDC_DEST_TOKEN_GAS + }); + + // The mocked receiver does not release the token to the pool, so we manually do it here + deal(address(s_token), address(s_usdcTokenPool), amount); + + bytes memory offchainTokenData = + abi.encode(USDCTokenPool.MessageAndAttestation({message: encodedUsdcMessage, attestation: attestation})); + + vm.expectCall( + address(s_mockUSDCTransmitter), + abi.encodeWithSelector(MockE2EUSDCTransmitter.receiveMessage.selector, encodedUsdcMessage, attestation) + ); + + vm.startPrank(s_routerAllowedOffRamp); + s_usdcTokenPool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: abi.encode(OWNER), + receiver: OWNER, + amount: amount, + localToken: address(s_token), + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + sourcePoolAddress: sourceTokenData.sourcePoolAddress, + sourcePoolData: sourceTokenData.extraData, + offchainTokenData: offchainTokenData + }) + ); + } + + // Reverts + function test_UnlockingUSDCFailed_Revert() public { + vm.startPrank(s_routerAllowedOffRamp); + s_mockUSDCTransmitter.setShouldSucceed(false); + + uint256 amount = 13255235235; + + USDCMessage memory usdcMessage = USDCMessage({ + version: 0, + sourceDomain: SOURCE_DOMAIN_IDENTIFIER, + destinationDomain: DEST_DOMAIN_IDENTIFIER, + nonce: 0x060606060606, + sender: SOURCE_CHAIN_TOKEN_SENDER, + recipient: bytes32(uint256(uint160(address(s_mockUSDC)))), + destinationCaller: bytes32(uint256(uint160(address(s_usdcTokenPool)))), + messageBody: _formatMessage( + 0, + bytes32(uint256(uint160(address(s_token)))), + bytes32(uint256(uint160(OWNER))), + amount, + bytes32(uint256(uint160(OWNER))) + ) + }); + + Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ + sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + destTokenAddress: abi.encode(address(s_usdcTokenPool)), + extraData: abi.encode( + USDCTokenPool.SourceTokenDataPayload({nonce: usdcMessage.nonce, sourceDomain: SOURCE_DOMAIN_IDENTIFIER}) + ), + destGasAmount: USDC_DEST_TOKEN_GAS + }); + + bytes memory offchainTokenData = abi.encode( + USDCTokenPool.MessageAndAttestation({message: _generateUSDCMessage(usdcMessage), attestation: bytes("")}) + ); + + vm.expectRevert(USDCTokenPool.UnlockingUSDCFailed.selector); + + s_usdcTokenPool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: abi.encode(OWNER), + receiver: OWNER, + amount: amount, + localToken: address(s_token), + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + sourcePoolAddress: sourceTokenData.sourcePoolAddress, + sourcePoolData: sourceTokenData.extraData, + offchainTokenData: offchainTokenData + }) + ); + } + + function test_TokenMaxCapacityExceeded_Revert() public { + uint256 capacity = _getInboundRateLimiterConfig().capacity; + uint256 amount = 10 * capacity; + address recipient = address(1); + vm.startPrank(s_routerAllowedOffRamp); + + Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ + sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + destTokenAddress: abi.encode(address(s_usdcTokenPool)), + extraData: abi.encode(USDCTokenPool.SourceTokenDataPayload({nonce: 1, sourceDomain: SOURCE_DOMAIN_IDENTIFIER})), + destGasAmount: USDC_DEST_TOKEN_GAS + }); + + bytes memory offchainTokenData = + abi.encode(USDCTokenPool.MessageAndAttestation({message: bytes(""), attestation: bytes("")})); + + vm.expectRevert( + abi.encodeWithSelector(RateLimiter.TokenMaxCapacityExceeded.selector, capacity, amount, address(s_token)) + ); + + s_usdcTokenPool.releaseOrMint( + Pool.ReleaseOrMintInV1({ + originalSender: abi.encode(OWNER), + receiver: recipient, + amount: amount, + localToken: address(s_token), + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + sourcePoolAddress: sourceTokenData.sourcePoolAddress, + sourcePoolData: sourceTokenData.extraData, + offchainTokenData: offchainTokenData + }) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.setDomains.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.setDomains.t.sol new file mode 100644 index 00000000000..1fe5d828bdb --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.setDomains.t.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../../shared/access/Ownable2Step.sol"; +import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol"; +import {USDCTokenPoolSetup} from "./USDCTokenPoolSetup.t.sol"; + +contract USDCTokenPool_setDomains is USDCTokenPoolSetup { + mapping(uint64 destChainSelector => USDCTokenPool.Domain domain) private s_chainToDomain; + + // Setting lower fuzz run as 256 runs was causing differing gas results in snapshot. + /// forge-config: default.fuzz.runs = 32 + /// forge-config: ccip.fuzz.runs = 32 + function test_Fuzz_SetDomains_Success( + bytes32[5] calldata allowedCallers, + uint32[5] calldata domainIdentifiers, + uint64[5] calldata destChainSelectors + ) public { + uint256 numberOfDomains = allowedCallers.length; + USDCTokenPool.DomainUpdate[] memory domainUpdates = new USDCTokenPool.DomainUpdate[](numberOfDomains); + for (uint256 i = 0; i < numberOfDomains; ++i) { + vm.assume(allowedCallers[i] != bytes32(0) && domainIdentifiers[i] != 0 && destChainSelectors[i] != 0); + + domainUpdates[i] = USDCTokenPool.DomainUpdate({ + allowedCaller: allowedCallers[i], + domainIdentifier: domainIdentifiers[i], + destChainSelector: destChainSelectors[i], + enabled: true + }); + + s_chainToDomain[destChainSelectors[i]] = + USDCTokenPool.Domain({domainIdentifier: domainIdentifiers[i], allowedCaller: allowedCallers[i], enabled: true}); + } + + vm.expectEmit(); + emit USDCTokenPool.DomainsSet(domainUpdates); + + s_usdcTokenPool.setDomains(domainUpdates); + + for (uint256 i = 0; i < numberOfDomains; ++i) { + USDCTokenPool.Domain memory expected = s_chainToDomain[destChainSelectors[i]]; + USDCTokenPool.Domain memory got = s_usdcTokenPool.getDomain(destChainSelectors[i]); + assertEq(got.allowedCaller, expected.allowedCaller); + assertEq(got.domainIdentifier, expected.domainIdentifier); + } + } + + // Reverts + + function test_OnlyOwner_Revert() public { + USDCTokenPool.DomainUpdate[] memory domainUpdates = new USDCTokenPool.DomainUpdate[](0); + + vm.startPrank(STRANGER); + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + + s_usdcTokenPool.setDomains(domainUpdates); + } + + function test_InvalidDomain_Revert() public { + bytes32 validCaller = bytes32(uint256(25)); + // Ensure valid domain works + USDCTokenPool.DomainUpdate[] memory domainUpdates = new USDCTokenPool.DomainUpdate[](1); + domainUpdates[0] = USDCTokenPool.DomainUpdate({ + allowedCaller: validCaller, + domainIdentifier: 0, // ensures 0 is valid, as this is eth mainnet + destChainSelector: 45690, + enabled: true + }); + + s_usdcTokenPool.setDomains(domainUpdates); + + // Make update invalid on allowedCaller + domainUpdates[0].allowedCaller = bytes32(0); + vm.expectRevert(abi.encodeWithSelector(USDCTokenPool.InvalidDomain.selector, domainUpdates[0])); + + s_usdcTokenPool.setDomains(domainUpdates); + + // Make valid again + domainUpdates[0].allowedCaller = validCaller; + + // Make invalid on destChainSelector + domainUpdates[0].destChainSelector = 0; + vm.expectRevert(abi.encodeWithSelector(USDCTokenPool.InvalidDomain.selector, domainUpdates[0])); + + s_usdcTokenPool.setDomains(domainUpdates); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.supportsInterface.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.supportsInterface.t.sol new file mode 100644 index 00000000000..05ac5f08136 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.supportsInterface.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IPoolV1} from "../../../../interfaces/IPool.sol"; +import {USDCTokenPoolSetup} from "./USDCTokenPoolSetup.t.sol"; + +import {IERC165} from "../../../../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/introspection/IERC165.sol"; + +contract USDCTokenPool_supportsInterface is USDCTokenPoolSetup { + function test_SupportsInterface_Success() public view { + assertTrue(s_usdcTokenPool.supportsInterface(type(IPoolV1).interfaceId)); + assertTrue(s_usdcTokenPool.supportsInterface(type(IERC165).interfaceId)); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.validateMessage.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.validateMessage.t.sol new file mode 100644 index 00000000000..c53fa6e81b9 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPool.validateMessage.t.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol"; +import {USDCTokenPoolSetup} from "./USDCTokenPoolSetup.t.sol"; + +contract USDCTokenPool__validateMessage is USDCTokenPoolSetup { + function test_Fuzz_ValidateMessage_Success(uint32 sourceDomain, uint64 nonce) public { + vm.pauseGasMetering(); + USDCMessage memory usdcMessage = USDCMessage({ + version: 0, + sourceDomain: sourceDomain, + destinationDomain: DEST_DOMAIN_IDENTIFIER, + nonce: nonce, + sender: SOURCE_CHAIN_TOKEN_SENDER, + recipient: bytes32(uint256(299999)), + destinationCaller: bytes32(uint256(uint160(address(s_usdcTokenPool)))), + messageBody: bytes("") + }); + + bytes memory encodedUsdcMessage = _generateUSDCMessage(usdcMessage); + + vm.resumeGasMetering(); + s_usdcTokenPool.validateMessage( + encodedUsdcMessage, USDCTokenPool.SourceTokenDataPayload({nonce: nonce, sourceDomain: sourceDomain}) + ); + } + + // Reverts + + function test_ValidateInvalidMessage_Revert() public { + USDCMessage memory usdcMessage = USDCMessage({ + version: 0, + sourceDomain: 1553252, + destinationDomain: DEST_DOMAIN_IDENTIFIER, + nonce: 387289284924, + sender: SOURCE_CHAIN_TOKEN_SENDER, + recipient: bytes32(uint256(92398429395823)), + destinationCaller: bytes32(uint256(uint160(address(s_usdcTokenPool)))), + messageBody: bytes("") + }); + + USDCTokenPool.SourceTokenDataPayload memory sourceTokenData = + USDCTokenPool.SourceTokenDataPayload({nonce: usdcMessage.nonce, sourceDomain: usdcMessage.sourceDomain}); + + bytes memory encodedUsdcMessage = _generateUSDCMessage(usdcMessage); + + s_usdcTokenPool.validateMessage(encodedUsdcMessage, sourceTokenData); + + uint32 expectedSourceDomain = usdcMessage.sourceDomain + 1; + + vm.expectRevert( + abi.encodeWithSelector(USDCTokenPool.InvalidSourceDomain.selector, expectedSourceDomain, usdcMessage.sourceDomain) + ); + s_usdcTokenPool.validateMessage( + encodedUsdcMessage, + USDCTokenPool.SourceTokenDataPayload({nonce: usdcMessage.nonce, sourceDomain: expectedSourceDomain}) + ); + + uint64 expectedNonce = usdcMessage.nonce + 1; + + vm.expectRevert(abi.encodeWithSelector(USDCTokenPool.InvalidNonce.selector, expectedNonce, usdcMessage.nonce)); + s_usdcTokenPool.validateMessage( + encodedUsdcMessage, + USDCTokenPool.SourceTokenDataPayload({nonce: expectedNonce, sourceDomain: usdcMessage.sourceDomain}) + ); + + usdcMessage.destinationDomain = DEST_DOMAIN_IDENTIFIER + 1; + vm.expectRevert( + abi.encodeWithSelector( + USDCTokenPool.InvalidDestinationDomain.selector, DEST_DOMAIN_IDENTIFIER, usdcMessage.destinationDomain + ) + ); + + s_usdcTokenPool.validateMessage( + _generateUSDCMessage(usdcMessage), + USDCTokenPool.SourceTokenDataPayload({nonce: usdcMessage.nonce, sourceDomain: usdcMessage.sourceDomain}) + ); + usdcMessage.destinationDomain = DEST_DOMAIN_IDENTIFIER; + + uint32 wrongVersion = usdcMessage.version + 1; + + usdcMessage.version = wrongVersion; + encodedUsdcMessage = _generateUSDCMessage(usdcMessage); + + vm.expectRevert(abi.encodeWithSelector(USDCTokenPool.InvalidMessageVersion.selector, wrongVersion)); + s_usdcTokenPool.validateMessage(encodedUsdcMessage, sourceTokenData); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPoolSetup.t.sol b/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPoolSetup.t.sol new file mode 100644 index 00000000000..614da422bb4 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/pools/USDC/USDCTokenPool/USDCTokenPoolSetup.t.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IBurnMintERC20} from "../../../../../shared/token/ERC20/IBurnMintERC20.sol"; + +import {BurnMintERC677} from "../../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {Router} from "../../../../Router.sol"; +import {TokenPool} from "../../../../pools/TokenPool.sol"; +import {USDCTokenPool} from "../../../../pools/USDC/USDCTokenPool.sol"; +import {BaseTest} from "../../../BaseTest.t.sol"; +import {USDCTokenPoolHelper} from "../../../helpers/USDCTokenPoolHelper.sol"; +import {MockE2EUSDCTransmitter} from "../../../mocks/MockE2EUSDCTransmitter.sol"; +import {MockUSDCTokenMessenger} from "../../../mocks/MockUSDCTokenMessenger.sol"; + +contract USDCTokenPoolSetup is BaseTest { + IBurnMintERC20 internal s_token; + MockUSDCTokenMessenger internal s_mockUSDC; + MockE2EUSDCTransmitter internal s_mockUSDCTransmitter; + uint32 internal constant USDC_DEST_TOKEN_GAS = 150_000; + + struct USDCMessage { + uint32 version; + uint32 sourceDomain; + uint32 destinationDomain; + uint64 nonce; + bytes32 sender; + bytes32 recipient; + bytes32 destinationCaller; + bytes messageBody; + } + + uint32 internal constant SOURCE_DOMAIN_IDENTIFIER = 0x02020202; + uint32 internal constant DEST_DOMAIN_IDENTIFIER = 0; + + bytes32 internal constant SOURCE_CHAIN_TOKEN_SENDER = bytes32(uint256(uint160(0x01111111221))); + address internal constant SOURCE_CHAIN_USDC_POOL = address(0x23789765456789); + address internal constant DEST_CHAIN_USDC_POOL = address(0x987384873458734); + address internal constant DEST_CHAIN_USDC_TOKEN = address(0x23598918358198766); + + address internal s_routerAllowedOnRamp = address(3456); + address internal s_routerAllowedOffRamp = address(234); + Router internal s_router; + + USDCTokenPoolHelper internal s_usdcTokenPool; + USDCTokenPoolHelper internal s_usdcTokenPoolWithAllowList; + address[] internal s_allowedList; + + function setUp() public virtual override { + BaseTest.setUp(); + BurnMintERC677 usdcToken = new BurnMintERC677("LINK", "LNK", 18, 0); + s_token = usdcToken; + deal(address(s_token), OWNER, type(uint256).max); + _setUpRamps(); + + s_mockUSDCTransmitter = new MockE2EUSDCTransmitter(0, DEST_DOMAIN_IDENTIFIER, address(s_token)); + s_mockUSDC = new MockUSDCTokenMessenger(0, address(s_mockUSDCTransmitter)); + + usdcToken.grantMintAndBurnRoles(address(s_mockUSDCTransmitter)); + + s_usdcTokenPool = + new USDCTokenPoolHelper(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); + usdcToken.grantMintAndBurnRoles(address(s_mockUSDC)); + + s_allowedList.push(USER_1); + s_usdcTokenPoolWithAllowList = + new USDCTokenPoolHelper(s_mockUSDC, s_token, s_allowedList, address(s_mockRMN), address(s_router)); + + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](2); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: SOURCE_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(address(s_token)), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + chainUpdates[1] = TokenPool.ChainUpdate({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(DEST_CHAIN_USDC_POOL), + remoteTokenAddress: abi.encode(DEST_CHAIN_USDC_TOKEN), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + + s_usdcTokenPool.applyChainUpdates(chainUpdates); + s_usdcTokenPoolWithAllowList.applyChainUpdates(chainUpdates); + + USDCTokenPool.DomainUpdate[] memory domains = new USDCTokenPool.DomainUpdate[](1); + domains[0] = USDCTokenPool.DomainUpdate({ + destChainSelector: DEST_CHAIN_SELECTOR, + domainIdentifier: 9999, + allowedCaller: keccak256("allowedCaller"), + enabled: true + }); + + s_usdcTokenPool.setDomains(domains); + s_usdcTokenPoolWithAllowList.setDomains(domains); + } + + function _setUpRamps() internal { + s_router = new Router(address(s_token), address(s_mockRMN)); + + Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); + onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_routerAllowedOnRamp}); + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); + address[] memory offRamps = new address[](1); + offRamps[0] = s_routerAllowedOffRamp; + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: offRamps[0]}); + + s_router.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); + } + + function _generateUSDCMessage( + USDCMessage memory usdcMessage + ) internal pure returns (bytes memory) { + return abi.encodePacked( + usdcMessage.version, + usdcMessage.sourceDomain, + usdcMessage.destinationDomain, + usdcMessage.nonce, + usdcMessage.sender, + usdcMessage.recipient, + usdcMessage.destinationCaller, + usdcMessage.messageBody + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/pools/USDCTokenPool.t.sol b/contracts/src/v0.8/ccip/test/pools/USDCTokenPool.t.sol deleted file mode 100644 index d00ef9b8536..00000000000 --- a/contracts/src/v0.8/ccip/test/pools/USDCTokenPool.t.sol +++ /dev/null @@ -1,703 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {IBurnMintERC20} from "../../../shared/token/ERC20/IBurnMintERC20.sol"; -import {IPoolV1} from "../../interfaces/IPool.sol"; -import {ITokenMessenger} from "../../pools/USDC/ITokenMessenger.sol"; - -import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; -import {BurnMintERC677} from "../../../shared/token/ERC677/BurnMintERC677.sol"; -import {Router} from "../../Router.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {Pool} from "../../libraries/Pool.sol"; -import {RateLimiter} from "../../libraries/RateLimiter.sol"; -import {TokenPool} from "../../pools/TokenPool.sol"; -import {USDCTokenPool} from "../../pools/USDC/USDCTokenPool.sol"; -import {BaseTest} from "../BaseTest.t.sol"; -import {USDCTokenPoolHelper} from "../helpers/USDCTokenPoolHelper.sol"; -import {MockE2EUSDCTransmitter} from "../mocks/MockE2EUSDCTransmitter.sol"; -import {MockUSDCTokenMessenger} from "../mocks/MockUSDCTokenMessenger.sol"; - -import {IERC165} from "../../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/introspection/IERC165.sol"; - -contract USDCTokenPoolSetup is BaseTest { - IBurnMintERC20 internal s_token; - MockUSDCTokenMessenger internal s_mockUSDC; - MockE2EUSDCTransmitter internal s_mockUSDCTransmitter; - uint32 internal constant USDC_DEST_TOKEN_GAS = 150_000; - - struct USDCMessage { - uint32 version; - uint32 sourceDomain; - uint32 destinationDomain; - uint64 nonce; - bytes32 sender; - bytes32 recipient; - bytes32 destinationCaller; - bytes messageBody; - } - - uint32 internal constant SOURCE_DOMAIN_IDENTIFIER = 0x02020202; - uint32 internal constant DEST_DOMAIN_IDENTIFIER = 0; - - bytes32 internal constant SOURCE_CHAIN_TOKEN_SENDER = bytes32(uint256(uint160(0x01111111221))); - address internal constant SOURCE_CHAIN_USDC_POOL = address(0x23789765456789); - address internal constant DEST_CHAIN_USDC_POOL = address(0x987384873458734); - address internal constant DEST_CHAIN_USDC_TOKEN = address(0x23598918358198766); - - address internal s_routerAllowedOnRamp = address(3456); - address internal s_routerAllowedOffRamp = address(234); - Router internal s_router; - - USDCTokenPoolHelper internal s_usdcTokenPool; - USDCTokenPoolHelper internal s_usdcTokenPoolWithAllowList; - address[] internal s_allowedList; - - function setUp() public virtual override { - BaseTest.setUp(); - BurnMintERC677 usdcToken = new BurnMintERC677("LINK", "LNK", 18, 0); - s_token = usdcToken; - deal(address(s_token), OWNER, type(uint256).max); - _setUpRamps(); - - s_mockUSDCTransmitter = new MockE2EUSDCTransmitter(0, DEST_DOMAIN_IDENTIFIER, address(s_token)); - s_mockUSDC = new MockUSDCTokenMessenger(0, address(s_mockUSDCTransmitter)); - - usdcToken.grantMintAndBurnRoles(address(s_mockUSDCTransmitter)); - - s_usdcTokenPool = - new USDCTokenPoolHelper(s_mockUSDC, s_token, new address[](0), address(s_mockRMN), address(s_router)); - usdcToken.grantMintAndBurnRoles(address(s_mockUSDC)); - - s_allowedList.push(USER_1); - s_usdcTokenPoolWithAllowList = - new USDCTokenPoolHelper(s_mockUSDC, s_token, s_allowedList, address(s_mockRMN), address(s_router)); - - TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](2); - chainUpdates[0] = TokenPool.ChainUpdate({ - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - remotePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), - remoteTokenAddress: abi.encode(address(s_token)), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - chainUpdates[1] = TokenPool.ChainUpdate({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - remotePoolAddress: abi.encode(DEST_CHAIN_USDC_POOL), - remoteTokenAddress: abi.encode(DEST_CHAIN_USDC_TOKEN), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - - s_usdcTokenPool.applyChainUpdates(chainUpdates); - s_usdcTokenPoolWithAllowList.applyChainUpdates(chainUpdates); - - USDCTokenPool.DomainUpdate[] memory domains = new USDCTokenPool.DomainUpdate[](1); - domains[0] = USDCTokenPool.DomainUpdate({ - destChainSelector: DEST_CHAIN_SELECTOR, - domainIdentifier: 9999, - allowedCaller: keccak256("allowedCaller"), - enabled: true - }); - - s_usdcTokenPool.setDomains(domains); - s_usdcTokenPoolWithAllowList.setDomains(domains); - } - - function _setUpRamps() internal { - s_router = new Router(address(s_token), address(s_mockRMN)); - - Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); - onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: s_routerAllowedOnRamp}); - Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](1); - address[] memory offRamps = new address[](1); - offRamps[0] = s_routerAllowedOffRamp; - offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: offRamps[0]}); - - s_router.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); - } - - function _generateUSDCMessage( - USDCMessage memory usdcMessage - ) internal pure returns (bytes memory) { - return abi.encodePacked( - usdcMessage.version, - usdcMessage.sourceDomain, - usdcMessage.destinationDomain, - usdcMessage.nonce, - usdcMessage.sender, - usdcMessage.recipient, - usdcMessage.destinationCaller, - usdcMessage.messageBody - ); - } -} - -contract USDCTokenPool_lockOrBurn is USDCTokenPoolSetup { - // Base test case, included for PR gas comparisons as fuzz tests are excluded from forge snapshot due to being flaky. - function test_LockOrBurn_Success() public { - bytes32 receiver = bytes32(uint256(uint160(STRANGER))); - uint256 amount = 1; - s_token.transfer(address(s_usdcTokenPool), amount); - vm.startPrank(s_routerAllowedOnRamp); - - USDCTokenPool.Domain memory expectedDomain = s_usdcTokenPool.getDomain(DEST_CHAIN_SELECTOR); - - vm.expectEmit(); - emit RateLimiter.TokensConsumed(amount); - - vm.expectEmit(); - emit ITokenMessenger.DepositForBurn( - s_mockUSDC.s_nonce(), - address(s_token), - amount, - address(s_usdcTokenPool), - receiver, - expectedDomain.domainIdentifier, - s_mockUSDC.DESTINATION_TOKEN_MESSENGER(), - expectedDomain.allowedCaller - ); - - vm.expectEmit(); - emit TokenPool.Burned(s_routerAllowedOnRamp, amount); - - Pool.LockOrBurnOutV1 memory poolReturnDataV1 = s_usdcTokenPool.lockOrBurn( - Pool.LockOrBurnInV1({ - originalSender: OWNER, - receiver: abi.encodePacked(receiver), - amount: amount, - remoteChainSelector: DEST_CHAIN_SELECTOR, - localToken: address(s_token) - }) - ); - - uint64 nonce = abi.decode(poolReturnDataV1.destPoolData, (uint64)); - assertEq(s_mockUSDC.s_nonce() - 1, nonce); - } - - function test_Fuzz_LockOrBurn_Success(bytes32 destinationReceiver, uint256 amount) public { - vm.assume(destinationReceiver != bytes32(0)); - amount = bound(amount, 1, _getOutboundRateLimiterConfig().capacity); - s_token.transfer(address(s_usdcTokenPool), amount); - vm.startPrank(s_routerAllowedOnRamp); - - USDCTokenPool.Domain memory expectedDomain = s_usdcTokenPool.getDomain(DEST_CHAIN_SELECTOR); - - vm.expectEmit(); - emit RateLimiter.TokensConsumed(amount); - - vm.expectEmit(); - emit ITokenMessenger.DepositForBurn( - s_mockUSDC.s_nonce(), - address(s_token), - amount, - address(s_usdcTokenPool), - destinationReceiver, - expectedDomain.domainIdentifier, - s_mockUSDC.DESTINATION_TOKEN_MESSENGER(), - expectedDomain.allowedCaller - ); - - vm.expectEmit(); - emit TokenPool.Burned(s_routerAllowedOnRamp, amount); - - Pool.LockOrBurnOutV1 memory poolReturnDataV1 = s_usdcTokenPool.lockOrBurn( - Pool.LockOrBurnInV1({ - originalSender: OWNER, - receiver: abi.encodePacked(destinationReceiver), - amount: amount, - remoteChainSelector: DEST_CHAIN_SELECTOR, - localToken: address(s_token) - }) - ); - - uint64 nonce = abi.decode(poolReturnDataV1.destPoolData, (uint64)); - assertEq(s_mockUSDC.s_nonce() - 1, nonce); - assertEq(poolReturnDataV1.destTokenAddress, abi.encode(DEST_CHAIN_USDC_TOKEN)); - } - - function test_Fuzz_LockOrBurnWithAllowList_Success(bytes32 destinationReceiver, uint256 amount) public { - vm.assume(destinationReceiver != bytes32(0)); - amount = bound(amount, 1, _getOutboundRateLimiterConfig().capacity); - s_token.transfer(address(s_usdcTokenPoolWithAllowList), amount); - vm.startPrank(s_routerAllowedOnRamp); - - USDCTokenPool.Domain memory expectedDomain = s_usdcTokenPoolWithAllowList.getDomain(DEST_CHAIN_SELECTOR); - - vm.expectEmit(); - emit RateLimiter.TokensConsumed(amount); - vm.expectEmit(); - emit ITokenMessenger.DepositForBurn( - s_mockUSDC.s_nonce(), - address(s_token), - amount, - address(s_usdcTokenPoolWithAllowList), - destinationReceiver, - expectedDomain.domainIdentifier, - s_mockUSDC.DESTINATION_TOKEN_MESSENGER(), - expectedDomain.allowedCaller - ); - vm.expectEmit(); - emit TokenPool.Burned(s_routerAllowedOnRamp, amount); - - Pool.LockOrBurnOutV1 memory poolReturnDataV1 = s_usdcTokenPoolWithAllowList.lockOrBurn( - Pool.LockOrBurnInV1({ - originalSender: s_allowedList[0], - receiver: abi.encodePacked(destinationReceiver), - amount: amount, - remoteChainSelector: DEST_CHAIN_SELECTOR, - localToken: address(s_token) - }) - ); - uint64 nonce = abi.decode(poolReturnDataV1.destPoolData, (uint64)); - assertEq(s_mockUSDC.s_nonce() - 1, nonce); - assertEq(poolReturnDataV1.destTokenAddress, abi.encode(DEST_CHAIN_USDC_TOKEN)); - } - - // Reverts - function test_UnknownDomain_Revert() public { - uint64 wrongDomain = DEST_CHAIN_SELECTOR + 1; - // We need to setup the wrong chainSelector so it reaches the domain check - Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); - onRampUpdates[0] = Router.OnRamp({destChainSelector: wrongDomain, onRamp: s_routerAllowedOnRamp}); - s_router.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), new Router.OffRamp[](0)); - - TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); - chainUpdates[0] = TokenPool.ChainUpdate({ - remoteChainSelector: wrongDomain, - remotePoolAddress: abi.encode(address(1)), - remoteTokenAddress: abi.encode(address(2)), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - - s_usdcTokenPool.applyChainUpdates(chainUpdates); - - uint256 amount = 1000; - vm.startPrank(s_routerAllowedOnRamp); - deal(address(s_token), s_routerAllowedOnRamp, amount); - s_token.approve(address(s_usdcTokenPool), amount); - - vm.expectRevert(abi.encodeWithSelector(USDCTokenPool.UnknownDomain.selector, wrongDomain)); - - s_usdcTokenPool.lockOrBurn( - Pool.LockOrBurnInV1({ - originalSender: OWNER, - receiver: abi.encodePacked(address(0)), - amount: amount, - remoteChainSelector: wrongDomain, - localToken: address(s_token) - }) - ); - } - - function test_CallerIsNotARampOnRouter_Revert() public { - vm.expectRevert(abi.encodeWithSelector(TokenPool.CallerIsNotARampOnRouter.selector, OWNER)); - - s_usdcTokenPool.lockOrBurn( - Pool.LockOrBurnInV1({ - originalSender: OWNER, - receiver: abi.encodePacked(address(0)), - amount: 0, - remoteChainSelector: DEST_CHAIN_SELECTOR, - localToken: address(s_token) - }) - ); - } - - function test_LockOrBurnWithAllowList_Revert() public { - vm.startPrank(s_routerAllowedOnRamp); - - vm.expectRevert(abi.encodeWithSelector(TokenPool.SenderNotAllowed.selector, STRANGER)); - - s_usdcTokenPoolWithAllowList.lockOrBurn( - Pool.LockOrBurnInV1({ - originalSender: STRANGER, - receiver: abi.encodePacked(address(0)), - amount: 1000, - remoteChainSelector: DEST_CHAIN_SELECTOR, - localToken: address(s_token) - }) - ); - } -} - -contract USDCTokenPool_releaseOrMint is USDCTokenPoolSetup { - // From https://github.com/circlefin/evm-cctp-contracts/blob/377c9bd813fb86a42d900ae4003599d82aef635a/src/messages/BurnMessage.sol#L57 - function _formatMessage( - uint32 _version, - bytes32 _burnToken, - bytes32 _mintRecipient, - uint256 _amount, - bytes32 _messageSender - ) internal pure returns (bytes memory) { - return abi.encodePacked(_version, _burnToken, _mintRecipient, _amount, _messageSender); - } - - function test_Fuzz_ReleaseOrMint_Success(address recipient, uint256 amount) public { - vm.assume(recipient != address(0) && recipient != address(s_token)); - amount = bound(amount, 0, _getInboundRateLimiterConfig().capacity); - - USDCMessage memory usdcMessage = USDCMessage({ - version: 0, - sourceDomain: SOURCE_DOMAIN_IDENTIFIER, - destinationDomain: DEST_DOMAIN_IDENTIFIER, - nonce: 0x060606060606, - sender: SOURCE_CHAIN_TOKEN_SENDER, - recipient: bytes32(uint256(uint160(recipient))), - destinationCaller: bytes32(uint256(uint160(address(s_usdcTokenPool)))), - messageBody: _formatMessage( - 0, - bytes32(uint256(uint160(address(s_token)))), - bytes32(uint256(uint160(recipient))), - amount, - bytes32(uint256(uint160(OWNER))) - ) - }); - - bytes memory message = _generateUSDCMessage(usdcMessage); - bytes memory attestation = bytes("attestation bytes"); - - Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ - sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), - destTokenAddress: abi.encode(address(s_usdcTokenPool)), - extraData: abi.encode( - USDCTokenPool.SourceTokenDataPayload({nonce: usdcMessage.nonce, sourceDomain: SOURCE_DOMAIN_IDENTIFIER}) - ), - destGasAmount: USDC_DEST_TOKEN_GAS - }); - - bytes memory offchainTokenData = - abi.encode(USDCTokenPool.MessageAndAttestation({message: message, attestation: attestation})); - - // The mocked receiver does not release the token to the pool, so we manually do it here - deal(address(s_token), address(s_usdcTokenPool), amount); - - vm.expectEmit(); - emit TokenPool.Minted(s_routerAllowedOffRamp, recipient, amount); - - vm.expectCall( - address(s_mockUSDCTransmitter), - abi.encodeWithSelector(MockE2EUSDCTransmitter.receiveMessage.selector, message, attestation) - ); - - vm.startPrank(s_routerAllowedOffRamp); - s_usdcTokenPool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: abi.encode(OWNER), - receiver: recipient, - amount: amount, - localToken: address(s_token), - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - sourcePoolAddress: sourceTokenData.sourcePoolAddress, - sourcePoolData: sourceTokenData.extraData, - offchainTokenData: offchainTokenData - }) - ); - } - - // https://etherscan.io/tx/0xac9f501fe0b76df1f07a22e1db30929fd12524bc7068d74012dff948632f0883 - function test_ReleaseOrMintRealTx_Success() public { - bytes memory encodedUsdcMessage = - hex"000000000000000300000000000000000000127a00000000000000000000000019330d10d9cc8751218eaf51e8885d058642e08a000000000000000000000000bd3fa81b58ba92a82136038b25adec7066af3155000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000004af08f56978be7dce2d1be3c65c005b41e79401c000000000000000000000000000000000000000000000000000000002057ff7a0000000000000000000000003a23f943181408eac424116af7b7790c94cb97a50000000000000000000000000000000000000000000000000000000000000000000000000000008274119237535fd659626b090f87e365ff89ebc7096bb32e8b0e85f155626b73ae7c4bb2485c184b7cc3cf7909045487890b104efb62ae74a73e32901bdcec91df1bb9ee08ccb014fcbcfe77b74d1263fd4e0b0e8de05d6c9a5913554364abfd5ea768b222f50c715908183905d74044bb2b97527c7e70ae7983c443a603557cac3b1c000000000000000000000000000000000000000000000000000000000000"; - bytes memory attestation = bytes("attestation bytes"); - - uint32 nonce = 4730; - uint32 sourceDomain = 3; - uint256 amount = 100; - - Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ - sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), - destTokenAddress: abi.encode(address(s_usdcTokenPool)), - extraData: abi.encode(USDCTokenPool.SourceTokenDataPayload({nonce: nonce, sourceDomain: sourceDomain})), - destGasAmount: USDC_DEST_TOKEN_GAS - }); - - // The mocked receiver does not release the token to the pool, so we manually do it here - deal(address(s_token), address(s_usdcTokenPool), amount); - - bytes memory offchainTokenData = - abi.encode(USDCTokenPool.MessageAndAttestation({message: encodedUsdcMessage, attestation: attestation})); - - vm.expectCall( - address(s_mockUSDCTransmitter), - abi.encodeWithSelector(MockE2EUSDCTransmitter.receiveMessage.selector, encodedUsdcMessage, attestation) - ); - - vm.startPrank(s_routerAllowedOffRamp); - s_usdcTokenPool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: abi.encode(OWNER), - receiver: OWNER, - amount: amount, - localToken: address(s_token), - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - sourcePoolAddress: sourceTokenData.sourcePoolAddress, - sourcePoolData: sourceTokenData.extraData, - offchainTokenData: offchainTokenData - }) - ); - } - - // Reverts - function test_UnlockingUSDCFailed_Revert() public { - vm.startPrank(s_routerAllowedOffRamp); - s_mockUSDCTransmitter.setShouldSucceed(false); - - uint256 amount = 13255235235; - - USDCMessage memory usdcMessage = USDCMessage({ - version: 0, - sourceDomain: SOURCE_DOMAIN_IDENTIFIER, - destinationDomain: DEST_DOMAIN_IDENTIFIER, - nonce: 0x060606060606, - sender: SOURCE_CHAIN_TOKEN_SENDER, - recipient: bytes32(uint256(uint160(address(s_mockUSDC)))), - destinationCaller: bytes32(uint256(uint160(address(s_usdcTokenPool)))), - messageBody: _formatMessage( - 0, - bytes32(uint256(uint160(address(s_token)))), - bytes32(uint256(uint160(OWNER))), - amount, - bytes32(uint256(uint160(OWNER))) - ) - }); - - Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ - sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), - destTokenAddress: abi.encode(address(s_usdcTokenPool)), - extraData: abi.encode( - USDCTokenPool.SourceTokenDataPayload({nonce: usdcMessage.nonce, sourceDomain: SOURCE_DOMAIN_IDENTIFIER}) - ), - destGasAmount: USDC_DEST_TOKEN_GAS - }); - - bytes memory offchainTokenData = abi.encode( - USDCTokenPool.MessageAndAttestation({message: _generateUSDCMessage(usdcMessage), attestation: bytes("")}) - ); - - vm.expectRevert(USDCTokenPool.UnlockingUSDCFailed.selector); - - s_usdcTokenPool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: abi.encode(OWNER), - receiver: OWNER, - amount: amount, - localToken: address(s_token), - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - sourcePoolAddress: sourceTokenData.sourcePoolAddress, - sourcePoolData: sourceTokenData.extraData, - offchainTokenData: offchainTokenData - }) - ); - } - - function test_TokenMaxCapacityExceeded_Revert() public { - uint256 capacity = _getInboundRateLimiterConfig().capacity; - uint256 amount = 10 * capacity; - address recipient = address(1); - vm.startPrank(s_routerAllowedOffRamp); - - Internal.SourceTokenData memory sourceTokenData = Internal.SourceTokenData({ - sourcePoolAddress: abi.encode(SOURCE_CHAIN_USDC_POOL), - destTokenAddress: abi.encode(address(s_usdcTokenPool)), - extraData: abi.encode(USDCTokenPool.SourceTokenDataPayload({nonce: 1, sourceDomain: SOURCE_DOMAIN_IDENTIFIER})), - destGasAmount: USDC_DEST_TOKEN_GAS - }); - - bytes memory offchainTokenData = - abi.encode(USDCTokenPool.MessageAndAttestation({message: bytes(""), attestation: bytes("")})); - - vm.expectRevert( - abi.encodeWithSelector(RateLimiter.TokenMaxCapacityExceeded.selector, capacity, amount, address(s_token)) - ); - - s_usdcTokenPool.releaseOrMint( - Pool.ReleaseOrMintInV1({ - originalSender: abi.encode(OWNER), - receiver: recipient, - amount: amount, - localToken: address(s_token), - remoteChainSelector: SOURCE_CHAIN_SELECTOR, - sourcePoolAddress: sourceTokenData.sourcePoolAddress, - sourcePoolData: sourceTokenData.extraData, - offchainTokenData: offchainTokenData - }) - ); - } -} - -contract USDCTokenPool_supportsInterface is USDCTokenPoolSetup { - function test_SupportsInterface_Success() public view { - assertTrue(s_usdcTokenPool.supportsInterface(type(IPoolV1).interfaceId)); - assertTrue(s_usdcTokenPool.supportsInterface(type(IERC165).interfaceId)); - } -} - -contract USDCTokenPool_setDomains is USDCTokenPoolSetup { - mapping(uint64 destChainSelector => USDCTokenPool.Domain domain) private s_chainToDomain; - - // Setting lower fuzz run as 256 runs was causing differing gas results in snapshot. - /// forge-config: default.fuzz.runs = 32 - /// forge-config: ccip.fuzz.runs = 32 - function test_Fuzz_SetDomains_Success( - bytes32[5] calldata allowedCallers, - uint32[5] calldata domainIdentifiers, - uint64[5] calldata destChainSelectors - ) public { - uint256 numberOfDomains = allowedCallers.length; - USDCTokenPool.DomainUpdate[] memory domainUpdates = new USDCTokenPool.DomainUpdate[](numberOfDomains); - for (uint256 i = 0; i < numberOfDomains; ++i) { - vm.assume(allowedCallers[i] != bytes32(0) && domainIdentifiers[i] != 0 && destChainSelectors[i] != 0); - - domainUpdates[i] = USDCTokenPool.DomainUpdate({ - allowedCaller: allowedCallers[i], - domainIdentifier: domainIdentifiers[i], - destChainSelector: destChainSelectors[i], - enabled: true - }); - - s_chainToDomain[destChainSelectors[i]] = - USDCTokenPool.Domain({domainIdentifier: domainIdentifiers[i], allowedCaller: allowedCallers[i], enabled: true}); - } - - vm.expectEmit(); - emit USDCTokenPool.DomainsSet(domainUpdates); - - s_usdcTokenPool.setDomains(domainUpdates); - - for (uint256 i = 0; i < numberOfDomains; ++i) { - USDCTokenPool.Domain memory expected = s_chainToDomain[destChainSelectors[i]]; - USDCTokenPool.Domain memory got = s_usdcTokenPool.getDomain(destChainSelectors[i]); - assertEq(got.allowedCaller, expected.allowedCaller); - assertEq(got.domainIdentifier, expected.domainIdentifier); - } - } - - // Reverts - - function test_OnlyOwner_Revert() public { - USDCTokenPool.DomainUpdate[] memory domainUpdates = new USDCTokenPool.DomainUpdate[](0); - - vm.startPrank(STRANGER); - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - - s_usdcTokenPool.setDomains(domainUpdates); - } - - function test_InvalidDomain_Revert() public { - bytes32 validCaller = bytes32(uint256(25)); - // Ensure valid domain works - USDCTokenPool.DomainUpdate[] memory domainUpdates = new USDCTokenPool.DomainUpdate[](1); - domainUpdates[0] = USDCTokenPool.DomainUpdate({ - allowedCaller: validCaller, - domainIdentifier: 0, // ensures 0 is valid, as this is eth mainnet - destChainSelector: 45690, - enabled: true - }); - - s_usdcTokenPool.setDomains(domainUpdates); - - // Make update invalid on allowedCaller - domainUpdates[0].allowedCaller = bytes32(0); - vm.expectRevert(abi.encodeWithSelector(USDCTokenPool.InvalidDomain.selector, domainUpdates[0])); - - s_usdcTokenPool.setDomains(domainUpdates); - - // Make valid again - domainUpdates[0].allowedCaller = validCaller; - - // Make invalid on destChainSelector - domainUpdates[0].destChainSelector = 0; - vm.expectRevert(abi.encodeWithSelector(USDCTokenPool.InvalidDomain.selector, domainUpdates[0])); - - s_usdcTokenPool.setDomains(domainUpdates); - } -} - -contract USDCTokenPool__validateMessage is USDCTokenPoolSetup { - function test_Fuzz_ValidateMessage_Success(uint32 sourceDomain, uint64 nonce) public { - vm.pauseGasMetering(); - USDCMessage memory usdcMessage = USDCMessage({ - version: 0, - sourceDomain: sourceDomain, - destinationDomain: DEST_DOMAIN_IDENTIFIER, - nonce: nonce, - sender: SOURCE_CHAIN_TOKEN_SENDER, - recipient: bytes32(uint256(299999)), - destinationCaller: bytes32(uint256(uint160(address(s_usdcTokenPool)))), - messageBody: bytes("") - }); - - bytes memory encodedUsdcMessage = _generateUSDCMessage(usdcMessage); - - vm.resumeGasMetering(); - s_usdcTokenPool.validateMessage( - encodedUsdcMessage, USDCTokenPool.SourceTokenDataPayload({nonce: nonce, sourceDomain: sourceDomain}) - ); - } - - // Reverts - - function test_ValidateInvalidMessage_Revert() public { - USDCMessage memory usdcMessage = USDCMessage({ - version: 0, - sourceDomain: 1553252, - destinationDomain: DEST_DOMAIN_IDENTIFIER, - nonce: 387289284924, - sender: SOURCE_CHAIN_TOKEN_SENDER, - recipient: bytes32(uint256(92398429395823)), - destinationCaller: bytes32(uint256(uint160(address(s_usdcTokenPool)))), - messageBody: bytes("") - }); - - USDCTokenPool.SourceTokenDataPayload memory sourceTokenData = - USDCTokenPool.SourceTokenDataPayload({nonce: usdcMessage.nonce, sourceDomain: usdcMessage.sourceDomain}); - - bytes memory encodedUsdcMessage = _generateUSDCMessage(usdcMessage); - - s_usdcTokenPool.validateMessage(encodedUsdcMessage, sourceTokenData); - - uint32 expectedSourceDomain = usdcMessage.sourceDomain + 1; - - vm.expectRevert( - abi.encodeWithSelector(USDCTokenPool.InvalidSourceDomain.selector, expectedSourceDomain, usdcMessage.sourceDomain) - ); - s_usdcTokenPool.validateMessage( - encodedUsdcMessage, - USDCTokenPool.SourceTokenDataPayload({nonce: usdcMessage.nonce, sourceDomain: expectedSourceDomain}) - ); - - uint64 expectedNonce = usdcMessage.nonce + 1; - - vm.expectRevert(abi.encodeWithSelector(USDCTokenPool.InvalidNonce.selector, expectedNonce, usdcMessage.nonce)); - s_usdcTokenPool.validateMessage( - encodedUsdcMessage, - USDCTokenPool.SourceTokenDataPayload({nonce: expectedNonce, sourceDomain: usdcMessage.sourceDomain}) - ); - - usdcMessage.destinationDomain = DEST_DOMAIN_IDENTIFIER + 1; - vm.expectRevert( - abi.encodeWithSelector( - USDCTokenPool.InvalidDestinationDomain.selector, DEST_DOMAIN_IDENTIFIER, usdcMessage.destinationDomain - ) - ); - - s_usdcTokenPool.validateMessage( - _generateUSDCMessage(usdcMessage), - USDCTokenPool.SourceTokenDataPayload({nonce: usdcMessage.nonce, sourceDomain: usdcMessage.sourceDomain}) - ); - usdcMessage.destinationDomain = DEST_DOMAIN_IDENTIFIER; - - uint32 wrongVersion = usdcMessage.version + 1; - - usdcMessage.version = wrongVersion; - encodedUsdcMessage = _generateUSDCMessage(usdcMessage); - - vm.expectRevert(abi.encodeWithSelector(USDCTokenPool.InvalidMessageVersion.selector, wrongVersion)); - s_usdcTokenPool.validateMessage(encodedUsdcMessage, sourceTokenData); - } -} From 5528a21eda5c0b87ba702f40b5c4c9f4d5716204 Mon Sep 17 00:00:00 2001 From: krehermann <16602512+krehermann@users.noreply.github.com> Date: Tue, 5 Nov 2024 12:23:14 -0700 Subject: [PATCH 08/11] Ks/update nodes cs cleanup (#15080) * update capability view defintion * test wip * wip, better types for view * finish type refactoring * cleanup * more denormalized form * code cleanup * simplify helpers * clean update node changeset to add helpers * take new code * unify capability mutation request --- .../changeset/append_node_capbilities.go | 47 ++--------------- .../changeset/update_node_capabilities.go | 51 +++++++++--------- deployment/keystone/changeset/view.go | 2 +- deployment/keystone/deploy.go | 8 +-- deployment/keystone/deploy_test.go | 2 +- deployment/keystone/state.go | 10 ++-- deployment/keystone/types.go | 52 +++++++++++++------ 7 files changed, 75 insertions(+), 97 deletions(-) diff --git a/deployment/keystone/changeset/append_node_capbilities.go b/deployment/keystone/changeset/append_node_capbilities.go index 20988825110..0cee9b442c8 100644 --- a/deployment/keystone/changeset/append_node_capbilities.go +++ b/deployment/keystone/changeset/append_node_capbilities.go @@ -3,10 +3,6 @@ package changeset import ( "fmt" - chainsel "github.com/smartcontractkit/chain-selectors" - kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" - "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" - "github.com/smartcontractkit/chainlink/deployment" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" @@ -14,45 +10,10 @@ import ( var _ deployment.ChangeSet = AppendNodeCapabilities -type AppendNodeCapabilitiesRequest struct { - AddressBook deployment.AddressBook - RegistryChainSel uint64 - - P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc -} - -func (req *AppendNodeCapabilitiesRequest) Validate() error { - if len(req.P2pToCapabilities) == 0 { - return fmt.Errorf("p2pToCapabilities is empty") - } - if len(req.NopToNodes) == 0 { - return fmt.Errorf("nopToNodes is empty") - } - if req.AddressBook == nil { - return fmt.Errorf("registry is nil") - } - _, exists := chainsel.ChainBySelector(req.RegistryChainSel) - if !exists { - return fmt.Errorf("registry chain selector %d does not exist", req.RegistryChainSel) - } - - return nil -} - -/* -// AppendNodeCapabilibity adds any new capabilities to the registry, merges the new capabilities with the existing capabilities -// of the node, and updates the nodes in the registry host the union of the new and existing capabilities. -func AppendNodeCapabilities(lggr logger.Logger, req *AppendNodeCapabilitiesRequest) (deployment.ChangesetOutput, error) { - _, err := appendNodeCapabilitiesImpl(lggr, req) - if err != nil { - return deployment.ChangesetOutput{}, err - } - return deployment.ChangesetOutput{}, nil -} -*/ +// AppendNodeCapabilitiesRequest is a request to add capabilities to the existing capabilities of nodes in the registry +type AppendNodeCapabilitiesRequest = MutateNodeCapabilitiesRequest -// AppendNodeCapabilibity adds any new capabilities to the registry, merges the new capabilities with the existing capabilities +// AppendNodeCapabilities adds any new capabilities to the registry, merges the new capabilities with the existing capabilities // of the node, and updates the nodes in the registry host the union of the new and existing capabilities. func AppendNodeCapabilities(env deployment.Environment, config any) (deployment.ChangesetOutput, error) { req, ok := config.(*AppendNodeCapabilitiesRequest) @@ -79,7 +40,7 @@ func (req *AppendNodeCapabilitiesRequest) convert(e deployment.Environment) (*in if !ok { return nil, fmt.Errorf("registry chain selector %d does not exist in environment", req.RegistryChainSel) } - contracts, err := kslib.GetContractSets(&kslib.GetContractSetsRequest{ + contracts, err := kslib.GetContractSets(e.Logger, &kslib.GetContractSetsRequest{ Chains: map[uint64]deployment.Chain{req.RegistryChainSel: registryChain}, AddressBook: req.AddressBook, }) diff --git a/deployment/keystone/changeset/update_node_capabilities.go b/deployment/keystone/changeset/update_node_capabilities.go index 462a527273d..422411e9061 100644 --- a/deployment/keystone/changeset/update_node_capabilities.go +++ b/deployment/keystone/changeset/update_node_capabilities.go @@ -6,6 +6,7 @@ import ( chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" @@ -17,7 +18,23 @@ var _ deployment.ChangeSet = UpdateNodeCapabilities type P2PSignerEnc = internal.P2PSignerEnc -type UpdateNodeCapabilitiesRequest struct { +func NewP2PSignerEnc(n *models.Node, registryChainSel uint64) (*P2PSignerEnc, error) { + p2p, signer, enc, err := kslib.ExtractKeys(n, registryChainSel) + if err != nil { + return nil, fmt.Errorf("failed to extract keys: %w", err) + } + return &P2PSignerEnc{ + Signer: signer, + P2PKey: p2p, + EncryptionPublicKey: enc, + }, nil +} + +// UpdateNodeCapabilitiesRequest is a request to set the capabilities of nodes in the registry +type UpdateNodeCapabilitiesRequest = MutateNodeCapabilitiesRequest + +// MutateNodeCapabilitiesRequest is a request to change the capabilities of nodes in the registry +type MutateNodeCapabilitiesRequest struct { AddressBook deployment.AddressBook RegistryChainSel uint64 @@ -25,7 +42,7 @@ type UpdateNodeCapabilitiesRequest struct { NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc } -func (req *UpdateNodeCapabilitiesRequest) Validate() error { +func (req *MutateNodeCapabilitiesRequest) Validate() error { if req.AddressBook == nil { return fmt.Errorf("address book is nil") } @@ -39,32 +56,11 @@ func (req *UpdateNodeCapabilitiesRequest) Validate() error { if !exists { return fmt.Errorf("registry chain selector %d does not exist", req.RegistryChainSel) } - return nil -} - -type UpdateNodeCapabilitiesImplRequest struct { - Chain deployment.Chain - Registry *kcr.CapabilitiesRegistry - - P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc -} - -func (req *UpdateNodeCapabilitiesImplRequest) Validate() error { - if len(req.P2pToCapabilities) == 0 { - return fmt.Errorf("p2pToCapabilities is empty") - } - if len(req.NopToNodes) == 0 { - return fmt.Errorf("nopToNodes is empty") - } - if req.Registry == nil { - return fmt.Errorf("registry is nil") - } return nil } -func (req *UpdateNodeCapabilitiesRequest) updateNodeCapabilitiesImplRequest(e deployment.Environment) (*internal.UpdateNodeCapabilitiesImplRequest, error) { +func (req *MutateNodeCapabilitiesRequest) updateNodeCapabilitiesImplRequest(e deployment.Environment) (*internal.UpdateNodeCapabilitiesImplRequest, error) { if err := req.Validate(); err != nil { return nil, fmt.Errorf("failed to validate UpdateNodeCapabilitiesRequest: %w", err) } @@ -72,7 +68,7 @@ func (req *UpdateNodeCapabilitiesRequest) updateNodeCapabilitiesImplRequest(e de if !ok { return nil, fmt.Errorf("registry chain selector %d does not exist in environment", req.RegistryChainSel) } - contracts, err := kslib.GetContractSets(&kslib.GetContractSetsRequest{ + contracts, err := kslib.GetContractSets(e.Logger, &kslib.GetContractSetsRequest{ Chains: map[uint64]deployment.Chain{req.RegistryChainSel: registryChain}, AddressBook: req.AddressBook, }) @@ -83,6 +79,7 @@ func (req *UpdateNodeCapabilitiesRequest) updateNodeCapabilitiesImplRequest(e de if registry == nil { return nil, fmt.Errorf("capabilities registry not found for chain %d", req.RegistryChainSel) } + return &internal.UpdateNodeCapabilitiesImplRequest{ Chain: registryChain, Registry: registry, @@ -93,9 +90,9 @@ func (req *UpdateNodeCapabilitiesRequest) updateNodeCapabilitiesImplRequest(e de // UpdateNodeCapabilities updates the capabilities of nodes in the registry func UpdateNodeCapabilities(env deployment.Environment, config any) (deployment.ChangesetOutput, error) { - req, ok := config.(*UpdateNodeCapabilitiesRequest) + req, ok := config.(*MutateNodeCapabilitiesRequest) if !ok { - return deployment.ChangesetOutput{}, fmt.Errorf("invalid config type. want %T, got %T", &UpdateNodeCapabilitiesRequest{}, config) + return deployment.ChangesetOutput{}, fmt.Errorf("invalid config type. want %T, got %T", &MutateNodeCapabilitiesRequest{}, config) } c, err := req.updateNodeCapabilitiesImplRequest(env) if err != nil { diff --git a/deployment/keystone/changeset/view.go b/deployment/keystone/changeset/view.go index 3f8d3c256f5..cab4ca25ae7 100644 --- a/deployment/keystone/changeset/view.go +++ b/deployment/keystone/changeset/view.go @@ -14,7 +14,7 @@ import ( var _ deployment.ViewState = ViewKeystone func ViewKeystone(e deployment.Environment) (json.Marshaler, error) { - state, err := keystone.GetContractSets(&keystone.GetContractSetsRequest{ + state, err := keystone.GetContractSets(e.Logger, &keystone.GetContractSetsRequest{ Chains: e.Chains, AddressBook: e.ExistingAddresses, }) diff --git a/deployment/keystone/deploy.go b/deployment/keystone/deploy.go index d6a1b2bf157..eec648979f4 100644 --- a/deployment/keystone/deploy.go +++ b/deployment/keystone/deploy.go @@ -145,7 +145,7 @@ func ConfigureRegistry(ctx context.Context, lggr logger.Logger, req ConfigureCon return nil, fmt.Errorf("chain %d not found in environment", req.RegistryChainSel) } - contractSetsResp, err := GetContractSets(&GetContractSetsRequest{ + contractSetsResp, err := GetContractSets(req.Env.Logger, &GetContractSetsRequest{ Chains: req.Env.Chains, AddressBook: addrBook, }) @@ -244,7 +244,7 @@ func ConfigureRegistry(ctx context.Context, lggr logger.Logger, req ConfigureCon // ConfigureForwardContracts configures the forwarder contracts on all chains for the given DONS // the address book is required to contain the an address of the deployed forwarder contract for every chain in the environment func ConfigureForwardContracts(env *deployment.Environment, dons []RegisteredDon, addrBook deployment.AddressBook) error { - contractSetsResp, err := GetContractSets(&GetContractSetsRequest{ + contractSetsResp, err := GetContractSets(env.Logger, &GetContractSetsRequest{ Chains: env.Chains, AddressBook: addrBook, }) @@ -279,7 +279,7 @@ func ConfigureOCR3Contract(env *deployment.Environment, chainSel uint64, dons [] return fmt.Errorf("chain %d not found in environment", chainSel) } - contractSetsResp, err := GetContractSets(&GetContractSetsRequest{ + contractSetsResp, err := GetContractSets(env.Logger, &GetContractSetsRequest{ Chains: env.Chains, AddressBook: addrBook, }) @@ -319,7 +319,7 @@ func ConfigureOCR3ContractFromCLO(env *deployment.Environment, chainSel uint64, if !ok { return fmt.Errorf("chain %d not found in environment", chainSel) } - contractSetsResp, err := GetContractSets(&GetContractSetsRequest{ + contractSetsResp, err := GetContractSets(env.Logger, &GetContractSetsRequest{ Chains: env.Chains, AddressBook: addrBook, }) diff --git a/deployment/keystone/deploy_test.go b/deployment/keystone/deploy_test.go index 7ca8a98498b..211e273c38e 100644 --- a/deployment/keystone/deploy_test.go +++ b/deployment/keystone/deploy_test.go @@ -112,7 +112,7 @@ func TestDeploy(t *testing.T) { AddressBook: ad, } - contractSetsResp, err := keystone.GetContractSets(req) + contractSetsResp, err := keystone.GetContractSets(lggr, req) require.NoError(t, err) require.Len(t, contractSetsResp.ContractSets, len(env.Chains)) // check the registry diff --git a/deployment/keystone/state.go b/deployment/keystone/state.go index e226d3b4b91..33200a40e02 100644 --- a/deployment/keystone/state.go +++ b/deployment/keystone/state.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/deployment" common_v1_0 "github.com/smartcontractkit/chainlink/deployment/common/view/v1_0" "github.com/smartcontractkit/chainlink/deployment/keystone/view" @@ -40,7 +41,7 @@ func (cs ContractSet) View() (view.KeystoneChainView, error) { return out, nil } -func GetContractSets(req *GetContractSetsRequest) (*GetContractSetsResponse, error) { +func GetContractSets(lggr logger.Logger, req *GetContractSetsRequest) (*GetContractSetsResponse, error) { resp := &GetContractSetsResponse{ ContractSets: make(map[uint64]ContractSet), } @@ -49,7 +50,7 @@ func GetContractSets(req *GetContractSetsRequest) (*GetContractSetsResponse, err if err != nil { return nil, fmt.Errorf("failed to get addresses for chain %d: %w", id, err) } - cs, err := loadContractSet(chain, addrs) + cs, err := loadContractSet(lggr, chain, addrs) if err != nil { return nil, fmt.Errorf("failed to load contract set for chain %d: %w", id, err) } @@ -58,7 +59,7 @@ func GetContractSets(req *GetContractSetsRequest) (*GetContractSetsResponse, err return resp, nil } -func loadContractSet(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*ContractSet, error) { +func loadContractSet(lggr logger.Logger, chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*ContractSet, error) { var out ContractSet for addr, tv := range addresses { @@ -83,7 +84,8 @@ func loadContractSet(chain deployment.Chain, addresses map[string]deployment.Typ } out.OCR3 = c default: - return nil, fmt.Errorf("unknown contract type %s", tv.Type) + lggr.Warnw("unknown contract type", "type", tv.Type) + // ignore unknown contract types } } return &out, nil diff --git a/deployment/keystone/types.go b/deployment/keystone/types.go index 0d2a1b6f58e..18967ccf445 100644 --- a/deployment/keystone/types.go +++ b/deployment/keystone/types.go @@ -100,6 +100,7 @@ func (o *ocr2Node) toNodeKeys() NodeKeys { AptosOnchainPublicKey: aptosOnchainPublicKey, } } + func newOcr2NodeFromClo(n *models.Node, registryChainSel uint64) (*ocr2Node, error) { if n.PublicKey == nil { return nil, errors.New("no public key") @@ -123,6 +124,14 @@ func newOcr2NodeFromClo(n *models.Node, registryChainSel uint64) (*ocr2Node, err return newOcr2Node(n.ID, cfgs, *n.PublicKey) } +func ExtractKeys(n *models.Node, registerChainSel uint64) (p2p p2pkey.PeerID, signer [32]byte, encPubKey [32]byte, err error) { + orc2n, err := newOcr2NodeFromClo(n, registerChainSel) + if err != nil { + return p2p, signer, encPubKey, fmt.Errorf("failed to create ocr2 node for node %s: %w", n.ID, err) + } + return orc2n.P2PKey, orc2n.Signer, orc2n.EncryptionPublicKey, nil +} + func newOcr2Node(id string, ccfgs map[chaintype.ChainType]*v1.ChainConfig, csaPubKey string) (*ocr2Node, error) { if ccfgs == nil { return nil, errors.New("nil ocr2config") @@ -202,32 +211,41 @@ type DonCapabilities struct { // map the node id to the NOP func (dc DonCapabilities) nodeIdToNop(cs uint64) (map[string]capabilities_registry.CapabilitiesRegistryNodeOperator, error) { - cid, err := chainsel.ChainIdFromSelector(cs) - if err != nil { - return nil, fmt.Errorf("failed to get chain id from selector %d: %w", cs, err) - } - cidStr := strconv.FormatUint(cid, 10) out := make(map[string]capabilities_registry.CapabilitiesRegistryNodeOperator) for _, nop := range dc.Nops { for _, node := range nop.Nodes { - found := false - for _, chain := range node.ChainConfigs { - if chain.Network.ChainID == cidStr { - found = true - out[node.ID] = capabilities_registry.CapabilitiesRegistryNodeOperator{ - Name: nop.Name, - Admin: adminAddr(chain.AdminAddress), - } - } - } - if !found { - return nil, fmt.Errorf("node '%s' %s does not support chain %d", node.Name, node.ID, cid) + a, err := AdminAddress(node, cs) + if err != nil { + return nil, fmt.Errorf("failed to get admin address for node %s: %w", node.ID, err) } + out[node.ID] = NodeOperator(dc.Name, a) + } } return out, nil } +func NodeOperator(name string, adminAddress string) capabilities_registry.CapabilitiesRegistryNodeOperator { + return capabilities_registry.CapabilitiesRegistryNodeOperator{ + Name: name, + Admin: adminAddr(adminAddress), + } +} + +func AdminAddress(n *models.Node, chainSel uint64) (string, error) { + cid, err := chainsel.ChainIdFromSelector(chainSel) + if err != nil { + return "", fmt.Errorf("failed to get chain id from selector %d: %w", chainSel, err) + } + cidStr := strconv.FormatUint(cid, 10) + for _, chain := range n.ChainConfigs { + if chain.Network.ChainID == cidStr { + return chain.AdminAddress, nil + } + } + return "", fmt.Errorf("no chain config for chain %d", cid) +} + // helpers to maintain compatibility with the existing registration functions // nodesToNops converts a list of DonCapabilities to a map of node id to NOP func nodesToNops(dons []DonCapabilities, chainSel uint64) (map[string]capabilities_registry.CapabilitiesRegistryNodeOperator, error) { From 58995a5eaf42feeb553a98b65002060ccac41edc Mon Sep 17 00:00:00 2001 From: Rens Rooimans Date: Wed, 6 Nov 2024 10:56:46 +0100 Subject: [PATCH 09/11] CCIP-4115 split tests for onRamp and FeeQuoter (#15116) * Clean up offRamp tests * split onRamp * cleanup onRamp * split feeQuoter * cleanup feeQuoter * cleanup misc * lint --- contracts/.changeset/poor-ears-hear.md | 5 + contracts/gas-snapshots/ccip.gas-snapshot | 324 +-- contracts/src/v0.8/ccip/test/BaseTest.t.sol | 27 - .../src/v0.8/ccip/test/NonceManager.t.sol | 14 +- contracts/src/v0.8/ccip/test/TokenSetup.t.sol | 13 - .../test/applications/DefensiveExample.t.sol | 2 +- .../test/applications/ImmutableExample.t.sol | 2 +- .../ccip/test/applications/PingPongDemo.t.sol | 2 +- .../onRamp/OnRampTokenPoolReentrancy.t.sol | 2 +- .../src/v0.8/ccip/test/e2e/End2End.t.sol | 32 +- ...eeQuoter.applyDestChainConfigUpdates.t.sol | 124 + .../FeeQuoter.applyFeeTokensUpdates.t.sol | 58 + ...plyPremiumMultiplierWeiPerEthUpdates.t.sol | 93 + ...r.applyTokenTransferFeeConfigUpdates.t.sol | 191 ++ .../feeQuoter/FeeQuoter.constructor.t.sol | 148 + .../FeeQuoter.convertTokenAmount.t.sol | 60 + .../FeeQuoter.getDataAvailabilityCost.t.sol | 121 + .../FeeQuoter.getTokenAndGasPrices.t.sol | 70 + .../feeQuoter/FeeQuoter.getTokenPrice.t.sol | 53 + .../feeQuoter/FeeQuoter.getTokenPrices.t.sol | 23 + .../FeeQuoter.getTokenTransferCost.t.sol | 271 ++ .../feeQuoter/FeeQuoter.getValidatedFee.t.sol | 263 ++ .../FeeQuoter.getValidatedTokenPrice.t.sol | 185 ++ .../test/feeQuoter/FeeQuoter.onReport.t.sol | 147 + ...FeeQuoter.parseEVMExtraArgsFromBytes.t.sol | 78 + .../FeeQuoter.processMessageArgs.t.sol | 319 +++ .../v0.8/ccip/test/feeQuoter/FeeQuoter.t.sol | 2378 ----------------- .../feeQuoter/FeeQuoter.updatePrices.t.sol | 140 + .../FeeQuoter.updateTokenPriceFeeds.t.sol | 127 + .../FeeQuoter.validateDestFamilyAddress.t.sol | 42 + .../ccip/test/feeQuoter/FeeQuoterSetup.t.sol | 106 +- .../offRamp/OffRamp.batchExecute.t.sol | 16 +- .../offRamp/offRamp/OffRamp.ccipReceive.t.sol | 3 +- .../offRamp/offRamp/OffRamp.constructor.t.sol | 6 +- .../offRamp/offRamp/OffRamp.execute.t.sol | 16 +- .../offRamp/OffRamp.executeSingleReport.t.sol | 36 +- .../offRamp/OffRamp.manuallyExecute.t.sol | 26 +- .../offRamp/OffRamp.releaseOrMintTokens.t.sol | 16 + .../offRamp/OffRamp.setDynamicConfig.t.sol | 2 +- .../test/offRamp/offRamp/OffRampSetup.t.sol | 81 +- .../src/v0.8/ccip/test/onRamp/OnRamp.t.sol | 1087 -------- .../OnRamp.applyDestChainConfigUpdates.t.sol | 228 ++ .../onRamp/onRamp/OnRamp.constructor.t.sol | 132 + .../onRamp/OnRamp.forwardFromRouter.t.sol | 509 ++++ .../test/onRamp/onRamp/OnRamp.getFee.t.sol | 98 + .../onRamp/OnRamp.getSupportedTokens.t.sol | 12 + .../onRamp/onRamp/OnRamp.getTokenPool.t.sol | 24 + .../onRamp/OnRamp.setDynamicConfig.t.sol | 89 + .../onRamp/OnRamp.withdrawFeeTokens.t.sol | 69 + .../onRamp/{ => onRamp}/OnRampSetup.t.sol | 135 +- .../LockReleaseTokenPoolSetup.t.sol | 2 +- .../TokenPoolWithAllowListSetup.t.sol | 2 +- .../src/v0.8/ccip/test/router/Router.t.sol | 2 +- 53 files changed, 4051 insertions(+), 3960 deletions(-) create mode 100644 contracts/.changeset/poor-ears-hear.md create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyDestChainConfigUpdates.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyFeeTokensUpdates.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyPremiumMultiplierWeiPerEthUpdates.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyTokenTransferFeeConfigUpdates.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.constructor.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.convertTokenAmount.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getDataAvailabilityCost.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenAndGasPrices.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenPrice.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenPrices.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenTransferCost.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedFee.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedTokenPrice.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.onReport.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.parseEVMExtraArgsFromBytes.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.processMessageArgs.t.sol delete mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.updatePrices.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.updateTokenPriceFeeds.t.sol create mode 100644 contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.validateDestFamilyAddress.t.sol delete mode 100644 contracts/src/v0.8/ccip/test/onRamp/OnRamp.t.sol create mode 100644 contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.applyDestChainConfigUpdates.t.sol create mode 100644 contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.constructor.t.sol create mode 100644 contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.forwardFromRouter.t.sol create mode 100644 contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getFee.t.sol create mode 100644 contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getSupportedTokens.t.sol create mode 100644 contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getTokenPool.t.sol create mode 100644 contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.setDynamicConfig.t.sol create mode 100644 contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.withdrawFeeTokens.t.sol rename contracts/src/v0.8/ccip/test/onRamp/{ => onRamp}/OnRampSetup.t.sol (58%) diff --git a/contracts/.changeset/poor-ears-hear.md b/contracts/.changeset/poor-ears-hear.md new file mode 100644 index 00000000000..6e0fbe26663 --- /dev/null +++ b/contracts/.changeset/poor-ears-hear.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +#internal split onRamp and feeQuoter tests diff --git a/contracts/gas-snapshots/ccip.gas-snapshot b/contracts/gas-snapshots/ccip.gas-snapshot index b5b42e26d06..e6e2a6e9945 100644 --- a/contracts/gas-snapshots/ccip.gas-snapshot +++ b/contracts/gas-snapshots/ccip.gas-snapshot @@ -67,8 +67,8 @@ CCIPHome_setCandidate:test_setCandidate_CanOnlySelfCall_reverts() (gas: 29383) CCIPHome_setCandidate:test_setCandidate_ConfigDigestMismatch_reverts() (gas: 1395154) CCIPHome_setCandidate:test_setCandidate_success() (gas: 1365439) DefensiveExampleTest:test_HappyPath_Success() (gas: 200473) -DefensiveExampleTest:test_Recovery() (gas: 424859) -E2E:test_E2E_3MessagesMMultiOffRampSuccess_gas() (gas: 1520821) +DefensiveExampleTest:test_Recovery() (gas: 424876) +E2E:test_E2E_3MessagesMMultiOffRampSuccess_gas() (gas: 1519829) EtherSenderReceiverTest_ccipReceive:test_ccipReceive_fallbackToWethTransfer() (gas: 96962) EtherSenderReceiverTest_ccipReceive:test_ccipReceive_happyPath() (gas: 49812) EtherSenderReceiverTest_ccipReceive:test_ccipReceive_wrongToken() (gas: 17457) @@ -152,10 +152,10 @@ FeeQuoter_getTokenAndGasPrices:test_ZeroGasPrice_Success() (gas: 109131) FeeQuoter_getTokenPrice:test_GetTokenPriceFromFeed_Success() (gas: 68015) FeeQuoter_getTokenPrice:test_GetTokenPrice_LocalMoreRecent_Success() (gas: 33463) FeeQuoter_getTokenPrices:test_GetTokenPrices_Success() (gas: 78498) -FeeQuoter_getTokenTransferCost:test_CustomTokenBpsFee_Success() (gas: 39502) +FeeQuoter_getTokenTransferCost:test_CustomTokenBpsFee_Success() (gas: 37372) FeeQuoter_getTokenTransferCost:test_FeeTokenBpsFee_Success() (gas: 35151) FeeQuoter_getTokenTransferCost:test_LargeTokenTransferChargesMaxFeeAndGas_Success() (gas: 28241) -FeeQuoter_getTokenTransferCost:test_MixedTokenTransferFee_Success() (gas: 98330) +FeeQuoter_getTokenTransferCost:test_MixedTokenTransferFee_Success() (gas: 96218) FeeQuoter_getTokenTransferCost:test_NoTokenTransferChargesZeroFee_Success() (gas: 20702) FeeQuoter_getTokenTransferCost:test_SmallTokenTransferChargesMinFeeAndGas_Success() (gas: 28049) FeeQuoter_getTokenTransferCost:test_ZeroAmountTokenTransferChargesMinFeeAndGas_Success() (gas: 28072) @@ -349,213 +349,213 @@ NonceManager_NonceIncrementation:test_getIncrementedOutboundNonce_Success() (gas NonceManager_NonceIncrementation:test_incrementInboundNonce_Skip() (gas: 23706) NonceManager_NonceIncrementation:test_incrementInboundNonce_Success() (gas: 38778) NonceManager_NonceIncrementation:test_incrementNoncesInboundAndOutbound_Success() (gas: 71901) -NonceManager_OffRampUpgrade:test_NoPrevOffRampForChain_Success() (gas: 185976) -NonceManager_OffRampUpgrade:test_UpgradedNonceNewSenderStartsAtZero_Success() (gas: 189423) -NonceManager_OffRampUpgrade:test_UpgradedNonceStartsAtV1Nonce_Success() (gas: 252593) -NonceManager_OffRampUpgrade:test_UpgradedOffRampNonceSkipsIfMsgInFlight_Success() (gas: 220830) -NonceManager_OffRampUpgrade:test_UpgradedSenderNoncesReadsPreviousRamp_Success() (gas: 60591) -NonceManager_OffRampUpgrade:test_Upgraded_Success() (gas: 153010) +NonceManager_OffRampUpgrade:test_NoPrevOffRampForChain_Success() (gas: 185739) +NonceManager_OffRampUpgrade:test_UpgradedNonceNewSenderStartsAtZero_Success() (gas: 189192) +NonceManager_OffRampUpgrade:test_UpgradedNonceStartsAtV1Nonce_Success() (gas: 252176) +NonceManager_OffRampUpgrade:test_UpgradedOffRampNonceSkipsIfMsgInFlight_Success() (gas: 220541) +NonceManager_OffRampUpgrade:test_UpgradedSenderNoncesReadsPreviousRamp_Success() (gas: 60497) +NonceManager_OffRampUpgrade:test_Upgraded_Success() (gas: 152904) NonceManager_OnRampUpgrade:test_UpgradeNonceNewSenderStartsAtZero_Success() (gas: 166101) -NonceManager_OnRampUpgrade:test_UpgradeNonceStartsAtV1Nonce_Success() (gas: 195806) +NonceManager_OnRampUpgrade:test_UpgradeNonceStartsAtV1Nonce_Success() (gas: 195828) NonceManager_OnRampUpgrade:test_UpgradeSenderNoncesReadsPreviousRamp_Success() (gas: 139098) -NonceManager_OnRampUpgrade:test_Upgrade_Success() (gas: 105257) +NonceManager_OnRampUpgrade:test_Upgrade_Success() (gas: 105168) NonceManager_applyPreviousRampsUpdates:test_MultipleRampsUpdates_success() (gas: 123604) NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySetOffRamp_Revert() (gas: 43403) -NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySetOnRampAndOffRamp_Revert() (gas: 64775) -NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySetOnRamp_Revert() (gas: 43201) +NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySetOnRampAndOffRamp_Revert() (gas: 64752) +NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySetOnRamp_Revert() (gas: 43245) NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySet_overrideAllowed_success() (gas: 45941) NonceManager_applyPreviousRampsUpdates:test_SingleRampUpdate_success() (gas: 66889) NonceManager_applyPreviousRampsUpdates:test_ZeroInput_success() (gas: 12213) NonceManager_typeAndVersion:test_typeAndVersion() (gas: 9705) -OffRamp_afterOC3ConfigSet:test_afterOCR3ConfigSet_SignatureVerificationDisabled_Revert() (gas: 5880084) +OffRamp_afterOC3ConfigSet:test_afterOCR3ConfigSet_SignatureVerificationDisabled_Revert() (gas: 5880050) OffRamp_applySourceChainConfigUpdates:test_AddMultipleChains_Success() (gas: 626115) -OffRamp_applySourceChainConfigUpdates:test_AddNewChain_Success() (gas: 166515) -OffRamp_applySourceChainConfigUpdates:test_ApplyZeroUpdates_Success() (gas: 16763) -OffRamp_applySourceChainConfigUpdates:test_InvalidOnRampUpdate_Revert() (gas: 274803) -OffRamp_applySourceChainConfigUpdates:test_ReplaceExistingChainOnRamp_Success() (gas: 168560) +OffRamp_applySourceChainConfigUpdates:test_AddNewChain_Success() (gas: 166493) +OffRamp_applySourceChainConfigUpdates:test_ApplyZeroUpdates_Success() (gas: 16741) +OffRamp_applySourceChainConfigUpdates:test_InvalidOnRampUpdate_Revert() (gas: 274735) +OffRamp_applySourceChainConfigUpdates:test_ReplaceExistingChainOnRamp_Success() (gas: 168604) OffRamp_applySourceChainConfigUpdates:test_ReplaceExistingChain_Success() (gas: 181059) OffRamp_applySourceChainConfigUpdates:test_RouterAddress_Revert() (gas: 13463) OffRamp_applySourceChainConfigUpdates:test_ZeroOnRampAddress_Revert() (gas: 72746) OffRamp_applySourceChainConfigUpdates:test_ZeroSourceChainSelector_Revert() (gas: 15476) -OffRamp_applySourceChainConfigUpdates:test_allowNonOnRampUpdateAfterLaneIsUsed_success() (gas: 285153) -OffRamp_batchExecute:test_MultipleReportsDifferentChainsSkipCursedChain_Success() (gas: 177564) -OffRamp_batchExecute:test_MultipleReportsDifferentChains_Success() (gas: 333809) -OffRamp_batchExecute:test_MultipleReportsSameChain_Success() (gas: 277075) -OffRamp_batchExecute:test_MultipleReportsSkipDuplicate_Success() (gas: 168494) +OffRamp_applySourceChainConfigUpdates:test_allowNonOnRampUpdateAfterLaneIsUsed_success() (gas: 285063) +OffRamp_batchExecute:test_MultipleReportsDifferentChainsSkipCursedChain_Success() (gas: 177349) +OffRamp_batchExecute:test_MultipleReportsDifferentChains_Success() (gas: 333175) +OffRamp_batchExecute:test_MultipleReportsSameChain_Success() (gas: 276441) +OffRamp_batchExecute:test_MultipleReportsSkipDuplicate_Success() (gas: 168334) OffRamp_batchExecute:test_OutOfBoundsGasLimitsAccess_Revert() (gas: 187853) -OffRamp_batchExecute:test_SingleReport_Success() (gas: 156555) -OffRamp_batchExecute:test_Unhealthy_Success() (gas: 553993) +OffRamp_batchExecute:test_SingleReport_Success() (gas: 156369) +OffRamp_batchExecute:test_Unhealthy_Success() (gas: 553439) OffRamp_batchExecute:test_ZeroReports_Revert() (gas: 10600) -OffRamp_ccipReceive:test_RevertWhen_Always() (gas: 15407) -OffRamp_commit:test_CommitOnRampMismatch_Revert() (gas: 92834) -OffRamp_commit:test_FailedRMNVerification_Reverts() (gas: 63500) -OffRamp_commit:test_InvalidIntervalMinLargerThanMax_Revert() (gas: 70146) -OffRamp_commit:test_InvalidInterval_Revert() (gas: 66209) -OffRamp_commit:test_InvalidRootRevert() (gas: 65304) -OffRamp_commit:test_NoConfigWithOtherConfigPresent_Revert() (gas: 6641216) -OffRamp_commit:test_NoConfig_Revert() (gas: 6224546) -OffRamp_commit:test_OnlyGasPriceUpdates_Success() (gas: 112980) -OffRamp_commit:test_OnlyPriceUpdateStaleReport_Revert() (gas: 121333) -OffRamp_commit:test_OnlyTokenPriceUpdates_Success() (gas: 112979) -OffRamp_commit:test_PriceSequenceNumberCleared_Success() (gas: 355372) -OffRamp_commit:test_ReportAndPriceUpdate_Success() (gas: 164388) -OffRamp_commit:test_ReportOnlyRootSuccess_gas() (gas: 141416) -OffRamp_commit:test_RootAlreadyCommitted_Revert() (gas: 148426) -OffRamp_commit:test_RootWithRMNDisabled_success() (gas: 154111) -OffRamp_commit:test_SourceChainNotEnabled_Revert() (gas: 61771) -OffRamp_commit:test_StaleReportWithRoot_Success() (gas: 232626) -OffRamp_commit:test_UnauthorizedTransmitter_Revert() (gas: 125320) -OffRamp_commit:test_Unhealthy_Revert() (gas: 60572) -OffRamp_commit:test_ValidPriceUpdateThenStaleReportWithRoot_Success() (gas: 207009) -OffRamp_commit:test_ZeroEpochAndRound_Revert() (gas: 53689) -OffRamp_constructor:test_Constructor_Success() (gas: 6186775) -OffRamp_constructor:test_SourceChainSelector_Revert() (gas: 136553) -OffRamp_constructor:test_ZeroChainSelector_Revert() (gas: 103634) +OffRamp_ccipReceive:test_RevertWhen_Always() (gas: 9303) +OffRamp_commit:test_CommitOnRampMismatch_Revert() (gas: 92744) +OffRamp_commit:test_FailedRMNVerification_Reverts() (gas: 63432) +OffRamp_commit:test_InvalidIntervalMinLargerThanMax_Revert() (gas: 69993) +OffRamp_commit:test_InvalidInterval_Revert() (gas: 66119) +OffRamp_commit:test_InvalidRootRevert() (gas: 65214) +OffRamp_commit:test_NoConfigWithOtherConfigPresent_Revert() (gas: 6641148) +OffRamp_commit:test_NoConfig_Revert() (gas: 6224566) +OffRamp_commit:test_OnlyGasPriceUpdates_Success() (gas: 112985) +OffRamp_commit:test_OnlyPriceUpdateStaleReport_Revert() (gas: 121175) +OffRamp_commit:test_OnlyTokenPriceUpdates_Success() (gas: 112917) +OffRamp_commit:test_PriceSequenceNumberCleared_Success() (gas: 355254) +OffRamp_commit:test_ReportAndPriceUpdate_Success() (gas: 164263) +OffRamp_commit:test_ReportOnlyRootSuccess_gas() (gas: 141269) +OffRamp_commit:test_RootAlreadyCommitted_Revert() (gas: 148268) +OffRamp_commit:test_RootWithRMNDisabled_success() (gas: 153986) +OffRamp_commit:test_SourceChainNotEnabled_Revert() (gas: 61681) +OffRamp_commit:test_StaleReportWithRoot_Success() (gas: 232354) +OffRamp_commit:test_UnauthorizedTransmitter_Revert() (gas: 125230) +OffRamp_commit:test_Unhealthy_Revert() (gas: 60482) +OffRamp_commit:test_ValidPriceUpdateThenStaleReportWithRoot_Success() (gas: 206800) +OffRamp_commit:test_ZeroEpochAndRound_Revert() (gas: 53621) +OffRamp_constructor:test_Constructor_Success() (gas: 6186663) +OffRamp_constructor:test_SourceChainSelector_Revert() (gas: 136575) +OffRamp_constructor:test_ZeroChainSelector_Revert() (gas: 103612) OffRamp_constructor:test_ZeroNonceManager_Revert() (gas: 101461) OffRamp_constructor:test_ZeroOnRampAddress_Revert() (gas: 162055) OffRamp_constructor:test_ZeroRMNRemote_Revert() (gas: 101378) -OffRamp_constructor:test_ZeroTokenAdminRegistry_Revert() (gas: 101427) +OffRamp_constructor:test_ZeroTokenAdminRegistry_Revert() (gas: 101382) OffRamp_execute:test_IncorrectArrayType_Revert() (gas: 17639) -OffRamp_execute:test_LargeBatch_Success() (gas: 3406667) -OffRamp_execute:test_MultipleReportsWithPartialValidationFailures_Success() (gas: 371505) -OffRamp_execute:test_MultipleReports_Success() (gas: 299194) -OffRamp_execute:test_NoConfigWithOtherConfigPresent_Revert() (gas: 7049301) +OffRamp_execute:test_LargeBatch_Success() (gas: 3374933) +OffRamp_execute:test_MultipleReportsWithPartialValidationFailures_Success() (gas: 371025) +OffRamp_execute:test_MultipleReports_Success() (gas: 298564) +OffRamp_execute:test_NoConfigWithOtherConfigPresent_Revert() (gas: 7049279) OffRamp_execute:test_NoConfig_Revert() (gas: 6273749) OffRamp_execute:test_NonArray_Revert() (gas: 27643) -OffRamp_execute:test_SingleReport_Success() (gas: 175809) -OffRamp_execute:test_UnauthorizedTransmitter_Revert() (gas: 147805) +OffRamp_execute:test_SingleReport_Success() (gas: 175627) +OffRamp_execute:test_UnauthorizedTransmitter_Revert() (gas: 147783) OffRamp_execute:test_WrongConfigWithSigners_Revert() (gas: 6940958) -OffRamp_execute:test_ZeroReports_Revert() (gas: 17317) -OffRamp_executeSingleMessage:test_MessageSender_Revert() (gas: 18537) -OffRamp_executeSingleMessage:test_NonContractWithTokens_Success() (gas: 244193) -OffRamp_executeSingleMessage:test_NonContract_Success() (gas: 20389) -OffRamp_executeSingleMessage:test_TokenHandlingError_Revert() (gas: 205666) -OffRamp_executeSingleMessage:test_ZeroGasDONExecution_Revert() (gas: 48884) -OffRamp_executeSingleMessage:test_executeSingleMessage_NoTokens_Success() (gas: 56065) -OffRamp_executeSingleMessage:test_executeSingleMessage_WithFailingValidationNoRouterCall_Revert() (gas: 212828) -OffRamp_executeSingleMessage:test_executeSingleMessage_WithFailingValidation_Revert() (gas: 85455) -OffRamp_executeSingleMessage:test_executeSingleMessage_WithTokens_Success() (gas: 274305) -OffRamp_executeSingleMessage:test_executeSingleMessage_WithVInterception_Success() (gas: 91944) -OffRamp_executeSingleReport:test_DisabledSourceChain_Revert() (gas: 28658) +OffRamp_execute:test_ZeroReports_Revert() (gas: 17361) +OffRamp_executeSingleMessage:test_MessageSender_Revert() (gas: 18533) +OffRamp_executeSingleMessage:test_NonContractWithTokens_Success() (gas: 244171) +OffRamp_executeSingleMessage:test_NonContract_Success() (gas: 20363) +OffRamp_executeSingleMessage:test_TokenHandlingError_Revert() (gas: 205647) +OffRamp_executeSingleMessage:test_ZeroGasDONExecution_Revert() (gas: 48880) +OffRamp_executeSingleMessage:test_executeSingleMessage_NoTokens_Success() (gas: 56102) +OffRamp_executeSingleMessage:test_executeSingleMessage_WithFailingValidationNoRouterCall_Revert() (gas: 212824) +OffRamp_executeSingleMessage:test_executeSingleMessage_WithFailingValidation_Revert() (gas: 85495) +OffRamp_executeSingleMessage:test_executeSingleMessage_WithTokens_Success() (gas: 274279) +OffRamp_executeSingleMessage:test_executeSingleMessage_WithVInterception_Success() (gas: 91918) +OffRamp_executeSingleReport:test_DisabledSourceChain_Revert() (gas: 28636) OffRamp_executeSingleReport:test_EmptyReport_Revert() (gas: 15580) -OffRamp_executeSingleReport:test_InvalidSourcePoolAddress_Success() (gas: 481795) -OffRamp_executeSingleReport:test_ManualExecutionNotYetEnabled_Revert() (gas: 48273) -OffRamp_executeSingleReport:test_MismatchingDestChainSelector_Revert() (gas: 34100) +OffRamp_executeSingleReport:test_InvalidSourcePoolAddress_Success() (gas: 481411) +OffRamp_executeSingleReport:test_ManualExecutionNotYetEnabled_Revert() (gas: 48295) +OffRamp_executeSingleReport:test_MismatchingDestChainSelector_Revert() (gas: 34188) OffRamp_executeSingleReport:test_NonExistingSourceChain_Revert() (gas: 28823) -OffRamp_executeSingleReport:test_ReceiverError_Success() (gas: 187698) -OffRamp_executeSingleReport:test_RetryFailedMessageWithoutManualExecution_Revert() (gas: 197829) -OffRamp_executeSingleReport:test_RootNotCommitted_Revert() (gas: 40686) -OffRamp_executeSingleReport:test_RouterYULCall_Revert() (gas: 404997) -OffRamp_executeSingleReport:test_SingleMessageNoTokensOtherChain_Success() (gas: 248698) -OffRamp_executeSingleReport:test_SingleMessageNoTokensUnordered_Success() (gas: 192576) -OffRamp_executeSingleReport:test_SingleMessageNoTokens_Success() (gas: 212587) -OffRamp_executeSingleReport:test_SingleMessageToNonCCIPReceiver_Success() (gas: 243705) -OffRamp_executeSingleReport:test_SingleMessagesNoTokensSuccess_gas() (gas: 141547) -OffRamp_executeSingleReport:test_SkippedIncorrectNonceStillExecutes_Success() (gas: 408961) +OffRamp_executeSingleReport:test_ReceiverError_Success() (gas: 187522) +OffRamp_executeSingleReport:test_RetryFailedMessageWithoutManualExecution_Revert() (gas: 197799) +OffRamp_executeSingleReport:test_RootNotCommitted_Revert() (gas: 40664) +OffRamp_executeSingleReport:test_RouterYULCall_Revert() (gas: 404911) +OffRamp_executeSingleReport:test_SingleMessageNoTokensOtherChain_Success() (gas: 248582) +OffRamp_executeSingleReport:test_SingleMessageNoTokensUnordered_Success() (gas: 192204) +OffRamp_executeSingleReport:test_SingleMessageNoTokens_Success() (gas: 212228) +OffRamp_executeSingleReport:test_SingleMessageToNonCCIPReceiver_Success() (gas: 243641) +OffRamp_executeSingleReport:test_SingleMessagesNoTokensSuccess_gas() (gas: 141397) +OffRamp_executeSingleReport:test_SkippedIncorrectNonceStillExecutes_Success() (gas: 408631) OffRamp_executeSingleReport:test_SkippedIncorrectNonce_Success() (gas: 58241) -OffRamp_executeSingleReport:test_TokenDataMismatch_Revert() (gas: 73808) -OffRamp_executeSingleReport:test_TwoMessagesWithTokensAndGE_Success() (gas: 583208) -OffRamp_executeSingleReport:test_TwoMessagesWithTokensSuccess_gas() (gas: 531794) -OffRamp_executeSingleReport:test_UnexpectedTokenData_Revert() (gas: 26774) -OffRamp_executeSingleReport:test_UnhealthySingleChainCurse_Revert() (gas: 549633) -OffRamp_executeSingleReport:test_Unhealthy_Success() (gas: 549580) -OffRamp_executeSingleReport:test_WithCurseOnAnotherSourceChain_Success() (gas: 460249) -OffRamp_executeSingleReport:test__execute_SkippedAlreadyExecutedMessageUnordered_Success() (gas: 135267) -OffRamp_executeSingleReport:test__execute_SkippedAlreadyExecutedMessage_Success() (gas: 164910) -OffRamp_getExecutionState:test_FillExecutionState_Success() (gas: 3911118) -OffRamp_getExecutionState:test_GetDifferentChainExecutionState_Success() (gas: 121222) -OffRamp_getExecutionState:test_GetExecutionState_Success() (gas: 89706) +OffRamp_executeSingleReport:test_TokenDataMismatch_Revert() (gas: 73786) +OffRamp_executeSingleReport:test_TwoMessagesWithTokensAndGE_Success() (gas: 582446) +OffRamp_executeSingleReport:test_TwoMessagesWithTokensSuccess_gas() (gas: 531112) +OffRamp_executeSingleReport:test_UnexpectedTokenData_Revert() (gas: 26751) +OffRamp_executeSingleReport:test_UnhealthySingleChainCurse_Revert() (gas: 549057) +OffRamp_executeSingleReport:test_Unhealthy_Success() (gas: 549093) +OffRamp_executeSingleReport:test_WithCurseOnAnotherSourceChain_Success() (gas: 460204) +OffRamp_executeSingleReport:test__execute_SkippedAlreadyExecutedMessageUnordered_Success() (gas: 135139) +OffRamp_executeSingleReport:test__execute_SkippedAlreadyExecutedMessage_Success() (gas: 164782) +OffRamp_getExecutionState:test_FillExecutionState_Success() (gas: 3888824) +OffRamp_getExecutionState:test_GetDifferentChainExecutionState_Success() (gas: 121048) +OffRamp_getExecutionState:test_GetExecutionState_Success() (gas: 89561) OffRamp_manuallyExecute:test_ManualExecGasLimitMismatchSingleReport_Revert() (gas: 81178) OffRamp_manuallyExecute:test_manuallyExecute_DestinationGasAmountCountMismatch_Revert() (gas: 74108) -OffRamp_manuallyExecute:test_manuallyExecute_DoesNotRevertIfUntouched_Success() (gas: 172634) +OffRamp_manuallyExecute:test_manuallyExecute_DoesNotRevertIfUntouched_Success() (gas: 172480) OffRamp_manuallyExecute:test_manuallyExecute_FailedTx_Revert() (gas: 212935) OffRamp_manuallyExecute:test_manuallyExecute_ForkedChain_Revert() (gas: 27166) OffRamp_manuallyExecute:test_manuallyExecute_GasLimitMismatchMultipleReports_Revert() (gas: 164939) OffRamp_manuallyExecute:test_manuallyExecute_InvalidReceiverExecutionGasLimit_Revert() (gas: 27703) OffRamp_manuallyExecute:test_manuallyExecute_InvalidTokenGasOverride_Revert() (gas: 55274) -OffRamp_manuallyExecute:test_manuallyExecute_LowGasLimit_Success() (gas: 489576) -OffRamp_manuallyExecute:test_manuallyExecute_MultipleReportsWithSingleCursedLane_Revert() (gas: 314392) -OffRamp_manuallyExecute:test_manuallyExecute_ReentrancyFails_Success() (gas: 2227930) +OffRamp_manuallyExecute:test_manuallyExecute_LowGasLimit_Success() (gas: 489352) +OffRamp_manuallyExecute:test_manuallyExecute_MultipleReportsWithSingleCursedLane_Revert() (gas: 314370) +OffRamp_manuallyExecute:test_manuallyExecute_ReentrancyFails_Success() (gas: 2227706) OffRamp_manuallyExecute:test_manuallyExecute_SourceChainSelectorMismatch_Revert() (gas: 165133) -OffRamp_manuallyExecute:test_manuallyExecute_Success() (gas: 225972) -OffRamp_manuallyExecute:test_manuallyExecute_WithGasOverride_Success() (gas: 226534) -OffRamp_manuallyExecute:test_manuallyExecute_WithMultiReportGasOverride_Success() (gas: 774706) -OffRamp_manuallyExecute:test_manuallyExecute_WithPartialMessages_Success() (gas: 344831) -OffRamp_releaseOrMintSingleToken:test__releaseOrMintSingleToken_NotACompatiblePool_Revert() (gas: 37632) -OffRamp_releaseOrMintSingleToken:test__releaseOrMintSingleToken_Success() (gas: 104648) +OffRamp_manuallyExecute:test_manuallyExecute_Success() (gas: 225844) +OffRamp_manuallyExecute:test_manuallyExecute_WithGasOverride_Success() (gas: 226384) +OffRamp_manuallyExecute:test_manuallyExecute_WithMultiReportGasOverride_Success() (gas: 773426) +OffRamp_manuallyExecute:test_manuallyExecute_WithPartialMessages_Success() (gas: 344159) +OffRamp_releaseOrMintSingleToken:test__releaseOrMintSingleToken_NotACompatiblePool_Revert() (gas: 37654) +OffRamp_releaseOrMintSingleToken:test__releaseOrMintSingleToken_Success() (gas: 104625) OffRamp_releaseOrMintSingleToken:test__releaseOrMintSingleToken_TokenHandlingError_transfer_Revert() (gas: 83092) OffRamp_releaseOrMintSingleToken:test_releaseOrMintToken_InvalidDataLength_Revert() (gas: 36812) -OffRamp_releaseOrMintSingleToken:test_releaseOrMintToken_ReleaseOrMintBalanceMismatch_Revert() (gas: 94648) -OffRamp_releaseOrMintSingleToken:test_releaseOrMintToken_TokenHandlingError_BalanceOf_Revert() (gas: 37301) +OffRamp_releaseOrMintSingleToken:test_releaseOrMintToken_ReleaseOrMintBalanceMismatch_Revert() (gas: 94670) +OffRamp_releaseOrMintSingleToken:test_releaseOrMintToken_TokenHandlingError_BalanceOf_Revert() (gas: 37323) OffRamp_releaseOrMintSingleToken:test_releaseOrMintToken_skip_ReleaseOrMintBalanceMismatch_if_pool_Revert() (gas: 86760) OffRamp_releaseOrMintTokens:test_TokenHandlingError_Reverts() (gas: 162911) -OffRamp_releaseOrMintTokens:test__releaseOrMintTokens_PoolIsNotAPool_Reverts() (gas: 23881) +OffRamp_releaseOrMintTokens:test__releaseOrMintTokens_PoolIsNotAPool_Reverts() (gas: 23836) OffRamp_releaseOrMintTokens:test_releaseOrMintTokens_InvalidDataLengthReturnData_Revert() (gas: 62844) OffRamp_releaseOrMintTokens:test_releaseOrMintTokens_PoolDoesNotSupportDest_Reverts() (gas: 80014) -OffRamp_releaseOrMintTokens:test_releaseOrMintTokens_Success() (gas: 175034) +OffRamp_releaseOrMintTokens:test_releaseOrMintTokens_Success() (gas: 174989) OffRamp_releaseOrMintTokens:test_releaseOrMintTokens_WithGasOverride_Success() (gas: 176901) -OffRamp_releaseOrMintTokens:test_releaseOrMintTokens_destDenominatedDecimals_Success() (gas: 188145) +OffRamp_releaseOrMintTokens:test_releaseOrMintTokens_destDenominatedDecimals_Success() (gas: 188167) OffRamp_setDynamicConfig:test_FeeQuoterZeroAddress_Revert() (gas: 11509) OffRamp_setDynamicConfig:test_NonOwner_Revert() (gas: 14019) -OffRamp_setDynamicConfig:test_SetDynamicConfigWithInterceptor_Success() (gas: 47591) -OffRamp_setDynamicConfig:test_SetDynamicConfig_Success() (gas: 25564) -OffRamp_trialExecute:test_RateLimitError_Success() (gas: 219989) -OffRamp_trialExecute:test_TokenHandlingErrorIsCaught_Success() (gas: 228644) -OffRamp_trialExecute:test_TokenPoolIsNotAContract_Success() (gas: 295794) -OffRamp_trialExecute:test_trialExecute_Success() (gas: 278096) +OffRamp_setDynamicConfig:test_SetDynamicConfigWithInterceptor_Success() (gas: 47579) +OffRamp_setDynamicConfig:test_SetDynamicConfig_Success() (gas: 25552) +OffRamp_trialExecute:test_RateLimitError_Success() (gas: 219928) +OffRamp_trialExecute:test_TokenHandlingErrorIsCaught_Success() (gas: 228561) +OffRamp_trialExecute:test_TokenPoolIsNotAContract_Success() (gas: 295602) +OffRamp_trialExecute:test_trialExecute_Success() (gas: 278032) OnRampTokenPoolReentrancy:test_OnRampTokenPoolReentrancy_Success() (gas: 251573) OnRamp_applyAllowlistUpdates:test_applyAllowlistUpdates_InvalidAllowListRequestDisabledAllowListWithAdds() (gas: 17227) OnRamp_applyAllowlistUpdates:test_applyAllowlistUpdates_Revert() (gas: 67101) OnRamp_applyAllowlistUpdates:test_applyAllowlistUpdates_Success() (gas: 325983) OnRamp_applyDestChainConfigUpdates:test_ApplyDestChainConfigUpdates_Success() (gas: 65892) OnRamp_applyDestChainConfigUpdates:test_ApplyDestChainConfigUpdates_WithInvalidChainSelector_Revert() (gas: 12902) -OnRamp_constructor:test_Constructor_EnableAllowList_ForwardFromRouter_Reverts() (gas: 2569385) +OnRamp_constructor:test_Constructor_EnableAllowList_ForwardFromRouter_Reverts() (gas: 2569362) OnRamp_constructor:test_Constructor_InvalidConfigChainSelectorEqZero_Revert() (gas: 95148) OnRamp_constructor:test_Constructor_InvalidConfigNonceManagerEqAddressZero_Revert() (gas: 93090) OnRamp_constructor:test_Constructor_InvalidConfigRMNProxyEqAddressZero_Revert() (gas: 98066) -OnRamp_constructor:test_Constructor_InvalidConfigTokenAdminRegistryEqAddressZero_Revert() (gas: 93124) -OnRamp_constructor:test_Constructor_Success() (gas: 2647534) -OnRamp_forwardFromRouter:test_ForwardFromRouterExtraArgsV2AllowOutOfOrderTrue_Success() (gas: 115376) +OnRamp_constructor:test_Constructor_InvalidConfigTokenAdminRegistryEqAddressZero_Revert() (gas: 93146) +OnRamp_constructor:test_Constructor_Success() (gas: 2647459) +OnRamp_forwardFromRouter:test_ForwardFromRouterExtraArgsV2AllowOutOfOrderTrue_Success() (gas: 115398) OnRamp_forwardFromRouter:test_ForwardFromRouterExtraArgsV2_Success() (gas: 146244) -OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessCustomExtraArgs() (gas: 145819) -OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessEmptyExtraArgs() (gas: 144046) -OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessLegacyExtraArgs() (gas: 146016) -OnRamp_forwardFromRouter:test_ForwardFromRouter_Success() (gas: 145414) +OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessCustomExtraArgs() (gas: 145841) +OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessEmptyExtraArgs() (gas: 143957) +OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessLegacyExtraArgs() (gas: 146038) +OnRamp_forwardFromRouter:test_ForwardFromRouter_Success() (gas: 145436) OnRamp_forwardFromRouter:test_ForwardFromRouter_Success_ConfigurableSourceRouter() (gas: 140697) OnRamp_forwardFromRouter:test_InvalidExtraArgsTag_Revert() (gas: 38504) -OnRamp_forwardFromRouter:test_MessageInterceptionError_Revert() (gas: 143100) +OnRamp_forwardFromRouter:test_MessageInterceptionError_Revert() (gas: 143122) OnRamp_forwardFromRouter:test_MesssageFeeTooHigh_Revert() (gas: 36611) -OnRamp_forwardFromRouter:test_MultiCannotSendZeroTokens_Revert() (gas: 36471) -OnRamp_forwardFromRouter:test_OriginalSender_Revert() (gas: 18268) -OnRamp_forwardFromRouter:test_Paused_Revert() (gas: 38412) -OnRamp_forwardFromRouter:test_Permissions_Revert() (gas: 23629) +OnRamp_forwardFromRouter:test_MultiCannotSendZeroTokens_Revert() (gas: 36493) +OnRamp_forwardFromRouter:test_OriginalSender_Revert() (gas: 18290) +OnRamp_forwardFromRouter:test_Paused_Revert() (gas: 38434) +OnRamp_forwardFromRouter:test_Permissions_Revert() (gas: 23651) OnRamp_forwardFromRouter:test_ShouldIncrementNonceOnlyOnOrdered_Success() (gas: 186649) OnRamp_forwardFromRouter:test_ShouldIncrementSeqNumAndNonce_Success() (gas: 213078) -OnRamp_forwardFromRouter:test_ShouldStoreLinkFees() (gas: 147003) +OnRamp_forwardFromRouter:test_ShouldStoreLinkFees() (gas: 147025) OnRamp_forwardFromRouter:test_ShouldStoreNonLinkFees() (gas: 161214) -OnRamp_forwardFromRouter:test_SourceTokenDataTooLarge_Revert() (gas: 3566334) +OnRamp_forwardFromRouter:test_SourceTokenDataTooLarge_Revert() (gas: 3566245) OnRamp_forwardFromRouter:test_UnAllowedOriginalSender_Revert() (gas: 24015) -OnRamp_forwardFromRouter:test_UnsupportedToken_Revert() (gas: 75832) -OnRamp_forwardFromRouter:test_forwardFromRouter_UnsupportedToken_Revert() (gas: 38588) +OnRamp_forwardFromRouter:test_UnsupportedToken_Revert() (gas: 75854) +OnRamp_forwardFromRouter:test_forwardFromRouter_UnsupportedToken_Revert() (gas: 38610) OnRamp_forwardFromRouter:test_forwardFromRouter_WithInterception_Success() (gas: 280344) -OnRamp_getFee:test_EmptyMessage_Success() (gas: 98782) -OnRamp_getFee:test_EnforceOutOfOrder_Revert() (gas: 65498) -OnRamp_getFee:test_GetFeeOfZeroForTokenMessage_Success() (gas: 87208) -OnRamp_getFee:test_NotAFeeTokenButPricedToken_Revert() (gas: 35167) -OnRamp_getFee:test_SingleTokenMessage_Success() (gas: 113990) -OnRamp_getFee:test_Unhealthy_Revert() (gas: 17108) +OnRamp_getFee:test_EmptyMessage_Success() (gas: 98692) +OnRamp_getFee:test_EnforceOutOfOrder_Revert() (gas: 65453) +OnRamp_getFee:test_GetFeeOfZeroForTokenMessage_Success() (gas: 87185) +OnRamp_getFee:test_NotAFeeTokenButPricedToken_Revert() (gas: 35166) +OnRamp_getFee:test_SingleTokenMessage_Success() (gas: 113865) +OnRamp_getFee:test_Unhealthy_Revert() (gas: 17040) OnRamp_getSupportedTokens:test_GetSupportedTokens_Revert() (gas: 10565) OnRamp_getTokenPool:test_GetTokenPool_Success() (gas: 35405) -OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigFeeAggregatorEqAddressZero_Revert() (gas: 11558) +OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigFeeAggregatorEqAddressZero_Revert() (gas: 11535) OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigFeeQuoterEqAddressZero_Revert() (gas: 13194) OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigInvalidConfig_Revert() (gas: 11499) -OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigOnlyOwner_Revert() (gas: 16648) -OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigReentrancyGuardEnteredEqTrue_Revert() (gas: 13220) +OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigOnlyOwner_Revert() (gas: 11938) +OnRamp_setDynamicConfig:test_setDynamicConfig_InvalidConfigReentrancyGuardEnteredEqTrue_Revert() (gas: 13264) OnRamp_setDynamicConfig:test_setDynamicConfig_Success() (gas: 56440) OnRamp_withdrawFeeTokens:test_WithdrawFeeTokens_Success() (gas: 125867) PingPong_ccipReceive:test_CcipReceive_Success() (gas: 172841) -PingPong_plumbing:test_OutOfOrderExecution_Success() (gas: 20328) -PingPong_plumbing:test_Pausing_Success() (gas: 17760) +PingPong_plumbing:test_OutOfOrderExecution_Success() (gas: 20283) +PingPong_plumbing:test_Pausing_Success() (gas: 17738) PingPong_startPingPong:test_StartPingPong_With_OOO_Success() (gas: 151954) PingPong_startPingPong:test_StartPingPong_With_Sequenced_Ordered_Success() (gas: 177569) RMNHome__validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_DuplicateOffchainPublicKey_reverts() (gas: 18850) @@ -627,35 +627,35 @@ Router_applyRampUpdates:test_OffRampUpdatesWithRouting() (gas: 10749462) Router_applyRampUpdates:test_OnRampDisable() (gas: 56428) Router_applyRampUpdates:test_OnlyOwner_Revert() (gas: 12414) Router_ccipSend:test_CCIPSendLinkFeeNoTokenSuccess_gas() (gas: 131413) -Router_ccipSend:test_CCIPSendLinkFeeOneTokenSuccess_gas() (gas: 221307) +Router_ccipSend:test_CCIPSendLinkFeeOneTokenSuccess_gas() (gas: 221240) Router_ccipSend:test_FeeTokenAmountTooLow_Revert() (gas: 71841) Router_ccipSend:test_InvalidMsgValue() (gas: 32411) -Router_ccipSend:test_NativeFeeTokenInsufficientValue() (gas: 69502) -Router_ccipSend:test_NativeFeeTokenOverpay_Success() (gas: 193274) +Router_ccipSend:test_NativeFeeTokenInsufficientValue() (gas: 69524) +Router_ccipSend:test_NativeFeeTokenOverpay_Success() (gas: 193296) Router_ccipSend:test_NativeFeeTokenZeroValue() (gas: 61550) Router_ccipSend:test_NativeFeeToken_Success() (gas: 191900) -Router_ccipSend:test_NonLinkFeeToken_Success() (gas: 226510) +Router_ccipSend:test_NonLinkFeeToken_Success() (gas: 226532) Router_ccipSend:test_UnsupportedDestinationChain_Revert() (gas: 25056) -Router_ccipSend:test_WhenNotHealthy_Revert() (gas: 45034) +Router_ccipSend:test_WhenNotHealthy_Revert() (gas: 45056) Router_ccipSend:test_WrappedNativeFeeToken_Success() (gas: 194209) Router_ccipSend:test_ccipSend_nativeFeeNoTokenSuccess_gas() (gas: 140674) -Router_ccipSend:test_ccipSend_nativeFeeOneTokenSuccess_gas() (gas: 230481) -Router_constructor:test_Constructor_Success() (gas: 13155) +Router_ccipSend:test_ccipSend_nativeFeeOneTokenSuccess_gas() (gas: 230436) +Router_constructor:test_Constructor_Success() (gas: 13222) Router_getArmProxy:test_getArmProxy() (gas: 10573) -Router_getFee:test_GetFeeSupportedChain_Success() (gas: 51979) -Router_getFee:test_UnsupportedDestinationChain_Revert() (gas: 17430) +Router_getFee:test_GetFeeSupportedChain_Success() (gas: 51934) +Router_getFee:test_UnsupportedDestinationChain_Revert() (gas: 17385) Router_getSupportedTokens:test_GetSupportedTokens_Revert() (gas: 10565) -Router_recoverTokens:test_RecoverTokensInvalidRecipient_Revert() (gas: 11344) +Router_recoverTokens:test_RecoverTokensInvalidRecipient_Revert() (gas: 11410) Router_recoverTokens:test_RecoverTokensNoFunds_Revert() (gas: 20199) -Router_recoverTokens:test_RecoverTokensNonOwner_Revert() (gas: 11214) +Router_recoverTokens:test_RecoverTokensNonOwner_Revert() (gas: 11236) Router_recoverTokens:test_RecoverTokensValueReceiver_Revert() (gas: 349502) -Router_recoverTokens:test_RecoverTokens_Success() (gas: 52622) -Router_routeMessage:test_routeMessage_AutoExec_Success() (gas: 43367) -Router_routeMessage:test_routeMessage_ExecutionEvent_Success() (gas: 159649) -Router_routeMessage:test_routeMessage_ManualExec_Success() (gas: 35845) -Router_routeMessage:test_routeMessage_OnlyOffRamp_Revert() (gas: 25431) -Router_routeMessage:test_routeMessage_WhenNotHealthy_Revert() (gas: 44889) -Router_setWrappedNative:test_OnlyOwner_Revert() (gas: 10986) +Router_recoverTokens:test_RecoverTokens_Success() (gas: 52640) +Router_routeMessage:test_routeMessage_AutoExec_Success() (gas: 43213) +Router_routeMessage:test_routeMessage_ExecutionEvent_Success() (gas: 159418) +Router_routeMessage:test_routeMessage_ManualExec_Success() (gas: 35723) +Router_routeMessage:test_routeMessage_OnlyOffRamp_Revert() (gas: 25376) +Router_routeMessage:test_routeMessage_WhenNotHealthy_Revert() (gas: 44812) +Router_setWrappedNative:test_OnlyOwner_Revert() (gas: 11008) TokenAdminRegistry_acceptAdminRole:test_acceptAdminRole_OnlyPendingAdministrator_Revert() (gas: 51433) TokenAdminRegistry_acceptAdminRole:test_acceptAdminRole_Success() (gas: 44189) TokenAdminRegistry_addRegistryModule:test_addRegistryModule_OnlyOwner_Revert() (gas: 12662) diff --git a/contracts/src/v0.8/ccip/test/BaseTest.t.sol b/contracts/src/v0.8/ccip/test/BaseTest.t.sol index 2c54a49744f..2770f0fb4d6 100644 --- a/contracts/src/v0.8/ccip/test/BaseTest.t.sol +++ b/contracts/src/v0.8/ccip/test/BaseTest.t.sol @@ -14,15 +14,8 @@ contract BaseTest is Test { // Addresses address internal constant OWNER = 0x00007e64E1fB0C487F25dd6D3601ff6aF8d32e4e; address internal constant STRANGER = address(999999); - address internal constant DUMMY_CONTRACT_ADDRESS = 0x1111111111111111111111111111111111111112; - address internal constant ON_RAMP_ADDRESS = 0x11118e64e1FB0c487f25dD6D3601FF6aF8d32E4e; - address internal constant ZERO_ADDRESS = address(0); - address internal constant FEE_AGGREGATOR = 0xa33CDB32eAEce34F6affEfF4899cef45744EDea3; address internal constant USER_1 = address(1); - address internal constant USER_2 = address(2); - address internal constant USER_3 = address(3); - address internal constant USER_4 = address(4); // Message info uint64 internal constant SOURCE_CHAIN_SELECTOR = 1; @@ -34,7 +27,6 @@ contract BaseTest is Test { uint32 internal constant TWELVE_HOURS = 60 * 60 * 12; // Onramp - uint96 internal constant MAX_NOP_FEES_JUELS = 1e27; uint96 internal constant MAX_MSG_FEES_JUELS = 1_000e18; uint32 internal constant DEST_GAS_OVERHEAD = 300_000; uint16 internal constant DEST_GAS_PER_PAYLOAD_BYTE = 16; @@ -45,31 +37,12 @@ contract BaseTest is Test { bool private s_baseTestInitialized; - // Use 16 gas per data availability byte in our tests. - // This is an overestimation in OP stack, it ignores 4 gas per 0 byte rule. - // Arbitrum on the other hand, does always use 16 gas per data availability byte. - // This value may be substantially decreased after EIP 4844. - uint16 internal constant DEST_GAS_PER_DATA_AVAILABILITY_BYTE = 16; - - // Total L1 data availability overhead estimate is 33_596 gas. - // This value includes complete CommitStore and OffRamp call data. - uint32 internal constant DEST_DATA_AVAILABILITY_OVERHEAD_GAS = 188 // Fixed data availability overhead in OP stack. - + (32 * 31 + 4) * DEST_GAS_PER_DATA_AVAILABILITY_BYTE // CommitStore single-root transmission takes up about 31 slots, plus selector. - + (32 * 34 + 4) * DEST_GAS_PER_DATA_AVAILABILITY_BYTE; // OffRamp transmission excluding EVM2EVMMessage takes up about 34 slots, plus selector. - - // Multiples of bps, or 0.0001, use 6840 to be same as OP mainnet compression factor of 0.684. - uint16 internal constant DEST_GAS_DATA_AVAILABILITY_MULTIPLIER_BPS = 6840; - // OffRamp uint32 internal constant MAX_DATA_SIZE = 30_000; uint16 internal constant MAX_TOKENS_LENGTH = 5; uint16 internal constant GAS_FOR_CALL_EXACT_CHECK = 5000; - uint32 internal constant PERMISSION_LESS_EXECUTION_THRESHOLD_SECONDS = 500; uint32 internal constant MAX_GAS_LIMIT = 4_000_000; - // Rate limiter - address internal constant ADMIN = 0x11118e64e1FB0c487f25dD6D3601FF6aF8d32E4e; - MockRMN internal s_mockRMN; IRMNRemote internal s_mockRMNRemote; diff --git a/contracts/src/v0.8/ccip/test/NonceManager.t.sol b/contracts/src/v0.8/ccip/test/NonceManager.t.sol index 4c395e1dc54..f560b5be593 100644 --- a/contracts/src/v0.8/ccip/test/NonceManager.t.sol +++ b/contracts/src/v0.8/ccip/test/NonceManager.t.sol @@ -12,7 +12,7 @@ import {BaseTest} from "./BaseTest.t.sol"; import {EVM2EVMOffRampHelper} from "./helpers/EVM2EVMOffRampHelper.sol"; import {OnRampHelper} from "./helpers/OnRampHelper.sol"; import {OffRampSetup} from "./offRamp/offRamp/OffRampSetup.t.sol"; -import {OnRampSetup} from "./onRamp/OnRampSetup.t.sol"; +import {OnRampSetup} from "./onRamp/onRamp/OnRampSetup.t.sol"; import {Test} from "forge-std/Test.sol"; @@ -377,7 +377,7 @@ contract NonceManager_OffRampUpgrade is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -405,7 +405,7 @@ contract NonceManager_OffRampUpgrade is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messagesChain3), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_3, messagesChain3[0].header.sequenceNumber, messagesChain3[0].header.messageId, @@ -453,7 +453,7 @@ contract NonceManager_OffRampUpgrade is OffRampSetup { _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp[0].header.sequenceNumber, messagesMultiRamp[0].header.messageId, @@ -474,7 +474,7 @@ contract NonceManager_OffRampUpgrade is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp[0].header.sequenceNumber, messagesMultiRamp[0].header.messageId, @@ -507,7 +507,7 @@ contract NonceManager_OffRampUpgrade is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messagesMultiRamp[0].header.sequenceNumber, messagesMultiRamp[0].header.messageId, @@ -554,7 +554,7 @@ contract NonceManager_OffRampUpgrade is OffRampSetup { _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, diff --git a/contracts/src/v0.8/ccip/test/TokenSetup.t.sol b/contracts/src/v0.8/ccip/test/TokenSetup.t.sol index 203145881e3..42d10190f1e 100644 --- a/contracts/src/v0.8/ccip/test/TokenSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/TokenSetup.t.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.24; import {BurnMintERC677} from "../../shared/token/ERC677/BurnMintERC677.sol"; -import {Client} from "../libraries/Client.sol"; import {BurnMintTokenPool} from "../pools/BurnMintTokenPool.sol"; import {LockReleaseTokenPool} from "../pools/LockReleaseTokenPool.sol"; import {TokenPool} from "../pools/TokenPool.sol"; @@ -136,18 +135,6 @@ contract TokenSetup is RouterSetup { } } - function _getCastedSourceEVMTokenAmountsWithZeroAmounts() - internal - view - returns (Client.EVMTokenAmount[] memory tokenAmounts) - { - tokenAmounts = new Client.EVMTokenAmount[](s_sourceTokens.length); - for (uint256 i = 0; i < tokenAmounts.length; ++i) { - tokenAmounts[i].token = s_sourceTokens[i]; - } - return tokenAmounts; - } - function _setPool( TokenAdminRegistry tokenAdminRegistry, address token, diff --git a/contracts/src/v0.8/ccip/test/applications/DefensiveExample.t.sol b/contracts/src/v0.8/ccip/test/applications/DefensiveExample.t.sol index 70cbc9c950b..b4829668ce3 100644 --- a/contracts/src/v0.8/ccip/test/applications/DefensiveExample.t.sol +++ b/contracts/src/v0.8/ccip/test/applications/DefensiveExample.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import {DefensiveExample} from "../../applications/DefensiveExample.sol"; import {Client} from "../../libraries/Client.sol"; -import {OnRampSetup} from "../onRamp/OnRampSetup.t.sol"; +import {OnRampSetup} from "../onRamp/onRamp/OnRampSetup.t.sol"; import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/src/v0.8/ccip/test/applications/ImmutableExample.t.sol b/contracts/src/v0.8/ccip/test/applications/ImmutableExample.t.sol index 61b0204e7d8..f3f09ecc78c 100644 --- a/contracts/src/v0.8/ccip/test/applications/ImmutableExample.t.sol +++ b/contracts/src/v0.8/ccip/test/applications/ImmutableExample.t.sol @@ -4,7 +4,7 @@ import {IAny2EVMMessageReceiver} from "../../interfaces/IAny2EVMMessageReceiver. import {CCIPClientExample} from "../../applications/CCIPClientExample.sol"; import {Client} from "../../libraries/Client.sol"; -import {OnRampSetup} from "../onRamp/OnRampSetup.t.sol"; +import {OnRampSetup} from "../onRamp/onRamp/OnRampSetup.t.sol"; import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; import {ERC165Checker} from diff --git a/contracts/src/v0.8/ccip/test/applications/PingPongDemo.t.sol b/contracts/src/v0.8/ccip/test/applications/PingPongDemo.t.sol index 308e2c087c4..f645bd88cb5 100644 --- a/contracts/src/v0.8/ccip/test/applications/PingPongDemo.t.sol +++ b/contracts/src/v0.8/ccip/test/applications/PingPongDemo.t.sol @@ -5,7 +5,7 @@ import {PingPongDemo} from "../../applications/PingPongDemo.sol"; import {Client} from "../../libraries/Client.sol"; import {Internal} from "../../libraries/Internal.sol"; import {OnRamp} from "../../onRamp/OnRamp.sol"; -import {OnRampSetup} from "../onRamp/OnRampSetup.t.sol"; +import {OnRampSetup} from "../onRamp/onRamp/OnRampSetup.t.sol"; import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/src/v0.8/ccip/test/attacks/onRamp/OnRampTokenPoolReentrancy.t.sol b/contracts/src/v0.8/ccip/test/attacks/onRamp/OnRampTokenPoolReentrancy.t.sol index 3f262e2feb3..c50d86cad7d 100644 --- a/contracts/src/v0.8/ccip/test/attacks/onRamp/OnRampTokenPoolReentrancy.t.sol +++ b/contracts/src/v0.8/ccip/test/attacks/onRamp/OnRampTokenPoolReentrancy.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.24; import {Client} from "../../../libraries/Client.sol"; import {OnRamp} from "../../../onRamp/OnRamp.sol"; import {TokenPool} from "../../../pools/TokenPool.sol"; -import {OnRampSetup} from "../../onRamp/OnRampSetup.t.sol"; +import {OnRampSetup} from "../../onRamp/onRamp/OnRampSetup.t.sol"; import {FacadeClient} from "./FacadeClient.sol"; import {ReentrantMaliciousTokenPool} from "./ReentrantMaliciousTokenPool.sol"; diff --git a/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol b/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol index 4a0c05933ca..610bf311cd8 100644 --- a/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol +++ b/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol @@ -16,7 +16,7 @@ import {TokenAdminRegistry} from "../../tokenAdminRegistry/TokenAdminRegistry.so import {MerkleHelper} from "../helpers/MerkleHelper.sol"; import {OnRampHelper} from "../helpers/OnRampHelper.sol"; import {OffRampSetup} from "../offRamp/offRamp/OffRampSetup.t.sol"; -import {OnRampSetup} from "../onRamp/OnRampSetup.t.sol"; +import {OnRampSetup} from "../onRamp/onRamp/OnRampSetup.t.sol"; import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; @@ -28,6 +28,9 @@ import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/tok contract E2E is OnRampSetup, OffRampSetup { using Internal for Internal.Any2EVMRampMessage; + uint256 internal constant TOKEN_AMOUNT_1 = 9; + uint256 internal constant TOKEN_AMOUNT_2 = 7; + Router internal s_sourceRouter2; OnRampHelper internal s_onRamp2; TokenAdminRegistry internal s_tokenAdminRegistry2; @@ -137,9 +140,9 @@ contract E2E is OnRampSetup, OffRampSetup { uint256 expectedFee = s_sourceRouter.getFee(DEST_CHAIN_SELECTOR, _generateTokenMessage()); // Asserts that the tokens have been sent and the fee has been paid. assertEq( - balance0Pre - (messages1.length + messages2.length) * (i_tokenAmount0 + expectedFee), token0.balanceOf(OWNER) + balance0Pre - (messages1.length + messages2.length) * (TOKEN_AMOUNT_1 + expectedFee), token0.balanceOf(OWNER) ); - assertEq(balance1Pre - (messages1.length + messages2.length) * i_tokenAmount1, token1.balanceOf(OWNER)); + assertEq(balance1Pre - (messages1.length + messages2.length) * TOKEN_AMOUNT_2, token1.balanceOf(OWNER)); } // Commit @@ -215,7 +218,7 @@ contract E2E is OnRampSetup, OffRampSetup { vm.recordLogs(); _execute(reports); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR, messages1[0].header.sequenceNumber, messages1[0].header.messageId, @@ -224,7 +227,7 @@ contract E2E is OnRampSetup, OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR, messages1[1].header.sequenceNumber, messages1[1].header.messageId, @@ -233,7 +236,7 @@ contract E2E is OnRampSetup, OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR + 1, messages2[0].header.sequenceNumber, messages2[0].header.messageId, @@ -252,8 +255,8 @@ contract E2E is OnRampSetup, OffRampSetup { TokenAdminRegistry tokenAdminRegistry ) public returns (Internal.Any2EVMRampMessage memory) { Client.EVM2AnyMessage memory message = _generateTokenMessage(); - IERC20(s_sourceTokens[0]).approve(address(router), i_tokenAmount0 + router.getFee(DEST_CHAIN_SELECTOR, message)); - IERC20(s_sourceTokens[1]).approve(address(router), i_tokenAmount1); + IERC20(s_sourceTokens[0]).approve(address(router), TOKEN_AMOUNT_1 + router.getFee(DEST_CHAIN_SELECTOR, message)); + IERC20(s_sourceTokens[1]).approve(address(router), TOKEN_AMOUNT_2); uint256 feeAmount = router.getFee(DEST_CHAIN_SELECTOR, message); @@ -306,4 +309,17 @@ contract E2E is OnRampSetup, OffRampSetup { tokenAmounts: any2EVMTokenTransfer }); } + + function _generateTokenMessage() public view returns (Client.EVM2AnyMessage memory) { + Client.EVMTokenAmount[] memory tokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); + tokenAmounts[0].amount = TOKEN_AMOUNT_1; + tokenAmounts[1].amount = TOKEN_AMOUNT_2; + return Client.EVM2AnyMessage({ + receiver: abi.encode(OWNER), + data: "", + tokenAmounts: tokenAmounts, + feeToken: s_sourceFeeToken, + extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT})) + }); + } } diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyDestChainConfigUpdates.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyDestChainConfigUpdates.t.sol new file mode 100644 index 00000000000..44fe0e33eba --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyDestChainConfigUpdates.t.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_applyDestChainConfigUpdates is FeeQuoterSetup { + function test_Fuzz_applyDestChainConfigUpdates_Success( + FeeQuoter.DestChainConfigArgs memory destChainConfigArgs + ) public { + vm.assume(destChainConfigArgs.destChainSelector != 0); + vm.assume(destChainConfigArgs.destChainConfig.maxPerMsgGasLimit != 0); + destChainConfigArgs.destChainConfig.defaultTxGasLimit = uint32( + bound( + destChainConfigArgs.destChainConfig.defaultTxGasLimit, 1, destChainConfigArgs.destChainConfig.maxPerMsgGasLimit + ) + ); + destChainConfigArgs.destChainConfig.chainFamilySelector = Internal.CHAIN_FAMILY_SELECTOR_EVM; + + bool isNewChain = destChainConfigArgs.destChainSelector != DEST_CHAIN_SELECTOR; + + FeeQuoter.DestChainConfigArgs[] memory newDestChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](1); + newDestChainConfigArgs[0] = destChainConfigArgs; + + if (isNewChain) { + vm.expectEmit(); + emit FeeQuoter.DestChainAdded(destChainConfigArgs.destChainSelector, destChainConfigArgs.destChainConfig); + } else { + vm.expectEmit(); + emit FeeQuoter.DestChainConfigUpdated(destChainConfigArgs.destChainSelector, destChainConfigArgs.destChainConfig); + } + + s_feeQuoter.applyDestChainConfigUpdates(newDestChainConfigArgs); + + _assertFeeQuoterDestChainConfigsEqual( + destChainConfigArgs.destChainConfig, s_feeQuoter.getDestChainConfig(destChainConfigArgs.destChainSelector) + ); + } + + function test_applyDestChainConfigUpdates_Success() public { + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](2); + destChainConfigArgs[0] = _generateFeeQuoterDestChainConfigArgs()[0]; + destChainConfigArgs[0].destChainConfig.isEnabled = false; + destChainConfigArgs[1] = _generateFeeQuoterDestChainConfigArgs()[0]; + destChainConfigArgs[1].destChainSelector = DEST_CHAIN_SELECTOR + 1; + + vm.expectEmit(); + emit FeeQuoter.DestChainConfigUpdated(DEST_CHAIN_SELECTOR, destChainConfigArgs[0].destChainConfig); + vm.expectEmit(); + emit FeeQuoter.DestChainAdded(DEST_CHAIN_SELECTOR + 1, destChainConfigArgs[1].destChainConfig); + + vm.recordLogs(); + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + FeeQuoter.DestChainConfig memory gotDestChainConfig0 = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + FeeQuoter.DestChainConfig memory gotDestChainConfig1 = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR + 1); + + assertEq(vm.getRecordedLogs().length, 2); + _assertFeeQuoterDestChainConfigsEqual(destChainConfigArgs[0].destChainConfig, gotDestChainConfig0); + _assertFeeQuoterDestChainConfigsEqual(destChainConfigArgs[1].destChainConfig, gotDestChainConfig1); + } + + function test_applyDestChainConfigUpdatesZeroInput_Success() public { + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](0); + + vm.recordLogs(); + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + assertEq(vm.getRecordedLogs().length, 0); + } + + // Reverts + + function test_applyDestChainConfigUpdatesDefaultTxGasLimitEqZero_Revert() public { + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; + + destChainConfigArg.destChainConfig.defaultTxGasLimit = 0; + vm.expectRevert( + abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) + ); + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + } + + function test_applyDestChainConfigUpdatesDefaultTxGasLimitGtMaxPerMessageGasLimit_Revert() public { + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; + + // Allow setting to the max value + destChainConfigArg.destChainConfig.defaultTxGasLimit = destChainConfigArg.destChainConfig.maxPerMsgGasLimit; + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + // Revert when exceeding max value + destChainConfigArg.destChainConfig.defaultTxGasLimit = destChainConfigArg.destChainConfig.maxPerMsgGasLimit + 1; + vm.expectRevert( + abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) + ); + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + } + + function test_InvalidDestChainConfigDestChainSelectorEqZero_Revert() public { + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; + + destChainConfigArg.destChainSelector = 0; + vm.expectRevert( + abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) + ); + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + } + + function test_InvalidChainFamilySelector_Revert() public { + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; + + destChainConfigArg.destChainConfig.chainFamilySelector = bytes4(uint32(1)); + + vm.expectRevert( + abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) + ); + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyFeeTokensUpdates.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyFeeTokensUpdates.t.sol new file mode 100644 index 00000000000..a32e50bb3e4 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyFeeTokensUpdates.t.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_applyFeeTokensUpdates is FeeQuoterSetup { + function test_ApplyFeeTokensUpdates_Success() public { + address[] memory feeTokens = new address[](1); + feeTokens[0] = s_sourceTokens[1]; + + vm.expectEmit(); + emit FeeQuoter.FeeTokenAdded(feeTokens[0]); + + s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); + assertEq(s_feeQuoter.getFeeTokens().length, 3); + assertEq(s_feeQuoter.getFeeTokens()[2], feeTokens[0]); + + // add same feeToken is no-op + s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); + assertEq(s_feeQuoter.getFeeTokens().length, 3); + assertEq(s_feeQuoter.getFeeTokens()[2], feeTokens[0]); + + vm.expectEmit(); + emit FeeQuoter.FeeTokenRemoved(feeTokens[0]); + + s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); + assertEq(s_feeQuoter.getFeeTokens().length, 2); + + // removing already removed feeToken is no-op and does not emit an event + vm.recordLogs(); + + s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); + assertEq(s_feeQuoter.getFeeTokens().length, 2); + + vm.assertEq(vm.getRecordedLogs().length, 0); + + // Removing and adding the same fee token is allowed and emits both events + // Add it first + s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); + + vm.expectEmit(); + emit FeeQuoter.FeeTokenRemoved(feeTokens[0]); + vm.expectEmit(); + emit FeeQuoter.FeeTokenAdded(feeTokens[0]); + + s_feeQuoter.applyFeeTokensUpdates(feeTokens, feeTokens); + } + + function test_OnlyCallableByOwner_Revert() public { + vm.startPrank(STRANGER); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + + s_feeQuoter.applyFeeTokensUpdates(new address[](0), new address[](0)); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyPremiumMultiplierWeiPerEthUpdates.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyPremiumMultiplierWeiPerEthUpdates.t.sol new file mode 100644 index 00000000000..d31202e8a99 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyPremiumMultiplierWeiPerEthUpdates.t.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_applyPremiumMultiplierWeiPerEthUpdates is FeeQuoterSetup { + function test_Fuzz_applyPremiumMultiplierWeiPerEthUpdates_Success( + FeeQuoter.PremiumMultiplierWeiPerEthArgs memory premiumMultiplierWeiPerEthArg + ) public { + FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs = + new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](1); + premiumMultiplierWeiPerEthArgs[0] = premiumMultiplierWeiPerEthArg; + + vm.expectEmit(); + emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( + premiumMultiplierWeiPerEthArg.token, premiumMultiplierWeiPerEthArg.premiumMultiplierWeiPerEth + ); + + s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); + + assertEq( + premiumMultiplierWeiPerEthArg.premiumMultiplierWeiPerEth, + s_feeQuoter.getPremiumMultiplierWeiPerEth(premiumMultiplierWeiPerEthArg.token) + ); + } + + function test_applyPremiumMultiplierWeiPerEthUpdatesSingleToken_Success() public { + FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs = + new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](1); + premiumMultiplierWeiPerEthArgs[0] = s_feeQuoterPremiumMultiplierWeiPerEthArgs[0]; + premiumMultiplierWeiPerEthArgs[0].token = vm.addr(1); + + vm.expectEmit(); + emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( + vm.addr(1), premiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth + ); + + s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); + + assertEq( + s_feeQuoterPremiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth, + s_feeQuoter.getPremiumMultiplierWeiPerEth(vm.addr(1)) + ); + } + + function test_applyPremiumMultiplierWeiPerEthUpdatesMultipleTokens_Success() public { + FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs = + new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](2); + premiumMultiplierWeiPerEthArgs[0] = s_feeQuoterPremiumMultiplierWeiPerEthArgs[0]; + premiumMultiplierWeiPerEthArgs[0].token = vm.addr(1); + premiumMultiplierWeiPerEthArgs[1].token = vm.addr(2); + + vm.expectEmit(); + emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( + vm.addr(1), premiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth + ); + vm.expectEmit(); + emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( + vm.addr(2), premiumMultiplierWeiPerEthArgs[1].premiumMultiplierWeiPerEth + ); + + s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); + + assertEq( + premiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth, + s_feeQuoter.getPremiumMultiplierWeiPerEth(vm.addr(1)) + ); + assertEq( + premiumMultiplierWeiPerEthArgs[1].premiumMultiplierWeiPerEth, + s_feeQuoter.getPremiumMultiplierWeiPerEth(vm.addr(2)) + ); + } + + function test_applyPremiumMultiplierWeiPerEthUpdatesZeroInput() public { + vm.recordLogs(); + s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](0)); + + assertEq(vm.getRecordedLogs().length, 0); + } + + // Reverts + + function test_OnlyCallableByOwnerOrAdmin_Revert() public { + FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs; + vm.startPrank(STRANGER); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + + s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyTokenTransferFeeConfigUpdates.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyTokenTransferFeeConfigUpdates.t.sol new file mode 100644 index 00000000000..72ef7b89a75 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyTokenTransferFeeConfigUpdates.t.sol @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Pool} from "../../libraries/Pool.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_applyTokenTransferFeeConfigUpdates is FeeQuoterSetup { + function test_Fuzz_ApplyTokenTransferFeeConfig_Success( + FeeQuoter.TokenTransferFeeConfig[2] memory tokenTransferFeeConfigs + ) public { + // To prevent Invalid Fee Range error from the fuzzer, bound the results to a valid range that + // where minFee < maxFee + tokenTransferFeeConfigs[0].minFeeUSDCents = + uint32(bound(tokenTransferFeeConfigs[0].minFeeUSDCents, 0, type(uint8).max)); + tokenTransferFeeConfigs[1].minFeeUSDCents = + uint32(bound(tokenTransferFeeConfigs[1].minFeeUSDCents, 0, type(uint8).max)); + + tokenTransferFeeConfigs[0].maxFeeUSDCents = uint32( + bound(tokenTransferFeeConfigs[0].maxFeeUSDCents, tokenTransferFeeConfigs[0].minFeeUSDCents + 1, type(uint32).max) + ); + tokenTransferFeeConfigs[1].maxFeeUSDCents = uint32( + bound(tokenTransferFeeConfigs[1].maxFeeUSDCents, tokenTransferFeeConfigs[1].minFeeUSDCents + 1, type(uint32).max) + ); + + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(2, 2); + tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + tokenTransferFeeConfigArgs[1].destChainSelector = DEST_CHAIN_SELECTOR + 1; + + for (uint256 i = 0; i < tokenTransferFeeConfigArgs.length; ++i) { + for (uint256 j = 0; j < tokenTransferFeeConfigs.length; ++j) { + tokenTransferFeeConfigs[j].destBytesOverhead = uint32( + bound(tokenTransferFeeConfigs[j].destBytesOverhead, Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES, type(uint32).max) + ); + address feeToken = s_sourceTokens[j]; + tokenTransferFeeConfigArgs[i].tokenTransferFeeConfigs[j].token = feeToken; + tokenTransferFeeConfigArgs[i].tokenTransferFeeConfigs[j].tokenTransferFeeConfig = tokenTransferFeeConfigs[j]; + + vm.expectEmit(); + emit FeeQuoter.TokenTransferFeeConfigUpdated( + tokenTransferFeeConfigArgs[i].destChainSelector, feeToken, tokenTransferFeeConfigs[j] + ); + } + } + + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + + for (uint256 i = 0; i < tokenTransferFeeConfigs.length; ++i) { + _assertTokenTransferFeeConfigEqual( + tokenTransferFeeConfigs[i], + s_feeQuoter.getTokenTransferFeeConfig( + tokenTransferFeeConfigArgs[0].destChainSelector, + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[i].token + ) + ); + } + } + + function test_ApplyTokenTransferFeeConfig_Success() public { + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 2); + tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = address(5); + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ + minFeeUSDCents: 6, + maxFeeUSDCents: 7, + deciBps: 8, + destGasOverhead: 9, + destBytesOverhead: 312, + isEnabled: true + }); + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token = address(11); + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ + minFeeUSDCents: 12, + maxFeeUSDCents: 13, + deciBps: 14, + destGasOverhead: 15, + destBytesOverhead: 394, + isEnabled: true + }); + + vm.expectEmit(); + emit FeeQuoter.TokenTransferFeeConfigUpdated( + tokenTransferFeeConfigArgs[0].destChainSelector, + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token, + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig + ); + vm.expectEmit(); + emit FeeQuoter.TokenTransferFeeConfigUpdated( + tokenTransferFeeConfigArgs[0].destChainSelector, + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token, + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig + ); + + FeeQuoter.TokenTransferFeeConfigRemoveArgs[] memory tokensToRemove = + new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0); + s_feeQuoter.applyTokenTransferFeeConfigUpdates(tokenTransferFeeConfigArgs, tokensToRemove); + + FeeQuoter.TokenTransferFeeConfig memory config0 = s_feeQuoter.getTokenTransferFeeConfig( + tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token + ); + FeeQuoter.TokenTransferFeeConfig memory config1 = s_feeQuoter.getTokenTransferFeeConfig( + tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token + ); + + _assertTokenTransferFeeConfigEqual( + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig, config0 + ); + _assertTokenTransferFeeConfigEqual( + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig, config1 + ); + + // Remove only the first token and validate only the first token is removed + tokensToRemove = new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](1); + tokensToRemove[0] = FeeQuoter.TokenTransferFeeConfigRemoveArgs({ + destChainSelector: tokenTransferFeeConfigArgs[0].destChainSelector, + token: tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token + }); + + vm.expectEmit(); + emit FeeQuoter.TokenTransferFeeConfigDeleted( + tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token + ); + + s_feeQuoter.applyTokenTransferFeeConfigUpdates(new FeeQuoter.TokenTransferFeeConfigArgs[](0), tokensToRemove); + + config0 = s_feeQuoter.getTokenTransferFeeConfig( + tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token + ); + config1 = s_feeQuoter.getTokenTransferFeeConfig( + tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token + ); + + FeeQuoter.TokenTransferFeeConfig memory emptyConfig; + + _assertTokenTransferFeeConfigEqual(emptyConfig, config0); + _assertTokenTransferFeeConfigEqual( + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig, config1 + ); + } + + function test_ApplyTokenTransferFeeZeroInput() public { + vm.recordLogs(); + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + new FeeQuoter.TokenTransferFeeConfigArgs[](0), new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + + assertEq(vm.getRecordedLogs().length, 0); + } + + // Reverts + + function test_OnlyCallableByOwnerOrAdmin_Revert() public { + vm.startPrank(STRANGER); + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs; + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + } + + function test_InvalidDestBytesOverhead_Revert() public { + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); + tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = address(5); + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ + minFeeUSDCents: 6, + maxFeeUSDCents: 7, + deciBps: 8, + destGasOverhead: 9, + destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES - 1), + isEnabled: true + }); + + vm.expectRevert( + abi.encodeWithSelector( + FeeQuoter.InvalidDestBytesOverhead.selector, + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token, + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.destBytesOverhead + ) + ); + + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.constructor.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.constructor.t.sol new file mode 100644 index 00000000000..c2d36bee96d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.constructor.t.sol @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {FeeQuoterHelper} from "../helpers/FeeQuoterHelper.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_constructor is FeeQuoterSetup { + function test_Setup_Success() public virtual { + address[] memory priceUpdaters = new address[](2); + priceUpdaters[0] = STRANGER; + priceUpdaters[1] = OWNER; + address[] memory feeTokens = new address[](2); + feeTokens[0] = s_sourceTokens[0]; + feeTokens[1] = s_sourceTokens[1]; + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](2); + tokenPriceFeedUpdates[0] = + _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); + tokenPriceFeedUpdates[1] = + _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[1], s_dataFeedByToken[s_sourceTokens[1]], 6); + + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + + FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ + linkToken: s_sourceTokens[0], + maxFeeJuelsPerMsg: MAX_MSG_FEES_JUELS, + tokenPriceStalenessThreshold: uint32(TWELVE_HOURS) + }); + s_feeQuoter = new FeeQuoterHelper( + staticConfig, + priceUpdaters, + feeTokens, + tokenPriceFeedUpdates, + s_feeQuoterTokenTransferFeeConfigArgs, + s_feeQuoterPremiumMultiplierWeiPerEthArgs, + destChainConfigArgs + ); + + _assertFeeQuoterStaticConfigsEqual(s_feeQuoter.getStaticConfig(), staticConfig); + assertEq(feeTokens, s_feeQuoter.getFeeTokens()); + assertEq(priceUpdaters, s_feeQuoter.getAllAuthorizedCallers()); + assertEq(s_feeQuoter.typeAndVersion(), "FeeQuoter 1.6.0-dev"); + + _assertTokenPriceFeedConfigEquality( + tokenPriceFeedUpdates[0].feedConfig, s_feeQuoter.getTokenPriceFeedConfig(s_sourceTokens[0]) + ); + + _assertTokenPriceFeedConfigEquality( + tokenPriceFeedUpdates[1].feedConfig, s_feeQuoter.getTokenPriceFeedConfig(s_sourceTokens[1]) + ); + + assertEq( + s_feeQuoterPremiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth, + s_feeQuoter.getPremiumMultiplierWeiPerEth(s_feeQuoterPremiumMultiplierWeiPerEthArgs[0].token) + ); + + assertEq( + s_feeQuoterPremiumMultiplierWeiPerEthArgs[1].premiumMultiplierWeiPerEth, + s_feeQuoter.getPremiumMultiplierWeiPerEth(s_feeQuoterPremiumMultiplierWeiPerEthArgs[1].token) + ); + + FeeQuoter.TokenTransferFeeConfigArgs memory tokenTransferFeeConfigArg = s_feeQuoterTokenTransferFeeConfigArgs[0]; + for (uint256 i = 0; i < tokenTransferFeeConfigArg.tokenTransferFeeConfigs.length; ++i) { + FeeQuoter.TokenTransferFeeConfigSingleTokenArgs memory tokenFeeArgs = + s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[i]; + + _assertTokenTransferFeeConfigEqual( + tokenFeeArgs.tokenTransferFeeConfig, + s_feeQuoter.getTokenTransferFeeConfig(tokenTransferFeeConfigArg.destChainSelector, tokenFeeArgs.token) + ); + } + + for (uint256 i = 0; i < destChainConfigArgs.length; ++i) { + FeeQuoter.DestChainConfig memory expectedConfig = destChainConfigArgs[i].destChainConfig; + uint64 destChainSelector = destChainConfigArgs[i].destChainSelector; + + _assertFeeQuoterDestChainConfigsEqual(expectedConfig, s_feeQuoter.getDestChainConfig(destChainSelector)); + } + } + + function test_InvalidStalenessThreshold_Revert() public { + FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ + linkToken: s_sourceTokens[0], + maxFeeJuelsPerMsg: MAX_MSG_FEES_JUELS, + tokenPriceStalenessThreshold: 0 + }); + + vm.expectRevert(FeeQuoter.InvalidStaticConfig.selector); + + s_feeQuoter = new FeeQuoterHelper( + staticConfig, + new address[](0), + new address[](0), + new FeeQuoter.TokenPriceFeedUpdate[](0), + s_feeQuoterTokenTransferFeeConfigArgs, + s_feeQuoterPremiumMultiplierWeiPerEthArgs, + new FeeQuoter.DestChainConfigArgs[](0) + ); + } + + function test_InvalidLinkTokenEqZeroAddress_Revert() public { + FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ + linkToken: address(0), + maxFeeJuelsPerMsg: MAX_MSG_FEES_JUELS, + tokenPriceStalenessThreshold: uint32(TWELVE_HOURS) + }); + + vm.expectRevert(FeeQuoter.InvalidStaticConfig.selector); + + s_feeQuoter = new FeeQuoterHelper( + staticConfig, + new address[](0), + new address[](0), + new FeeQuoter.TokenPriceFeedUpdate[](0), + s_feeQuoterTokenTransferFeeConfigArgs, + s_feeQuoterPremiumMultiplierWeiPerEthArgs, + new FeeQuoter.DestChainConfigArgs[](0) + ); + } + + function test_InvalidMaxFeeJuelsPerMsg_Revert() public { + FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ + linkToken: s_sourceTokens[0], + maxFeeJuelsPerMsg: 0, + tokenPriceStalenessThreshold: uint32(TWELVE_HOURS) + }); + + vm.expectRevert(FeeQuoter.InvalidStaticConfig.selector); + + s_feeQuoter = new FeeQuoterHelper( + staticConfig, + new address[](0), + new address[](0), + new FeeQuoter.TokenPriceFeedUpdate[](0), + s_feeQuoterTokenTransferFeeConfigArgs, + s_feeQuoterPremiumMultiplierWeiPerEthArgs, + new FeeQuoter.DestChainConfigArgs[](0) + ); + } + + function _assertFeeQuoterStaticConfigsEqual( + FeeQuoter.StaticConfig memory a, + FeeQuoter.StaticConfig memory b + ) internal pure { + assertEq(a.linkToken, b.linkToken); + assertEq(a.maxFeeJuelsPerMsg, b.maxFeeJuelsPerMsg); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.convertTokenAmount.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.convertTokenAmount.t.sol new file mode 100644 index 00000000000..ca6a7e16126 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.convertTokenAmount.t.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_convertTokenAmount is FeeQuoterSetup { + function test_ConvertTokenAmount_Success() public view { + Internal.PriceUpdates memory initialPriceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); + uint256 amount = 3e16; + uint256 conversionRate = (uint256(initialPriceUpdates.tokenPriceUpdates[2].usdPerToken) * 1e18) + / uint256(initialPriceUpdates.tokenPriceUpdates[0].usdPerToken); + uint256 expected = (amount * conversionRate) / 1e18; + assertEq(s_feeQuoter.convertTokenAmount(s_weth, amount, s_sourceTokens[0]), expected); + } + + function test_Fuzz_ConvertTokenAmount_Success( + uint256 feeTokenAmount, + uint224 usdPerFeeToken, + uint160 usdPerLinkToken, + uint224 usdPerUnitGas + ) public { + vm.assume(usdPerFeeToken > 0); + vm.assume(usdPerLinkToken > 0); + // We bound the max fees to be at most uint96.max link. + feeTokenAmount = bound(feeTokenAmount, 0, (uint256(type(uint96).max) * usdPerLinkToken) / usdPerFeeToken); + + address feeToken = address(1); + address linkToken = address(2); + address[] memory feeTokens = new address[](1); + feeTokens[0] = feeToken; + s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); + + Internal.TokenPriceUpdate[] memory tokenPriceUpdates = new Internal.TokenPriceUpdate[](2); + tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: feeToken, usdPerToken: usdPerFeeToken}); + tokenPriceUpdates[1] = Internal.TokenPriceUpdate({sourceToken: linkToken, usdPerToken: usdPerLinkToken}); + + Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](1); + gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: DEST_CHAIN_SELECTOR, usdPerUnitGas: usdPerUnitGas}); + + Internal.PriceUpdates memory priceUpdates = + Internal.PriceUpdates({tokenPriceUpdates: tokenPriceUpdates, gasPriceUpdates: gasPriceUpdates}); + + s_feeQuoter.updatePrices(priceUpdates); + + uint256 linkFee = s_feeQuoter.convertTokenAmount(feeToken, feeTokenAmount, linkToken); + assertEq(linkFee, (feeTokenAmount * usdPerFeeToken) / usdPerLinkToken); + } + + // Reverts + + function test_LinkTokenNotSupported_Revert() public { + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, DUMMY_CONTRACT_ADDRESS)); + s_feeQuoter.convertTokenAmount(DUMMY_CONTRACT_ADDRESS, 3e16, s_sourceTokens[0]); + + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, DUMMY_CONTRACT_ADDRESS)); + s_feeQuoter.convertTokenAmount(s_sourceTokens[0], 3e16, DUMMY_CONTRACT_ADDRESS); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getDataAvailabilityCost.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getDataAvailabilityCost.t.sol new file mode 100644 index 00000000000..2e498746c3d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getDataAvailabilityCost.t.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_getDataAvailabilityCost is FeeQuoterSetup { + function test_EmptyMessageCalculatesDataAvailabilityCost_Success() public { + uint256 dataAvailabilityCostUSD = + s_feeQuoter.getDataAvailabilityCost(DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, 0, 0, 0); + + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + + uint256 dataAvailabilityGas = destChainConfig.destDataAvailabilityOverheadGas + + destChainConfig.destGasPerDataAvailabilityByte * Internal.MESSAGE_FIXED_BYTES; + uint256 expectedDataAvailabilityCostUSD = + USD_PER_DATA_AVAILABILITY_GAS * dataAvailabilityGas * destChainConfig.destDataAvailabilityMultiplierBps * 1e14; + + assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD); + + // Test that the cost is destination chain specific + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + destChainConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR + 1; + destChainConfigArgs[0].destChainConfig.destDataAvailabilityOverheadGas = + destChainConfig.destDataAvailabilityOverheadGas * 2; + destChainConfigArgs[0].destChainConfig.destGasPerDataAvailabilityByte = + destChainConfig.destGasPerDataAvailabilityByte * 2; + destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = + destChainConfig.destDataAvailabilityMultiplierBps * 2; + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR + 1); + uint256 dataAvailabilityCostUSD2 = + s_feeQuoter.getDataAvailabilityCost(DEST_CHAIN_SELECTOR + 1, USD_PER_DATA_AVAILABILITY_GAS, 0, 0, 0); + dataAvailabilityGas = destChainConfig.destDataAvailabilityOverheadGas + + destChainConfig.destGasPerDataAvailabilityByte * Internal.MESSAGE_FIXED_BYTES; + expectedDataAvailabilityCostUSD = + USD_PER_DATA_AVAILABILITY_GAS * dataAvailabilityGas * destChainConfig.destDataAvailabilityMultiplierBps * 1e14; + + assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD2); + assertFalse(dataAvailabilityCostUSD == dataAvailabilityCostUSD2); + } + + function test_SimpleMessageCalculatesDataAvailabilityCost_Success() public view { + uint256 dataAvailabilityCostUSD = + s_feeQuoter.getDataAvailabilityCost(DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, 100, 5, 50); + + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + + uint256 dataAvailabilityLengthBytes = + Internal.MESSAGE_FIXED_BYTES + 100 + (5 * Internal.MESSAGE_FIXED_BYTES_PER_TOKEN) + 50; + uint256 dataAvailabilityGas = destChainConfig.destDataAvailabilityOverheadGas + + destChainConfig.destGasPerDataAvailabilityByte * dataAvailabilityLengthBytes; + uint256 expectedDataAvailabilityCostUSD = + USD_PER_DATA_AVAILABILITY_GAS * dataAvailabilityGas * destChainConfig.destDataAvailabilityMultiplierBps * 1e14; + + assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD); + } + + function test_SimpleMessageCalculatesDataAvailabilityCostUnsupportedDestChainSelector_Success() public view { + uint256 dataAvailabilityCostUSD = s_feeQuoter.getDataAvailabilityCost(0, USD_PER_DATA_AVAILABILITY_GAS, 100, 5, 50); + + assertEq(dataAvailabilityCostUSD, 0); + } + + function test_Fuzz_ZeroDataAvailabilityGasPriceAlwaysCalculatesZeroDataAvailabilityCost_Success( + uint64 messageDataLength, + uint32 numberOfTokens, + uint32 tokenTransferBytesOverhead + ) public view { + uint256 dataAvailabilityCostUSD = s_feeQuoter.getDataAvailabilityCost( + DEST_CHAIN_SELECTOR, 0, messageDataLength, numberOfTokens, tokenTransferBytesOverhead + ); + + assertEq(0, dataAvailabilityCostUSD); + } + + function test_Fuzz_CalculateDataAvailabilityCost_Success( + uint64 destChainSelector, + uint32 destDataAvailabilityOverheadGas, + uint16 destGasPerDataAvailabilityByte, + uint16 destDataAvailabilityMultiplierBps, + uint112 dataAvailabilityGasPrice, + uint64 messageDataLength, + uint32 numberOfTokens, + uint32 tokenTransferBytesOverhead + ) public { + vm.assume(destChainSelector != 0); + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](1); + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(destChainSelector); + destChainConfigArgs[0] = + FeeQuoter.DestChainConfigArgs({destChainSelector: destChainSelector, destChainConfig: destChainConfig}); + destChainConfigArgs[0].destChainConfig.destDataAvailabilityOverheadGas = destDataAvailabilityOverheadGas; + destChainConfigArgs[0].destChainConfig.destGasPerDataAvailabilityByte = destGasPerDataAvailabilityByte; + destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = destDataAvailabilityMultiplierBps; + destChainConfigArgs[0].destChainConfig.defaultTxGasLimit = GAS_LIMIT; + destChainConfigArgs[0].destChainConfig.maxPerMsgGasLimit = GAS_LIMIT; + destChainConfigArgs[0].destChainConfig.chainFamilySelector = Internal.CHAIN_FAMILY_SELECTOR_EVM; + + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + uint256 dataAvailabilityCostUSD = s_feeQuoter.getDataAvailabilityCost( + destChainConfigArgs[0].destChainSelector, + dataAvailabilityGasPrice, + messageDataLength, + numberOfTokens, + tokenTransferBytesOverhead + ); + + uint256 dataAvailabilityLengthBytes = Internal.MESSAGE_FIXED_BYTES + messageDataLength + + (numberOfTokens * Internal.MESSAGE_FIXED_BYTES_PER_TOKEN) + tokenTransferBytesOverhead; + + uint256 dataAvailabilityGas = + destDataAvailabilityOverheadGas + destGasPerDataAvailabilityByte * dataAvailabilityLengthBytes; + uint256 expectedDataAvailabilityCostUSD = + dataAvailabilityGasPrice * dataAvailabilityGas * destDataAvailabilityMultiplierBps * 1e14; + + assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenAndGasPrices.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenAndGasPrices.t.sol new file mode 100644 index 00000000000..18ccf5efa79 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenAndGasPrices.t.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_getTokenAndGasPrices is FeeQuoterSetup { + function test_GetFeeTokenAndGasPrices_Success() public view { + (uint224 feeTokenPrice, uint224 gasPrice) = s_feeQuoter.getTokenAndGasPrices(s_sourceFeeToken, DEST_CHAIN_SELECTOR); + + Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); + + assertEq(feeTokenPrice, s_sourceTokenPrices[0]); + assertEq(gasPrice, priceUpdates.gasPriceUpdates[0].usdPerUnitGas); + } + + function test_StalenessCheckDisabled_Success() public { + uint64 neverStaleChainSelector = 345678; + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + destChainConfigArgs[0].destChainSelector = neverStaleChainSelector; + destChainConfigArgs[0].destChainConfig.gasPriceStalenessThreshold = 0; // disables the staleness check + + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](1); + gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: neverStaleChainSelector, usdPerUnitGas: 999}); + + Internal.PriceUpdates memory priceUpdates = + Internal.PriceUpdates({tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), gasPriceUpdates: gasPriceUpdates}); + s_feeQuoter.updatePrices(priceUpdates); + + // this should have no affect! But we do it anyway to make sure the staleness check is disabled + vm.warp(block.timestamp + 52_000_000 weeks); // 1M-ish years + + (, uint224 gasPrice) = s_feeQuoter.getTokenAndGasPrices(s_sourceFeeToken, neverStaleChainSelector); + + assertEq(gasPrice, 999); + } + + function test_ZeroGasPrice_Success() public { + uint64 zeroGasDestChainSelector = 345678; + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + destChainConfigArgs[0].destChainSelector = zeroGasDestChainSelector; + + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](1); + gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: zeroGasDestChainSelector, usdPerUnitGas: 0}); + + Internal.PriceUpdates memory priceUpdates = + Internal.PriceUpdates({tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), gasPriceUpdates: gasPriceUpdates}); + s_feeQuoter.updatePrices(priceUpdates); + + (, uint224 gasPrice) = s_feeQuoter.getTokenAndGasPrices(s_sourceFeeToken, zeroGasDestChainSelector); + + assertEq(gasPrice, 0); + } + + function test_UnsupportedChain_Revert() public { + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.DestinationChainNotEnabled.selector, DEST_CHAIN_SELECTOR + 1)); + s_feeQuoter.getTokenAndGasPrices(s_sourceTokens[0], DEST_CHAIN_SELECTOR + 1); + } + + function test_StaleGasPrice_Revert() public { + uint256 diff = TWELVE_HOURS + 1; + vm.warp(block.timestamp + diff); + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.StaleGasPrice.selector, DEST_CHAIN_SELECTOR, TWELVE_HOURS, diff)); + s_feeQuoter.getTokenAndGasPrices(s_sourceTokens[0], DEST_CHAIN_SELECTOR); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenPrice.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenPrice.t.sol new file mode 100644 index 00000000000..c00e750d27f --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenPrice.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {MockV3Aggregator} from "../../../tests/MockV3Aggregator.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_getTokenPrice is FeeQuoterSetup { + function test_GetTokenPriceFromFeed_Success() public { + uint256 originalTimestampValue = block.timestamp; + + // Above staleness threshold + vm.warp(originalTimestampValue + s_feeQuoter.getStaticConfig().tokenPriceStalenessThreshold + 1); + + address sourceToken = _initialiseSingleTokenPriceFeed(); + + vm.expectCall(s_dataFeedByToken[sourceToken], abi.encodeWithSelector(MockV3Aggregator.latestRoundData.selector)); + + Internal.TimestampedPackedUint224 memory tokenPriceAnswer = s_feeQuoter.getTokenPrice(sourceToken); + + // Price answer is 1e8 (18 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 + assertEq(tokenPriceAnswer.value, uint224(1e18)); + assertEq(tokenPriceAnswer.timestamp, uint32(originalTimestampValue)); + } + + function test_GetTokenPrice_LocalMoreRecent_Success() public { + uint256 originalTimestampValue = block.timestamp; + + Internal.PriceUpdates memory update = Internal.PriceUpdates({ + tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), + gasPriceUpdates: new Internal.GasPriceUpdate[](0) + }); + + update.tokenPriceUpdates[0] = + Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: uint32(originalTimestampValue + 5)}); + + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated( + update.tokenPriceUpdates[0].sourceToken, update.tokenPriceUpdates[0].usdPerToken, block.timestamp + ); + + s_feeQuoter.updatePrices(update); + + vm.warp(originalTimestampValue + s_feeQuoter.getStaticConfig().tokenPriceStalenessThreshold + 10); + + Internal.TimestampedPackedUint224 memory tokenPriceAnswer = s_feeQuoter.getTokenPrice(s_sourceTokens[0]); + + //Assert that the returned price is the local price, not the oracle price + assertEq(tokenPriceAnswer.value, update.tokenPriceUpdates[0].usdPerToken); + assertEq(tokenPriceAnswer.timestamp, uint32(originalTimestampValue)); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenPrices.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenPrices.t.sol new file mode 100644 index 00000000000..63f936332fd --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenPrices.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_getTokenPrices is FeeQuoterSetup { + function test_GetTokenPrices_Success() public view { + Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); + + address[] memory tokens = new address[](3); + tokens[0] = s_sourceTokens[0]; + tokens[1] = s_sourceTokens[1]; + tokens[2] = s_weth; + + Internal.TimestampedPackedUint224[] memory tokenPrices = s_feeQuoter.getTokenPrices(tokens); + + assertEq(tokenPrices.length, 3); + assertEq(tokenPrices[0].value, priceUpdates.tokenPriceUpdates[0].usdPerToken); + assertEq(tokenPrices[1].value, priceUpdates.tokenPriceUpdates[1].usdPerToken); + assertEq(tokenPrices[2].value, priceUpdates.tokenPriceUpdates[2].usdPerToken); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenTransferCost.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenTransferCost.t.sol new file mode 100644 index 00000000000..a426775ca63 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getTokenTransferCost.t.sol @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Client} from "../../libraries/Client.sol"; +import {Pool} from "../../libraries/Pool.sol"; +import {USDPriceWith18Decimals} from "../../libraries/USDPriceWith18Decimals.sol"; +import {FeeQuoterFeeSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_getTokenTransferCost is FeeQuoterFeeSetup { + using USDPriceWith18Decimals for uint224; + + address internal s_selfServeTokenDefaultPricing = makeAddr("self-serve-token-default-pricing"); + + function test_NoTokenTransferChargesZeroFee_Success() public view { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + assertEq(0, feeUSDWei); + assertEq(0, destGasOverhead); + assertEq(0, destBytesOverhead); + } + + function test_getTokenTransferCost_selfServeUsesDefaults_Success() public view { + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_selfServeTokenDefaultPricing, 1000); + + // Get config to assert it isn't set + FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); + + assertFalse(transferFeeConfig.isEnabled); + + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + // Assert that the default values are used + assertEq(uint256(DEFAULT_TOKEN_FEE_USD_CENTS) * 1e16, feeUSDWei); + assertEq(DEFAULT_TOKEN_DEST_GAS_OVERHEAD, destGasOverhead); + assertEq(DEFAULT_TOKEN_BYTES_OVERHEAD, destBytesOverhead); + } + + function test_SmallTokenTransferChargesMinFeeAndGas_Success() public view { + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 1000); + FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); + + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + assertEq(_configUSDCentToWei(transferFeeConfig.minFeeUSDCents), feeUSDWei); + assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); + assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); + } + + function test_ZeroAmountTokenTransferChargesMinFeeAndGas_Success() public view { + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 0); + FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); + + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + assertEq(_configUSDCentToWei(transferFeeConfig.minFeeUSDCents), feeUSDWei); + assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); + assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); + } + + function test_LargeTokenTransferChargesMaxFeeAndGas_Success() public view { + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 1e36); + FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); + + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + assertEq(_configUSDCentToWei(transferFeeConfig.maxFeeUSDCents), feeUSDWei); + assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); + assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); + } + + function test_FeeTokenBpsFee_Success() public view { + uint256 tokenAmount = 10000e18; + + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, tokenAmount); + FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); + + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + uint256 usdWei = _calcUSDValueFromTokenAmount(s_feeTokenPrice, tokenAmount); + uint256 bpsUSDWei = _applyBpsRatio( + usdWei, s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.deciBps + ); + + assertEq(bpsUSDWei, feeUSDWei); + assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); + assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); + } + + function test_CustomTokenBpsFee_Success() public view { + uint256 tokenAmount = 200000e18; + + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(OWNER), + data: "", + tokenAmounts: new Client.EVMTokenAmount[](1), + feeToken: s_sourceFeeToken, + extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT})) + }); + message.tokenAmounts[0] = Client.EVMTokenAmount({token: CUSTOM_TOKEN, amount: tokenAmount}); + + FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); + + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + uint256 usdWei = _calcUSDValueFromTokenAmount(CUSTOM_TOKEN_PRICE, tokenAmount); + uint256 bpsUSDWei = _applyBpsRatio( + usdWei, s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig.deciBps + ); + + assertEq(bpsUSDWei, feeUSDWei); + assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); + assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); + } + + function test_ZeroFeeConfigChargesMinFee_Success() public { + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); + tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = s_sourceFeeToken; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ + minFeeUSDCents: 0, + maxFeeUSDCents: 1, + deciBps: 0, + destGasOverhead: 0, + destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES), + isEnabled: true + }); + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 1e36); + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); + + // if token charges 0 bps, it should cost minFee to transfer + assertEq( + _configUSDCentToWei( + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.minFeeUSDCents + ), + feeUSDWei + ); + assertEq(0, destGasOverhead); + assertEq(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES, destBytesOverhead); + } + + function test_Fuzz_TokenTransferFeeDuplicateTokens_Success(uint256 transfers, uint256 amount) public view { + // It shouldn't be possible to pay materially lower fees by splitting up the transfers. + // Note it is possible to pay higher fees since the minimum fees are added. + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + transfers = bound(transfers, 1, destChainConfig.maxNumberOfTokensPerMsg); + // Cap amount to avoid overflow + amount = bound(amount, 0, 1e36); + Client.EVMTokenAmount[] memory multiple = new Client.EVMTokenAmount[](transfers); + for (uint256 i = 0; i < transfers; ++i) { + multiple[i] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: amount}); + } + Client.EVMTokenAmount[] memory single = new Client.EVMTokenAmount[](1); + single[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: amount * transfers}); + + address feeToken = s_sourceRouter.getWrappedNative(); + + (uint256 feeSingleUSDWei, uint32 gasOverheadSingle, uint32 bytesOverheadSingle) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, feeToken, s_wrappedTokenPrice, single); + (uint256 feeMultipleUSDWei, uint32 gasOverheadMultiple, uint32 bytesOverheadMultiple) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, feeToken, s_wrappedTokenPrice, multiple); + + // Note that there can be a rounding error once per split. + assertGe(feeMultipleUSDWei, (feeSingleUSDWei - destChainConfig.maxNumberOfTokensPerMsg)); + assertEq(gasOverheadMultiple, gasOverheadSingle * transfers); + assertEq(bytesOverheadMultiple, bytesOverheadSingle * transfers); + } + + function test_MixedTokenTransferFee_Success() public view { + address[3] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative(), CUSTOM_TOKEN]; + uint224[3] memory tokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice, CUSTOM_TOKEN_PRICE]; + FeeQuoter.TokenTransferFeeConfig[3] memory tokenTransferFeeConfigs = [ + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[0]), + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[1]), + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[2]) + ]; + + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(OWNER), + data: "", + tokenAmounts: new Client.EVMTokenAmount[](3), + feeToken: s_sourceRouter.getWrappedNative(), + extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT})) + }); + uint256 expectedTotalGas = 0; + uint256 expectedTotalBytes = 0; + + // Start with small token transfers, total bps fee is lower than min token transfer fee + for (uint256 i = 0; i < testTokens.length; ++i) { + message.tokenAmounts[i] = Client.EVMTokenAmount({token: testTokens[i], amount: 1e14}); + FeeQuoter.TokenTransferFeeConfig memory tokenTransferFeeConfig = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[i]); + + expectedTotalGas += tokenTransferFeeConfig.destGasOverhead == 0 + ? DEFAULT_TOKEN_DEST_GAS_OVERHEAD + : tokenTransferFeeConfig.destGasOverhead; + expectedTotalBytes += tokenTransferFeeConfig.destBytesOverhead == 0 + ? DEFAULT_TOKEN_BYTES_OVERHEAD + : tokenTransferFeeConfig.destBytesOverhead; + } + (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_wrappedTokenPrice, message.tokenAmounts); + + uint256 expectedFeeUSDWei = 0; + for (uint256 i = 0; i < testTokens.length; ++i) { + expectedFeeUSDWei += _configUSDCentToWei( + tokenTransferFeeConfigs[i].minFeeUSDCents == 0 + ? DEFAULT_TOKEN_FEE_USD_CENTS + : tokenTransferFeeConfigs[i].minFeeUSDCents + ); + } + + assertEq(expectedFeeUSDWei, feeUSDWei, "wrong feeUSDWei 1"); + assertEq(expectedTotalGas, destGasOverhead, "wrong destGasOverhead 1"); + assertEq(expectedTotalBytes, destBytesOverhead, "wrong destBytesOverhead 1"); + + // Set 1st token transfer to a meaningful amount so its bps fee is now between min and max fee + message.tokenAmounts[0] = Client.EVMTokenAmount({token: testTokens[0], amount: 10000e18}); + + uint256 token0USDWei = _applyBpsRatio( + _calcUSDValueFromTokenAmount(tokenPrices[0], message.tokenAmounts[0].amount), tokenTransferFeeConfigs[0].deciBps + ); + uint256 token1USDWei = _configUSDCentToWei(DEFAULT_TOKEN_FEE_USD_CENTS); + + (feeUSDWei, destGasOverhead, destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_wrappedTokenPrice, message.tokenAmounts); + expectedFeeUSDWei = token0USDWei + token1USDWei + _configUSDCentToWei(tokenTransferFeeConfigs[2].minFeeUSDCents); + + assertEq(expectedFeeUSDWei, feeUSDWei, "wrong feeUSDWei 2"); + assertEq(expectedTotalGas, destGasOverhead, "wrong destGasOverhead 2"); + assertEq(expectedTotalBytes, destBytesOverhead, "wrong destBytesOverhead 2"); + + // Set 2nd token transfer to a large amount that is higher than maxFeeUSD + message.tokenAmounts[2] = Client.EVMTokenAmount({token: testTokens[2], amount: 1e36}); + + (feeUSDWei, destGasOverhead, destBytesOverhead) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_wrappedTokenPrice, message.tokenAmounts); + expectedFeeUSDWei = token0USDWei + token1USDWei + _configUSDCentToWei(tokenTransferFeeConfigs[2].maxFeeUSDCents); + + assertEq(expectedFeeUSDWei, feeUSDWei, "wrong feeUSDWei 3"); + assertEq(expectedTotalGas, destGasOverhead, "wrong destGasOverhead 3"); + assertEq(expectedTotalBytes, destBytesOverhead, "wrong destBytesOverhead 3"); + } + + function _applyBpsRatio(uint256 tokenAmount, uint16 ratio) internal pure returns (uint256) { + return (tokenAmount * ratio) / 1e5; + } + + function _calcUSDValueFromTokenAmount(uint224 tokenPrice, uint256 tokenAmount) internal pure returns (uint256) { + return (tokenPrice * tokenAmount) / 1e18; + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedFee.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedFee.t.sol new file mode 100644 index 00000000000..fdafde91f62 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedFee.t.sol @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Client} from "../../libraries/Client.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {Pool} from "../../libraries/Pool.sol"; +import {USDPriceWith18Decimals} from "../../libraries/USDPriceWith18Decimals.sol"; +import {FeeQuoterFeeSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_getValidatedFee is FeeQuoterFeeSetup { + using USDPriceWith18Decimals for uint224; + + function test_EmptyMessage_Success() public view { + address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; + uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; + + for (uint256 i = 0; i < feeTokenPrices.length; ++i) { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = testTokens[i]; + uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + + uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + + uint256 gasUsed = GAS_LIMIT + DEST_GAS_OVERHEAD; + uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); + uint256 messageFeeUSD = (_configUSDCentToWei(destChainConfig.networkFeeUSDCents) * premiumMultiplierWeiPerEth); + uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( + DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, message.data.length, message.tokenAmounts.length, 0 + ); + + uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; + assertEq(totalPriceInFeeToken, feeAmount); + } + } + + function test_ZeroDataAvailabilityMultiplier_Success() public { + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](1); + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + destChainConfigArgs[0] = + FeeQuoter.DestChainConfigArgs({destChainSelector: DEST_CHAIN_SELECTOR, destChainConfig: destChainConfig}); + destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = 0; + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); + + uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + + uint256 gasUsed = GAS_LIMIT + DEST_GAS_OVERHEAD; + uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); + uint256 messageFeeUSD = (_configUSDCentToWei(destChainConfig.networkFeeUSDCents) * premiumMultiplierWeiPerEth); + + uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD) / s_feeTokenPrice; + assertEq(totalPriceInFeeToken, feeAmount); + } + + function test_HighGasMessage_Success() public view { + address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; + uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; + + uint256 customGasLimit = MAX_GAS_LIMIT; + uint256 customDataSize = MAX_DATA_SIZE; + for (uint256 i = 0; i < feeTokenPrices.length; ++i) { + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(OWNER), + data: new bytes(customDataSize), + tokenAmounts: new Client.EVMTokenAmount[](0), + feeToken: testTokens[i], + extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: customGasLimit})) + }); + + uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + + uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + uint256 gasUsed = customGasLimit + DEST_GAS_OVERHEAD + customDataSize * DEST_GAS_PER_PAYLOAD_BYTE; + uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); + uint256 messageFeeUSD = (_configUSDCentToWei(destChainConfig.networkFeeUSDCents) * premiumMultiplierWeiPerEth); + uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( + DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, message.data.length, message.tokenAmounts.length, 0 + ); + + uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; + assertEq(totalPriceInFeeToken, feeAmount); + } + } + + function test_SingleTokenMessage_Success() public view { + address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; + uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; + + uint256 tokenAmount = 10000e18; + for (uint256 i = 0; i < feeTokenPrices.length; ++i) { + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, tokenAmount); + message.feeToken = testTokens[i]; + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + uint32 destBytesOverhead = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token).destBytesOverhead; + uint32 tokenBytesOverhead = + destBytesOverhead == 0 ? uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) : destBytesOverhead; + + uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + + uint256 gasUsed = GAS_LIMIT + DEST_GAS_OVERHEAD + + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token).destGasOverhead; + uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); + (uint256 transferFeeUSD,,) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, feeTokenPrices[i], message.tokenAmounts); + uint256 messageFeeUSD = (transferFeeUSD * s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken)); + uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( + DEST_CHAIN_SELECTOR, + USD_PER_DATA_AVAILABILITY_GAS, + message.data.length, + message.tokenAmounts.length, + tokenBytesOverhead + ); + + uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; + assertEq(totalPriceInFeeToken, feeAmount); + } + } + + function test_MessageWithDataAndTokenTransfer_Success() public view { + address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; + uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; + + uint256 customGasLimit = 1_000_000; + for (uint256 i = 0; i < feeTokenPrices.length; ++i) { + Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ + receiver: abi.encode(OWNER), + data: "", + tokenAmounts: new Client.EVMTokenAmount[](2), + feeToken: testTokens[i], + extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: customGasLimit})) + }); + uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); + FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); + + message.tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceFeeToken, amount: 10000e18}); // feeTokenAmount + message.tokenAmounts[1] = Client.EVMTokenAmount({token: CUSTOM_TOKEN, amount: 200000e18}); // customTokenAmount + message.data = "random bits and bytes that should be factored into the cost of the message"; + + uint32 tokenGasOverhead = 0; + uint32 tokenBytesOverhead = 0; + for (uint256 j = 0; j < message.tokenAmounts.length; ++j) { + tokenGasOverhead += + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[j].token).destGasOverhead; + uint32 destBytesOverhead = + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[j].token).destBytesOverhead; + tokenBytesOverhead += destBytesOverhead == 0 ? uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) : destBytesOverhead; + } + + uint256 gasUsed = + customGasLimit + DEST_GAS_OVERHEAD + message.data.length * DEST_GAS_PER_PAYLOAD_BYTE + tokenGasOverhead; + uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); + (uint256 transferFeeUSD,,) = + s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, feeTokenPrices[i], message.tokenAmounts); + uint256 messageFeeUSD = (transferFeeUSD * premiumMultiplierWeiPerEth); + uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( + DEST_CHAIN_SELECTOR, + USD_PER_DATA_AVAILABILITY_GAS, + message.data.length, + message.tokenAmounts.length, + tokenBytesOverhead + ); + + uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; + assertEq(totalPriceInFeeToken, s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message)); + } + } + + function test_Fuzz_EnforceOutOfOrder(bool enforce, bool allowOutOfOrderExecution) public { + // Update config to enforce allowOutOfOrderExecution = defaultVal. + vm.stopPrank(); + vm.startPrank(OWNER); + + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + destChainConfigArgs[0].destChainConfig.enforceOutOfOrder = enforce; + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = abi.encodeWithSelector( + Client.EVM_EXTRA_ARGS_V2_TAG, + Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: allowOutOfOrderExecution}) + ); + + // If enforcement is on, only true should be allowed. + if (enforce && !allowOutOfOrderExecution) { + vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); + } + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + } + + // Reverts + + function test_DestinationChainNotEnabled_Revert() public { + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.DestinationChainNotEnabled.selector, DEST_CHAIN_SELECTOR + 1)); + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR + 1, _generateEmptyMessage()); + } + + function test_EnforceOutOfOrder_Revert() public { + // Update config to enforce allowOutOfOrderExecution = true. + vm.stopPrank(); + vm.startPrank(OWNER); + + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + destChainConfigArgs[0].destChainConfig.enforceOutOfOrder = true; + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + vm.stopPrank(); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + // Empty extraArgs to should revert since it enforceOutOfOrder is true. + message.extraArgs = ""; + + vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + } + + function test_MessageTooLarge_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.data = new bytes(MAX_DATA_SIZE + 1); + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.MessageTooLarge.selector, MAX_DATA_SIZE, message.data.length)); + + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + } + + function test_TooManyTokens_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + uint256 tooMany = MAX_TOKENS_LENGTH + 1; + message.tokenAmounts = new Client.EVMTokenAmount[](tooMany); + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.UnsupportedNumberOfTokens.selector, tooMany, MAX_TOKENS_LENGTH)); + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + } + + // Asserts gasLimit must be <=maxGasLimit + function test_MessageGasLimitTooHigh_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: MAX_GAS_LIMIT + 1})); + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.MessageGasLimitTooHigh.selector)); + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + } + + function test_NotAFeeToken_Revert() public { + address notAFeeToken = address(0x111111); + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(notAFeeToken, 1); + message.feeToken = notAFeeToken; + + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.FeeTokenNotSupported.selector, notAFeeToken)); + + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + } + + function test_InvalidEVMAddress_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.receiver = abi.encode(type(uint208).max); + + vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, message.receiver)); + + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedTokenPrice.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedTokenPrice.t.sol new file mode 100644 index 00000000000..6d508bc9116 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedTokenPrice.t.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {MockV3Aggregator} from "../../../tests/MockV3Aggregator.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_getValidatedTokenPrice is FeeQuoterSetup { + function test_GetValidatedTokenPrice_Success() public view { + Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); + address token = priceUpdates.tokenPriceUpdates[0].sourceToken; + + uint224 tokenPrice = s_feeQuoter.getValidatedTokenPrice(token); + + assertEq(priceUpdates.tokenPriceUpdates[0].usdPerToken, tokenPrice); + } + + function test_GetValidatedTokenPriceFromFeed_Success() public { + uint256 originalTimestampValue = block.timestamp; + + // Right below staleness threshold + vm.warp(originalTimestampValue + TWELVE_HOURS); + + address sourceToken = _initialiseSingleTokenPriceFeed(); + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(sourceToken); + + // Price answer is 1e8 (18 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 + assertEq(tokenPriceAnswer, uint224(1e18)); + } + + function test_GetValidatedTokenPriceFromFeedOverStalenessPeriod_Success() public { + uint256 originalTimestampValue = block.timestamp; + + // Right above staleness threshold + vm.warp(originalTimestampValue + TWELVE_HOURS + 1); + + address sourceToken = _initialiseSingleTokenPriceFeed(); + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(sourceToken); + + // Price answer is 1e8 (18 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 + assertEq(tokenPriceAnswer, uint224(1e18)); + } + + function test_GetValidatedTokenPriceFromFeedMaxInt224Value_Success() public { + address tokenAddress = _deploySourceToken("testToken", 0, 18); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, int256(uint256(type(uint224).max))); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); + + // Price answer is: uint224.MAX_VALUE * (10 ** (36 - 18 - 18)) + assertEq(tokenPriceAnswer, uint224(type(uint224).max)); + } + + function test_GetValidatedTokenPriceFromFeedErc20Below18Decimals_Success() public { + address tokenAddress = _deploySourceToken("testToken", 0, 6); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 8, 1e8); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 6); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); + + // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e6) -> expected 1e30 + assertEq(tokenPriceAnswer, uint224(1e30)); + } + + function test_GetValidatedTokenPriceFromFeedErc20Above18Decimals_Success() public { + address tokenAddress = _deploySourceToken("testToken", 0, 24); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 8, 1e8); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 24); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); + + // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e24) -> expected 1e12 + assertEq(tokenPriceAnswer, uint224(1e12)); + } + + function test_GetValidatedTokenPriceFromFeedFeedAt18Decimals_Success() public { + address tokenAddress = _deploySourceToken("testToken", 0, 18); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, 1e18); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); + + // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 + assertEq(tokenPriceAnswer, uint224(1e18)); + } + + function test_GetValidatedTokenPriceFromFeedFeedAt0Decimals_Success() public { + address tokenAddress = _deploySourceToken("testToken", 0, 0); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 0, 1e31); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 0); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); + + // Price answer is 1e31 (0 decimal token) - unit is (1e18 * 1e18 / 1e0) -> expected 1e36 + assertEq(tokenPriceAnswer, uint224(1e67)); + } + + function test_GetValidatedTokenPriceFromFeedFlippedDecimals_Success() public { + address tokenAddress = _deploySourceToken("testToken", 0, 20); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 20, 1e18); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 20); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); + + // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e20) -> expected 1e14 + assertEq(tokenPriceAnswer, uint224(1e14)); + } + + function test_StaleFeeToken_Success() public { + vm.warp(block.timestamp + TWELVE_HOURS + 1); + + Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); + address token = priceUpdates.tokenPriceUpdates[0].sourceToken; + + uint224 tokenPrice = s_feeQuoter.getValidatedTokenPrice(token); + + assertEq(priceUpdates.tokenPriceUpdates[0].usdPerToken, tokenPrice); + } + + // Reverts + + function test_OverflowFeedPrice_Revert() public { + address tokenAddress = _deploySourceToken("testToken", 0, 18); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, int256(uint256(type(uint224).max) + 1)); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + vm.expectRevert(FeeQuoter.DataFeedValueOutOfUint224Range.selector); + s_feeQuoter.getValidatedTokenPrice(tokenAddress); + } + + function test_UnderflowFeedPrice_Revert() public { + address tokenAddress = _deploySourceToken("testToken", 0, 18); + address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, -1); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + vm.expectRevert(FeeQuoter.DataFeedValueOutOfUint224Range.selector); + s_feeQuoter.getValidatedTokenPrice(tokenAddress); + } + + function test_TokenNotSupported_Revert() public { + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, DUMMY_CONTRACT_ADDRESS)); + s_feeQuoter.getValidatedTokenPrice(DUMMY_CONTRACT_ADDRESS); + } + + function test_TokenNotSupportedFeed_Revert() public { + address sourceToken = _initialiseSingleTokenPriceFeed(); + MockV3Aggregator(s_dataFeedByToken[sourceToken]).updateAnswer(0); + Internal.PriceUpdates memory priceUpdates = Internal.PriceUpdates({ + tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), + gasPriceUpdates: new Internal.GasPriceUpdate[](0) + }); + priceUpdates.tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: sourceToken, usdPerToken: 0}); + + s_feeQuoter.updatePrices(priceUpdates); + + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, sourceToken)); + s_feeQuoter.getValidatedTokenPrice(sourceToken); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.onReport.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.onReport.t.sol new file mode 100644 index 00000000000..aba4e178865 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.onReport.t.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {KeystoneFeedsPermissionHandler} from "../../../keystone/KeystoneFeedsPermissionHandler.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_onReport is FeeQuoterSetup { + address internal constant FORWARDER_1 = address(0x1); + address internal constant WORKFLOW_OWNER_1 = address(0x3); + bytes10 internal constant WORKFLOW_NAME_1 = "workflow1"; + bytes2 internal constant REPORT_NAME_1 = "01"; + address internal s_onReportTestToken1; + address internal s_onReportTestToken2; + + function setUp() public virtual override { + super.setUp(); + s_onReportTestToken1 = s_sourceTokens[0]; + s_onReportTestToken2 = _deploySourceToken("onReportTestToken2", 0, 20); + + KeystoneFeedsPermissionHandler.Permission[] memory permissions = new KeystoneFeedsPermissionHandler.Permission[](1); + permissions[0] = KeystoneFeedsPermissionHandler.Permission({ + forwarder: FORWARDER_1, + workflowOwner: WORKFLOW_OWNER_1, + workflowName: WORKFLOW_NAME_1, + reportName: REPORT_NAME_1, + isAllowed: true + }); + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeeds = new FeeQuoter.TokenPriceFeedUpdate[](2); + tokenPriceFeeds[0] = FeeQuoter.TokenPriceFeedUpdate({ + sourceToken: s_onReportTestToken1, + feedConfig: FeeQuoter.TokenPriceFeedConfig({dataFeedAddress: address(0x0), tokenDecimals: 18, isEnabled: true}) + }); + tokenPriceFeeds[1] = FeeQuoter.TokenPriceFeedUpdate({ + sourceToken: s_onReportTestToken2, + feedConfig: FeeQuoter.TokenPriceFeedConfig({dataFeedAddress: address(0x0), tokenDecimals: 20, isEnabled: true}) + }); + s_feeQuoter.setReportPermissions(permissions); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeeds); + } + + function test_onReport_Success() public { + bytes memory encodedPermissionsMetadata = + abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); + + FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](2); + report[0] = + FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp)}); + report[1] = + FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken2, price: 4e18, timestamp: uint32(block.timestamp)}); + + uint224 expectedStoredToken1Price = s_feeQuoter.calculateRebasedValue(18, 18, report[0].price); + uint224 expectedStoredToken2Price = s_feeQuoter.calculateRebasedValue(18, 20, report[1].price); + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken1, expectedStoredToken1Price, block.timestamp); + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken2, expectedStoredToken2Price, block.timestamp); + + changePrank(FORWARDER_1); + s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); + + vm.assertEq(s_feeQuoter.getTokenPrice(report[0].token).value, expectedStoredToken1Price); + vm.assertEq(s_feeQuoter.getTokenPrice(report[0].token).timestamp, report[0].timestamp); + + vm.assertEq(s_feeQuoter.getTokenPrice(report[1].token).value, expectedStoredToken2Price); + vm.assertEq(s_feeQuoter.getTokenPrice(report[1].token).timestamp, report[1].timestamp); + } + + function test_OnReport_StaleUpdate_SkipPriceUpdate_Success() public { + //Creating a correct report + bytes memory encodedPermissionsMetadata = + abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); + + FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); + report[0] = + FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp)}); + + uint224 expectedStoredTokenPrice = s_feeQuoter.calculateRebasedValue(18, 18, report[0].price); + + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken1, expectedStoredTokenPrice, block.timestamp); + + changePrank(FORWARDER_1); + //setting the correct price and time with the correct report + s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); + + //create a stale report + report[0] = FeeQuoter.ReceivedCCIPFeedReport({ + token: s_onReportTestToken1, + price: 4e18, + timestamp: uint32(block.timestamp - 1) + }); + + //record logs to check no events were emitted + vm.recordLogs(); + + s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); + + //no logs should have been emitted + assertEq(vm.getRecordedLogs().length, 0); + } + + function test_onReport_TokenNotSupported_Revert() public { + bytes memory encodedPermissionsMetadata = + abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); + FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); + report[0] = + FeeQuoter.ReceivedCCIPFeedReport({token: s_sourceTokens[1], price: 4e18, timestamp: uint32(block.timestamp)}); + + // Revert due to token config not being set with the isEnabled flag + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, s_sourceTokens[1])); + vm.startPrank(FORWARDER_1); + s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); + } + + function test_onReport_InvalidForwarder_Reverts() public { + bytes memory encodedPermissionsMetadata = + abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); + FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); + report[0] = + FeeQuoter.ReceivedCCIPFeedReport({token: s_sourceTokens[0], price: 4e18, timestamp: uint32(block.timestamp)}); + + vm.expectRevert( + abi.encodeWithSelector( + KeystoneFeedsPermissionHandler.ReportForwarderUnauthorized.selector, + STRANGER, + WORKFLOW_OWNER_1, + WORKFLOW_NAME_1, + REPORT_NAME_1 + ) + ); + changePrank(STRANGER); + s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); + } + + function test_onReport_UnsupportedToken_Reverts() public { + bytes memory encodedPermissionsMetadata = + abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); + FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); + report[0] = + FeeQuoter.ReceivedCCIPFeedReport({token: s_sourceTokens[1], price: 4e18, timestamp: uint32(block.timestamp)}); + + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, s_sourceTokens[1])); + changePrank(FORWARDER_1); + s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.parseEVMExtraArgsFromBytes.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.parseEVMExtraArgsFromBytes.t.sol new file mode 100644 index 00000000000..8f4e3f954ca --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.parseEVMExtraArgsFromBytes.t.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Client} from "../../libraries/Client.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_parseEVMExtraArgsFromBytes is FeeQuoterSetup { + FeeQuoter.DestChainConfig private s_destChainConfig; + + function setUp() public virtual override { + super.setUp(); + s_destChainConfig = _generateFeeQuoterDestChainConfigArgs()[0].destChainConfig; + } + + function test_EVMExtraArgsV1_Success() public view { + Client.EVMExtraArgsV1 memory inputArgs = Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT}); + bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); + Client.EVMExtraArgsV2 memory expectedOutputArgs = + Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: false}); + + vm.assertEq( + abi.encode(s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig)), + abi.encode(expectedOutputArgs) + ); + } + + function test_EVMExtraArgsV2_Success() public view { + Client.EVMExtraArgsV2 memory inputArgs = + Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: true}); + bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); + + vm.assertEq( + abi.encode(s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig)), abi.encode(inputArgs) + ); + } + + function test_EVMExtraArgsDefault_Success() public view { + Client.EVMExtraArgsV2 memory expectedOutputArgs = + Client.EVMExtraArgsV2({gasLimit: s_destChainConfig.defaultTxGasLimit, allowOutOfOrderExecution: false}); + + vm.assertEq( + abi.encode(s_feeQuoter.parseEVMExtraArgsFromBytes("", s_destChainConfig)), abi.encode(expectedOutputArgs) + ); + } + + // Reverts + + function test_EVMExtraArgsInvalidExtraArgsTag_Revert() public { + Client.EVMExtraArgsV2 memory inputArgs = + Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: true}); + bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); + // Invalidate selector + inputExtraArgs[0] = bytes1(uint8(0)); + + vm.expectRevert(FeeQuoter.InvalidExtraArgsTag.selector); + s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); + } + + function test_EVMExtraArgsEnforceOutOfOrder_Revert() public { + Client.EVMExtraArgsV2 memory inputArgs = + Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: false}); + bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); + s_destChainConfig.enforceOutOfOrder = true; + + vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); + s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); + } + + function test_EVMExtraArgsGasLimitTooHigh_Revert() public { + Client.EVMExtraArgsV2 memory inputArgs = + Client.EVMExtraArgsV2({gasLimit: s_destChainConfig.maxPerMsgGasLimit + 1, allowOutOfOrderExecution: true}); + bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); + + vm.expectRevert(FeeQuoter.MessageGasLimitTooHigh.selector); + s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.processMessageArgs.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.processMessageArgs.t.sol new file mode 100644 index 00000000000..65baa576ead --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.processMessageArgs.t.sol @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Client} from "../../libraries/Client.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {Pool} from "../../libraries/Pool.sol"; +import {USDPriceWith18Decimals} from "../../libraries/USDPriceWith18Decimals.sol"; +import {FeeQuoterFeeSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_processMessageArgs is FeeQuoterFeeSetup { + using USDPriceWith18Decimals for uint224; + + function setUp() public virtual override { + super.setUp(); + } + + function test_processMessageArgs_WithLinkTokenAmount_Success() public view { + ( + uint256 msgFeeJuels, + /* bool isOutOfOrderExecution */ + , + /* bytes memory convertedExtraArgs */ + , + /* destExecDataPerToken */ + ) = s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + // LINK + s_sourceTokens[0], + MAX_MSG_FEES_JUELS, + "", + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + + assertEq(msgFeeJuels, MAX_MSG_FEES_JUELS); + } + + function test_processMessageArgs_WithConvertedTokenAmount_Success() public view { + address feeToken = s_sourceTokens[1]; + uint256 feeTokenAmount = 10_000 gwei; + uint256 expectedConvertedAmount = s_feeQuoter.convertTokenAmount(feeToken, feeTokenAmount, s_sourceTokens[0]); + + ( + uint256 msgFeeJuels, + /* bool isOutOfOrderExecution */ + , + /* bytes memory convertedExtraArgs */ + , + /* destExecDataPerToken */ + ) = s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + feeToken, + feeTokenAmount, + "", + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + + assertEq(msgFeeJuels, expectedConvertedAmount); + } + + function test_processMessageArgs_WithEmptyEVMExtraArgs_Success() public view { + ( + /* uint256 msgFeeJuels */ + , + bool isOutOfOrderExecution, + bytes memory convertedExtraArgs, + /* destExecDataPerToken */ + ) = s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + s_sourceTokens[0], + 0, + "", + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + + assertEq(isOutOfOrderExecution, false); + assertEq(convertedExtraArgs, Client._argsToBytes(s_feeQuoter.parseEVMExtraArgsFromBytes("", DEST_CHAIN_SELECTOR))); + } + + function test_processMessageArgs_WithEVMExtraArgsV1_Success() public view { + bytes memory extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 1000})); + + ( + /* uint256 msgFeeJuels */ + , + bool isOutOfOrderExecution, + bytes memory convertedExtraArgs, + /* destExecDataPerToken */ + ) = s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + s_sourceTokens[0], + 0, + extraArgs, + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + + assertEq(isOutOfOrderExecution, false); + assertEq( + convertedExtraArgs, Client._argsToBytes(s_feeQuoter.parseEVMExtraArgsFromBytes(extraArgs, DEST_CHAIN_SELECTOR)) + ); + } + + function test_processMessageArgs_WitEVMExtraArgsV2_Success() public view { + bytes memory extraArgs = Client._argsToBytes(Client.EVMExtraArgsV2({gasLimit: 0, allowOutOfOrderExecution: true})); + + ( + /* uint256 msgFeeJuels */ + , + bool isOutOfOrderExecution, + bytes memory convertedExtraArgs, + /* destExecDataPerToken */ + ) = s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + s_sourceTokens[0], + 0, + extraArgs, + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + + assertEq(isOutOfOrderExecution, true); + assertEq( + convertedExtraArgs, Client._argsToBytes(s_feeQuoter.parseEVMExtraArgsFromBytes(extraArgs, DEST_CHAIN_SELECTOR)) + ); + } + + // Reverts + + function test_processMessageArgs_MessageFeeTooHigh_Revert() public { + vm.expectRevert( + abi.encodeWithSelector(FeeQuoter.MessageFeeTooHigh.selector, MAX_MSG_FEES_JUELS + 1, MAX_MSG_FEES_JUELS) + ); + + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + s_sourceTokens[0], + MAX_MSG_FEES_JUELS + 1, + "", + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + } + + function test_processMessageArgs_InvalidExtraArgs_Revert() public { + vm.expectRevert(FeeQuoter.InvalidExtraArgsTag.selector); + + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + s_sourceTokens[0], + 0, + "wrong extra args", + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + } + + function test_processMessageArgs_MalformedEVMExtraArgs_Revert() public { + // abi.decode error + vm.expectRevert(); + + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + s_sourceTokens[0], + 0, + abi.encodeWithSelector(Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV1({gasLimit: 100})), + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + } + + function test_processMessageArgs_WithCorrectPoolReturnData_Success() public view { + Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](2); + sourceTokenAmounts[0].amount = 1e18; + sourceTokenAmounts[0].token = s_sourceTokens[0]; + sourceTokenAmounts[1].amount = 1e18; + sourceTokenAmounts[1].token = CUSTOM_TOKEN_2; + + Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](2); + tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); + tokenAmounts[1] = _getSourceTokenData(sourceTokenAmounts[1], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); + bytes[] memory expectedDestExecData = new bytes[](2); + expectedDestExecData[0] = abi.encode( + s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.destGasOverhead + ); + expectedDestExecData[1] = abi.encode(DEFAULT_TOKEN_DEST_GAS_OVERHEAD); //expected return data should be abi.encoded default as isEnabled is false + + // No revert - successful + ( /* msgFeeJuels */ , /* isOutOfOrderExecution */, /* convertedExtraArgs */, bytes[] memory destExecData) = + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts + ); + + for (uint256 i = 0; i < destExecData.length; ++i) { + assertEq(destExecData[i], expectedDestExecData[i]); + } + } + + function test_processMessageArgs_TokenAmountArraysMismatching_Revert() public { + Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](2); + sourceTokenAmounts[0].amount = 1e18; + sourceTokenAmounts[0].token = s_sourceTokens[0]; + + Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](1); + tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); + + // Revert due to index out of bounds access + vm.expectRevert(); + + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + s_sourceTokens[0], + MAX_MSG_FEES_JUELS, + "", + new Internal.EVM2AnyTokenTransfer[](1), + new Client.EVMTokenAmount[](0) + ); + } + + function test_applyTokensTransferFeeConfigUpdates_InvalidFeeRange_Revert() public { + address sourceETH = s_sourceTokens[1]; + + // Set token config to allow larger data + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); + tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = sourceETH; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ + minFeeUSDCents: 1, + maxFeeUSDCents: 0, + deciBps: 0, + destGasOverhead: 0, + destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32, + isEnabled: true + }); + + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.InvalidFeeRange.selector, 1, 0)); + + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + } + + function test_processMessageArgs_SourceTokenDataTooLarge_Revert() public { + address sourceETH = s_sourceTokens[1]; + + Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](1); + sourceTokenAmounts[0].amount = 1000; + sourceTokenAmounts[0].token = sourceETH; + + Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](1); + tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); + + // No data set, should succeed + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts + ); + + // Set max data length, should succeed + tokenAmounts[0].extraData = new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES); + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts + ); + + // Set data to max length +1, should revert + tokenAmounts[0].extraData = new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES + 1); + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts + ); + + // Set token config to allow larger data + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); + tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = sourceETH; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ + minFeeUSDCents: 0, + maxFeeUSDCents: 1, + deciBps: 0, + destGasOverhead: 0, + destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32, + isEnabled: true + }); + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts + ); + + // Set the token data larger than the configured token data, should revert + tokenAmounts[0].extraData = new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES + 32 + 1); + + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts + ); + } + + function test_processMessageArgs_InvalidEVMAddressDestToken_Revert() public { + bytes memory nonEvmAddress = abi.encode(type(uint208).max); + + Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](1); + sourceTokenAmounts[0].amount = 1e18; + sourceTokenAmounts[0].token = s_sourceTokens[0]; + + Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](1); + tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); + tokenAmounts[0].destTokenAddress = nonEvmAddress; + + vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, nonEvmAddress)); + s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.t.sol deleted file mode 100644 index 4cd34db04a3..00000000000 --- a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.t.sol +++ /dev/null @@ -1,2378 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {KeystoneFeedsPermissionHandler} from "../../../keystone/KeystoneFeedsPermissionHandler.sol"; -import {AuthorizedCallers} from "../../../shared/access/AuthorizedCallers.sol"; -import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; -import {MockV3Aggregator} from "../../../tests/MockV3Aggregator.sol"; -import {FeeQuoter} from "../../FeeQuoter.sol"; -import {Client} from "../../libraries/Client.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {Pool} from "../../libraries/Pool.sol"; -import {USDPriceWith18Decimals} from "../../libraries/USDPriceWith18Decimals.sol"; -import {FeeQuoterHelper} from "../helpers/FeeQuoterHelper.sol"; -import {FeeQuoterFeeSetup, FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; - -import {Vm} from "forge-std/Vm.sol"; - -contract FeeQuoter_constructor is FeeQuoterSetup { - function test_Setup_Success() public virtual { - address[] memory priceUpdaters = new address[](2); - priceUpdaters[0] = STRANGER; - priceUpdaters[1] = OWNER; - address[] memory feeTokens = new address[](2); - feeTokens[0] = s_sourceTokens[0]; - feeTokens[1] = s_sourceTokens[1]; - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](2); - tokenPriceFeedUpdates[0] = - _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); - tokenPriceFeedUpdates[1] = - _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[1], s_dataFeedByToken[s_sourceTokens[1]], 6); - - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - - FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ - linkToken: s_sourceTokens[0], - maxFeeJuelsPerMsg: MAX_MSG_FEES_JUELS, - tokenPriceStalenessThreshold: uint32(TWELVE_HOURS) - }); - s_feeQuoter = new FeeQuoterHelper( - staticConfig, - priceUpdaters, - feeTokens, - tokenPriceFeedUpdates, - s_feeQuoterTokenTransferFeeConfigArgs, - s_feeQuoterPremiumMultiplierWeiPerEthArgs, - destChainConfigArgs - ); - - _assertFeeQuoterStaticConfigsEqual(s_feeQuoter.getStaticConfig(), staticConfig); - assertEq(feeTokens, s_feeQuoter.getFeeTokens()); - assertEq(priceUpdaters, s_feeQuoter.getAllAuthorizedCallers()); - assertEq(s_feeQuoter.typeAndVersion(), "FeeQuoter 1.6.0-dev"); - - _assertTokenPriceFeedConfigEquality( - tokenPriceFeedUpdates[0].feedConfig, s_feeQuoter.getTokenPriceFeedConfig(s_sourceTokens[0]) - ); - - _assertTokenPriceFeedConfigEquality( - tokenPriceFeedUpdates[1].feedConfig, s_feeQuoter.getTokenPriceFeedConfig(s_sourceTokens[1]) - ); - - assertEq( - s_feeQuoterPremiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth, - s_feeQuoter.getPremiumMultiplierWeiPerEth(s_feeQuoterPremiumMultiplierWeiPerEthArgs[0].token) - ); - - assertEq( - s_feeQuoterPremiumMultiplierWeiPerEthArgs[1].premiumMultiplierWeiPerEth, - s_feeQuoter.getPremiumMultiplierWeiPerEth(s_feeQuoterPremiumMultiplierWeiPerEthArgs[1].token) - ); - - FeeQuoter.TokenTransferFeeConfigArgs memory tokenTransferFeeConfigArg = s_feeQuoterTokenTransferFeeConfigArgs[0]; - for (uint256 i = 0; i < tokenTransferFeeConfigArg.tokenTransferFeeConfigs.length; ++i) { - FeeQuoter.TokenTransferFeeConfigSingleTokenArgs memory tokenFeeArgs = - s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[i]; - - _assertTokenTransferFeeConfigEqual( - tokenFeeArgs.tokenTransferFeeConfig, - s_feeQuoter.getTokenTransferFeeConfig(tokenTransferFeeConfigArg.destChainSelector, tokenFeeArgs.token) - ); - } - - for (uint256 i = 0; i < destChainConfigArgs.length; ++i) { - FeeQuoter.DestChainConfig memory expectedConfig = destChainConfigArgs[i].destChainConfig; - uint64 destChainSelector = destChainConfigArgs[i].destChainSelector; - - _assertFeeQuoterDestChainConfigsEqual(expectedConfig, s_feeQuoter.getDestChainConfig(destChainSelector)); - } - } - - function test_InvalidStalenessThreshold_Revert() public { - FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ - linkToken: s_sourceTokens[0], - maxFeeJuelsPerMsg: MAX_MSG_FEES_JUELS, - tokenPriceStalenessThreshold: 0 - }); - - vm.expectRevert(FeeQuoter.InvalidStaticConfig.selector); - - s_feeQuoter = new FeeQuoterHelper( - staticConfig, - new address[](0), - new address[](0), - new FeeQuoter.TokenPriceFeedUpdate[](0), - s_feeQuoterTokenTransferFeeConfigArgs, - s_feeQuoterPremiumMultiplierWeiPerEthArgs, - new FeeQuoter.DestChainConfigArgs[](0) - ); - } - - function test_InvalidLinkTokenEqZeroAddress_Revert() public { - FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ - linkToken: address(0), - maxFeeJuelsPerMsg: MAX_MSG_FEES_JUELS, - tokenPriceStalenessThreshold: uint32(TWELVE_HOURS) - }); - - vm.expectRevert(FeeQuoter.InvalidStaticConfig.selector); - - s_feeQuoter = new FeeQuoterHelper( - staticConfig, - new address[](0), - new address[](0), - new FeeQuoter.TokenPriceFeedUpdate[](0), - s_feeQuoterTokenTransferFeeConfigArgs, - s_feeQuoterPremiumMultiplierWeiPerEthArgs, - new FeeQuoter.DestChainConfigArgs[](0) - ); - } - - function test_InvalidMaxFeeJuelsPerMsg_Revert() public { - FeeQuoter.StaticConfig memory staticConfig = FeeQuoter.StaticConfig({ - linkToken: s_sourceTokens[0], - maxFeeJuelsPerMsg: 0, - tokenPriceStalenessThreshold: uint32(TWELVE_HOURS) - }); - - vm.expectRevert(FeeQuoter.InvalidStaticConfig.selector); - - s_feeQuoter = new FeeQuoterHelper( - staticConfig, - new address[](0), - new address[](0), - new FeeQuoter.TokenPriceFeedUpdate[](0), - s_feeQuoterTokenTransferFeeConfigArgs, - s_feeQuoterPremiumMultiplierWeiPerEthArgs, - new FeeQuoter.DestChainConfigArgs[](0) - ); - } -} - -contract FeeQuoter_getTokenPrices is FeeQuoterSetup { - function test_GetTokenPrices_Success() public view { - Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); - - address[] memory tokens = new address[](3); - tokens[0] = s_sourceTokens[0]; - tokens[1] = s_sourceTokens[1]; - tokens[2] = s_weth; - - Internal.TimestampedPackedUint224[] memory tokenPrices = s_feeQuoter.getTokenPrices(tokens); - - assertEq(tokenPrices.length, 3); - assertEq(tokenPrices[0].value, priceUpdates.tokenPriceUpdates[0].usdPerToken); - assertEq(tokenPrices[1].value, priceUpdates.tokenPriceUpdates[1].usdPerToken); - assertEq(tokenPrices[2].value, priceUpdates.tokenPriceUpdates[2].usdPerToken); - } -} - -contract FeeQuoter_getTokenPrice is FeeQuoterSetup { - function test_GetTokenPriceFromFeed_Success() public { - uint256 originalTimestampValue = block.timestamp; - - // Above staleness threshold - vm.warp(originalTimestampValue + s_feeQuoter.getStaticConfig().tokenPriceStalenessThreshold + 1); - - address sourceToken = _initialiseSingleTokenPriceFeed(); - - vm.expectCall(s_dataFeedByToken[sourceToken], abi.encodeWithSelector(MockV3Aggregator.latestRoundData.selector)); - - Internal.TimestampedPackedUint224 memory tokenPriceAnswer = s_feeQuoter.getTokenPrice(sourceToken); - - // Price answer is 1e8 (18 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 - assertEq(tokenPriceAnswer.value, uint224(1e18)); - assertEq(tokenPriceAnswer.timestamp, uint32(originalTimestampValue)); - } - - function test_GetTokenPrice_LocalMoreRecent_Success() public { - uint256 originalTimestampValue = block.timestamp; - - Internal.PriceUpdates memory update = Internal.PriceUpdates({ - tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), - gasPriceUpdates: new Internal.GasPriceUpdate[](0) - }); - - update.tokenPriceUpdates[0] = - Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: uint32(originalTimestampValue + 5)}); - - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated( - update.tokenPriceUpdates[0].sourceToken, update.tokenPriceUpdates[0].usdPerToken, block.timestamp - ); - - s_feeQuoter.updatePrices(update); - - vm.warp(originalTimestampValue + s_feeQuoter.getStaticConfig().tokenPriceStalenessThreshold + 10); - - Internal.TimestampedPackedUint224 memory tokenPriceAnswer = s_feeQuoter.getTokenPrice(s_sourceTokens[0]); - - //Assert that the returned price is the local price, not the oracle price - assertEq(tokenPriceAnswer.value, update.tokenPriceUpdates[0].usdPerToken); - assertEq(tokenPriceAnswer.timestamp, uint32(originalTimestampValue)); - } -} - -contract FeeQuoter_getValidatedTokenPrice is FeeQuoterSetup { - function test_GetValidatedTokenPrice_Success() public view { - Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); - address token = priceUpdates.tokenPriceUpdates[0].sourceToken; - - uint224 tokenPrice = s_feeQuoter.getValidatedTokenPrice(token); - - assertEq(priceUpdates.tokenPriceUpdates[0].usdPerToken, tokenPrice); - } - - function test_GetValidatedTokenPriceFromFeed_Success() public { - uint256 originalTimestampValue = block.timestamp; - - // Right below staleness threshold - vm.warp(originalTimestampValue + TWELVE_HOURS); - - address sourceToken = _initialiseSingleTokenPriceFeed(); - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(sourceToken); - - // Price answer is 1e8 (18 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 - assertEq(tokenPriceAnswer, uint224(1e18)); - } - - function test_GetValidatedTokenPriceFromFeedOverStalenessPeriod_Success() public { - uint256 originalTimestampValue = block.timestamp; - - // Right above staleness threshold - vm.warp(originalTimestampValue + TWELVE_HOURS + 1); - - address sourceToken = _initialiseSingleTokenPriceFeed(); - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(sourceToken); - - // Price answer is 1e8 (18 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 - assertEq(tokenPriceAnswer, uint224(1e18)); - } - - function test_GetValidatedTokenPriceFromFeedMaxInt224Value_Success() public { - address tokenAddress = _deploySourceToken("testToken", 0, 18); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, int256(uint256(type(uint224).max))); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); - - // Price answer is: uint224.MAX_VALUE * (10 ** (36 - 18 - 18)) - assertEq(tokenPriceAnswer, uint224(type(uint224).max)); - } - - function test_GetValidatedTokenPriceFromFeedErc20Below18Decimals_Success() public { - address tokenAddress = _deploySourceToken("testToken", 0, 6); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 8, 1e8); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 6); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); - - // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e6) -> expected 1e30 - assertEq(tokenPriceAnswer, uint224(1e30)); - } - - function test_GetValidatedTokenPriceFromFeedErc20Above18Decimals_Success() public { - address tokenAddress = _deploySourceToken("testToken", 0, 24); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 8, 1e8); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 24); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); - - // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e24) -> expected 1e12 - assertEq(tokenPriceAnswer, uint224(1e12)); - } - - function test_GetValidatedTokenPriceFromFeedFeedAt18Decimals_Success() public { - address tokenAddress = _deploySourceToken("testToken", 0, 18); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, 1e18); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); - - // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e18) -> expected 1e18 - assertEq(tokenPriceAnswer, uint224(1e18)); - } - - function test_GetValidatedTokenPriceFromFeedFeedAt0Decimals_Success() public { - address tokenAddress = _deploySourceToken("testToken", 0, 0); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 0, 1e31); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 0); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); - - // Price answer is 1e31 (0 decimal token) - unit is (1e18 * 1e18 / 1e0) -> expected 1e36 - assertEq(tokenPriceAnswer, uint224(1e67)); - } - - function test_GetValidatedTokenPriceFromFeedFlippedDecimals_Success() public { - address tokenAddress = _deploySourceToken("testToken", 0, 20); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 20, 1e18); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 20); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - uint224 tokenPriceAnswer = s_feeQuoter.getValidatedTokenPrice(tokenAddress); - - // Price answer is 1e8 (6 decimal token) - unit is (1e18 * 1e18 / 1e20) -> expected 1e14 - assertEq(tokenPriceAnswer, uint224(1e14)); - } - - function test_StaleFeeToken_Success() public { - vm.warp(block.timestamp + TWELVE_HOURS + 1); - - Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); - address token = priceUpdates.tokenPriceUpdates[0].sourceToken; - - uint224 tokenPrice = s_feeQuoter.getValidatedTokenPrice(token); - - assertEq(priceUpdates.tokenPriceUpdates[0].usdPerToken, tokenPrice); - } - - // Reverts - - function test_OverflowFeedPrice_Revert() public { - address tokenAddress = _deploySourceToken("testToken", 0, 18); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, int256(uint256(type(uint224).max) + 1)); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - vm.expectRevert(FeeQuoter.DataFeedValueOutOfUint224Range.selector); - s_feeQuoter.getValidatedTokenPrice(tokenAddress); - } - - function test_UnderflowFeedPrice_Revert() public { - address tokenAddress = _deploySourceToken("testToken", 0, 18); - address feedAddress = _deployTokenPriceDataFeed(tokenAddress, 18, -1); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = _getSingleTokenPriceFeedUpdateStruct(tokenAddress, feedAddress, 18); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - vm.expectRevert(FeeQuoter.DataFeedValueOutOfUint224Range.selector); - s_feeQuoter.getValidatedTokenPrice(tokenAddress); - } - - function test_TokenNotSupported_Revert() public { - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, DUMMY_CONTRACT_ADDRESS)); - s_feeQuoter.getValidatedTokenPrice(DUMMY_CONTRACT_ADDRESS); - } - - function test_TokenNotSupportedFeed_Revert() public { - address sourceToken = _initialiseSingleTokenPriceFeed(); - MockV3Aggregator(s_dataFeedByToken[sourceToken]).updateAnswer(0); - Internal.PriceUpdates memory priceUpdates = Internal.PriceUpdates({ - tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), - gasPriceUpdates: new Internal.GasPriceUpdate[](0) - }); - priceUpdates.tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: sourceToken, usdPerToken: 0}); - - s_feeQuoter.updatePrices(priceUpdates); - - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, sourceToken)); - s_feeQuoter.getValidatedTokenPrice(sourceToken); - } -} - -contract FeeQuoter_applyFeeTokensUpdates is FeeQuoterSetup { - function test_ApplyFeeTokensUpdates_Success() public { - address[] memory feeTokens = new address[](1); - feeTokens[0] = s_sourceTokens[1]; - - vm.expectEmit(); - emit FeeQuoter.FeeTokenAdded(feeTokens[0]); - - s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); - assertEq(s_feeQuoter.getFeeTokens().length, 3); - assertEq(s_feeQuoter.getFeeTokens()[2], feeTokens[0]); - - // add same feeToken is no-op - s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); - assertEq(s_feeQuoter.getFeeTokens().length, 3); - assertEq(s_feeQuoter.getFeeTokens()[2], feeTokens[0]); - - vm.expectEmit(); - emit FeeQuoter.FeeTokenRemoved(feeTokens[0]); - - s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); - assertEq(s_feeQuoter.getFeeTokens().length, 2); - - // removing already removed feeToken is no-op and does not emit an event - vm.recordLogs(); - - s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); - assertEq(s_feeQuoter.getFeeTokens().length, 2); - - vm.assertEq(vm.getRecordedLogs().length, 0); - - // Removing and adding the same fee token is allowed and emits both events - // Add it first - s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); - - vm.expectEmit(); - emit FeeQuoter.FeeTokenRemoved(feeTokens[0]); - vm.expectEmit(); - emit FeeQuoter.FeeTokenAdded(feeTokens[0]); - - s_feeQuoter.applyFeeTokensUpdates(feeTokens, feeTokens); - } - - function test_OnlyCallableByOwner_Revert() public { - vm.startPrank(STRANGER); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - - s_feeQuoter.applyFeeTokensUpdates(new address[](0), new address[](0)); - } -} - -contract FeeQuoter_updatePrices is FeeQuoterSetup { - function test_OnlyTokenPrice_Success() public { - Internal.PriceUpdates memory update = Internal.PriceUpdates({ - tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), - gasPriceUpdates: new Internal.GasPriceUpdate[](0) - }); - update.tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: 4e18}); - - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated( - update.tokenPriceUpdates[0].sourceToken, update.tokenPriceUpdates[0].usdPerToken, block.timestamp - ); - - s_feeQuoter.updatePrices(update); - - assertEq(s_feeQuoter.getTokenPrice(s_sourceTokens[0]).value, update.tokenPriceUpdates[0].usdPerToken); - } - - function test_OnlyGasPrice_Success() public { - Internal.PriceUpdates memory update = Internal.PriceUpdates({ - tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), - gasPriceUpdates: new Internal.GasPriceUpdate[](1) - }); - update.gasPriceUpdates[0] = - Internal.GasPriceUpdate({destChainSelector: DEST_CHAIN_SELECTOR, usdPerUnitGas: 2000e18}); - - vm.expectEmit(); - emit FeeQuoter.UsdPerUnitGasUpdated( - update.gasPriceUpdates[0].destChainSelector, update.gasPriceUpdates[0].usdPerUnitGas, block.timestamp - ); - - s_feeQuoter.updatePrices(update); - - assertEq( - s_feeQuoter.getDestinationChainGasPrice(DEST_CHAIN_SELECTOR).value, update.gasPriceUpdates[0].usdPerUnitGas - ); - } - - function test_UpdateMultiplePrices_Success() public { - Internal.TokenPriceUpdate[] memory tokenPriceUpdates = new Internal.TokenPriceUpdate[](3); - tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: 4e18}); - tokenPriceUpdates[1] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[1], usdPerToken: 1800e18}); - tokenPriceUpdates[2] = Internal.TokenPriceUpdate({sourceToken: address(12345), usdPerToken: 1e18}); - - Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](3); - gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: DEST_CHAIN_SELECTOR, usdPerUnitGas: 2e6}); - gasPriceUpdates[1] = Internal.GasPriceUpdate({destChainSelector: SOURCE_CHAIN_SELECTOR, usdPerUnitGas: 2000e18}); - gasPriceUpdates[2] = Internal.GasPriceUpdate({destChainSelector: 12345, usdPerUnitGas: 1e18}); - - Internal.PriceUpdates memory update = - Internal.PriceUpdates({tokenPriceUpdates: tokenPriceUpdates, gasPriceUpdates: gasPriceUpdates}); - - for (uint256 i = 0; i < tokenPriceUpdates.length; ++i) { - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated( - update.tokenPriceUpdates[i].sourceToken, update.tokenPriceUpdates[i].usdPerToken, block.timestamp - ); - } - for (uint256 i = 0; i < gasPriceUpdates.length; ++i) { - vm.expectEmit(); - emit FeeQuoter.UsdPerUnitGasUpdated( - update.gasPriceUpdates[i].destChainSelector, update.gasPriceUpdates[i].usdPerUnitGas, block.timestamp - ); - } - - s_feeQuoter.updatePrices(update); - - for (uint256 i = 0; i < tokenPriceUpdates.length; ++i) { - assertEq( - s_feeQuoter.getTokenPrice(update.tokenPriceUpdates[i].sourceToken).value, tokenPriceUpdates[i].usdPerToken - ); - } - for (uint256 i = 0; i < gasPriceUpdates.length; ++i) { - assertEq( - s_feeQuoter.getDestinationChainGasPrice(update.gasPriceUpdates[i].destChainSelector).value, - gasPriceUpdates[i].usdPerUnitGas - ); - } - } - - function test_UpdatableByAuthorizedCaller_Success() public { - Internal.PriceUpdates memory priceUpdates = Internal.PriceUpdates({ - tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), - gasPriceUpdates: new Internal.GasPriceUpdate[](0) - }); - priceUpdates.tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: 4e18}); - - // Revert when caller is not authorized - vm.startPrank(STRANGER); - vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); - s_feeQuoter.updatePrices(priceUpdates); - - address[] memory priceUpdaters = new address[](1); - priceUpdaters[0] = STRANGER; - vm.startPrank(OWNER); - s_feeQuoter.applyAuthorizedCallerUpdates( - AuthorizedCallers.AuthorizedCallerArgs({addedCallers: priceUpdaters, removedCallers: new address[](0)}) - ); - - // Stranger is now an authorized caller to update prices - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated( - priceUpdates.tokenPriceUpdates[0].sourceToken, priceUpdates.tokenPriceUpdates[0].usdPerToken, block.timestamp - ); - s_feeQuoter.updatePrices(priceUpdates); - - assertEq(s_feeQuoter.getTokenPrice(s_sourceTokens[0]).value, priceUpdates.tokenPriceUpdates[0].usdPerToken); - - vm.startPrank(OWNER); - s_feeQuoter.applyAuthorizedCallerUpdates( - AuthorizedCallers.AuthorizedCallerArgs({addedCallers: new address[](0), removedCallers: priceUpdaters}) - ); - - // Revert when authorized caller is removed - vm.startPrank(STRANGER); - vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); - s_feeQuoter.updatePrices(priceUpdates); - } - - // Reverts - - function test_OnlyCallableByUpdater_Revert() public { - Internal.PriceUpdates memory priceUpdates = Internal.PriceUpdates({ - tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), - gasPriceUpdates: new Internal.GasPriceUpdate[](0) - }); - - vm.startPrank(STRANGER); - vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); - s_feeQuoter.updatePrices(priceUpdates); - } -} - -contract FeeQuoter_convertTokenAmount is FeeQuoterSetup { - function test_ConvertTokenAmount_Success() public view { - Internal.PriceUpdates memory initialPriceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); - uint256 amount = 3e16; - uint256 conversionRate = (uint256(initialPriceUpdates.tokenPriceUpdates[2].usdPerToken) * 1e18) - / uint256(initialPriceUpdates.tokenPriceUpdates[0].usdPerToken); - uint256 expected = (amount * conversionRate) / 1e18; - assertEq(s_feeQuoter.convertTokenAmount(s_weth, amount, s_sourceTokens[0]), expected); - } - - function test_Fuzz_ConvertTokenAmount_Success( - uint256 feeTokenAmount, - uint224 usdPerFeeToken, - uint160 usdPerLinkToken, - uint224 usdPerUnitGas - ) public { - vm.assume(usdPerFeeToken > 0); - vm.assume(usdPerLinkToken > 0); - // We bound the max fees to be at most uint96.max link. - feeTokenAmount = bound(feeTokenAmount, 0, (uint256(type(uint96).max) * usdPerLinkToken) / usdPerFeeToken); - - address feeToken = address(1); - address linkToken = address(2); - address[] memory feeTokens = new address[](1); - feeTokens[0] = feeToken; - s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); - - Internal.TokenPriceUpdate[] memory tokenPriceUpdates = new Internal.TokenPriceUpdate[](2); - tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: feeToken, usdPerToken: usdPerFeeToken}); - tokenPriceUpdates[1] = Internal.TokenPriceUpdate({sourceToken: linkToken, usdPerToken: usdPerLinkToken}); - - Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](1); - gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: DEST_CHAIN_SELECTOR, usdPerUnitGas: usdPerUnitGas}); - - Internal.PriceUpdates memory priceUpdates = - Internal.PriceUpdates({tokenPriceUpdates: tokenPriceUpdates, gasPriceUpdates: gasPriceUpdates}); - - s_feeQuoter.updatePrices(priceUpdates); - - uint256 linkFee = s_feeQuoter.convertTokenAmount(feeToken, feeTokenAmount, linkToken); - assertEq(linkFee, (feeTokenAmount * usdPerFeeToken) / usdPerLinkToken); - } - - // Reverts - - function test_LinkTokenNotSupported_Revert() public { - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, DUMMY_CONTRACT_ADDRESS)); - s_feeQuoter.convertTokenAmount(DUMMY_CONTRACT_ADDRESS, 3e16, s_sourceTokens[0]); - - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, DUMMY_CONTRACT_ADDRESS)); - s_feeQuoter.convertTokenAmount(s_sourceTokens[0], 3e16, DUMMY_CONTRACT_ADDRESS); - } -} - -contract FeeQuoter_getTokenAndGasPrices is FeeQuoterSetup { - function test_GetFeeTokenAndGasPrices_Success() public view { - (uint224 feeTokenPrice, uint224 gasPrice) = s_feeQuoter.getTokenAndGasPrices(s_sourceFeeToken, DEST_CHAIN_SELECTOR); - - Internal.PriceUpdates memory priceUpdates = abi.decode(s_encodedInitialPriceUpdates, (Internal.PriceUpdates)); - - assertEq(feeTokenPrice, s_sourceTokenPrices[0]); - assertEq(gasPrice, priceUpdates.gasPriceUpdates[0].usdPerUnitGas); - } - - function test_StalenessCheckDisabled_Success() public { - uint64 neverStaleChainSelector = 345678; - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - destChainConfigArgs[0].destChainSelector = neverStaleChainSelector; - destChainConfigArgs[0].destChainConfig.gasPriceStalenessThreshold = 0; // disables the staleness check - - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](1); - gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: neverStaleChainSelector, usdPerUnitGas: 999}); - - Internal.PriceUpdates memory priceUpdates = - Internal.PriceUpdates({tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), gasPriceUpdates: gasPriceUpdates}); - s_feeQuoter.updatePrices(priceUpdates); - - // this should have no affect! But we do it anyway to make sure the staleness check is disabled - vm.warp(block.timestamp + 52_000_000 weeks); // 1M-ish years - - (, uint224 gasPrice) = s_feeQuoter.getTokenAndGasPrices(s_sourceFeeToken, neverStaleChainSelector); - - assertEq(gasPrice, 999); - } - - function test_ZeroGasPrice_Success() public { - uint64 zeroGasDestChainSelector = 345678; - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - destChainConfigArgs[0].destChainSelector = zeroGasDestChainSelector; - - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](1); - gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: zeroGasDestChainSelector, usdPerUnitGas: 0}); - - Internal.PriceUpdates memory priceUpdates = - Internal.PriceUpdates({tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), gasPriceUpdates: gasPriceUpdates}); - s_feeQuoter.updatePrices(priceUpdates); - - (, uint224 gasPrice) = s_feeQuoter.getTokenAndGasPrices(s_sourceFeeToken, zeroGasDestChainSelector); - - assertEq(gasPrice, 0); - } - - function test_UnsupportedChain_Revert() public { - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.DestinationChainNotEnabled.selector, DEST_CHAIN_SELECTOR + 1)); - s_feeQuoter.getTokenAndGasPrices(s_sourceTokens[0], DEST_CHAIN_SELECTOR + 1); - } - - function test_StaleGasPrice_Revert() public { - uint256 diff = TWELVE_HOURS + 1; - vm.warp(block.timestamp + diff); - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.StaleGasPrice.selector, DEST_CHAIN_SELECTOR, TWELVE_HOURS, diff)); - s_feeQuoter.getTokenAndGasPrices(s_sourceTokens[0], DEST_CHAIN_SELECTOR); - } -} - -contract FeeQuoter_updateTokenPriceFeeds is FeeQuoterSetup { - function test_ZeroFeeds_Success() public { - Vm.Log[] memory logEntries = vm.getRecordedLogs(); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](0); - vm.recordLogs(); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - // Verify no log emissions - assertEq(logEntries.length, 0); - } - - function test_SingleFeedUpdate_Success() public { - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = - _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); - - _assertTokenPriceFeedConfigNotConfigured(s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken)); - - vm.expectEmit(); - emit FeeQuoter.PriceFeedPerTokenUpdated(tokenPriceFeedUpdates[0].sourceToken, tokenPriceFeedUpdates[0].feedConfig); - - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - _assertTokenPriceFeedConfigEquality( - s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig - ); - } - - function test_MultipleFeedUpdate_Success() public { - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](2); - - for (uint256 i = 0; i < 2; ++i) { - tokenPriceFeedUpdates[i] = - _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[i], s_dataFeedByToken[s_sourceTokens[i]], 18); - - _assertTokenPriceFeedConfigNotConfigured( - s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[i].sourceToken) - ); - - vm.expectEmit(); - emit FeeQuoter.PriceFeedPerTokenUpdated(tokenPriceFeedUpdates[i].sourceToken, tokenPriceFeedUpdates[i].feedConfig); - } - - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - _assertTokenPriceFeedConfigEquality( - s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig - ); - _assertTokenPriceFeedConfigEquality( - s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[1].sourceToken), tokenPriceFeedUpdates[1].feedConfig - ); - } - - function test_FeedUnset_Success() public { - Internal.TimestampedPackedUint224 memory priceQueryInitial = s_feeQuoter.getTokenPrice(s_sourceTokens[0]); - assertFalse(priceQueryInitial.value == 0); - assertFalse(priceQueryInitial.timestamp == 0); - - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = - _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); - - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - _assertTokenPriceFeedConfigEquality( - s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig - ); - - tokenPriceFeedUpdates[0].feedConfig.dataFeedAddress = address(0); - vm.expectEmit(); - emit FeeQuoter.PriceFeedPerTokenUpdated(tokenPriceFeedUpdates[0].sourceToken, tokenPriceFeedUpdates[0].feedConfig); - - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - _assertTokenPriceFeedConfigEquality( - s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig - ); - - // Price data should remain after a feed has been set->unset - Internal.TimestampedPackedUint224 memory priceQueryPostUnsetFeed = s_feeQuoter.getTokenPrice(s_sourceTokens[0]); - assertEq(priceQueryPostUnsetFeed.value, priceQueryInitial.value); - assertEq(priceQueryPostUnsetFeed.timestamp, priceQueryInitial.timestamp); - } - - function test_FeedNotUpdated() public { - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = - _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); - - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - - _assertTokenPriceFeedConfigEquality( - s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig - ); - } - - // Reverts - - function test_FeedUpdatedByNonOwner_Revert() public { - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); - tokenPriceFeedUpdates[0] = - _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); - - vm.startPrank(STRANGER); - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); - } -} - -contract FeeQuoter_applyDestChainConfigUpdates is FeeQuoterSetup { - function test_Fuzz_applyDestChainConfigUpdates_Success( - FeeQuoter.DestChainConfigArgs memory destChainConfigArgs - ) public { - vm.assume(destChainConfigArgs.destChainSelector != 0); - vm.assume(destChainConfigArgs.destChainConfig.maxPerMsgGasLimit != 0); - destChainConfigArgs.destChainConfig.defaultTxGasLimit = uint32( - bound( - destChainConfigArgs.destChainConfig.defaultTxGasLimit, 1, destChainConfigArgs.destChainConfig.maxPerMsgGasLimit - ) - ); - destChainConfigArgs.destChainConfig.chainFamilySelector = Internal.CHAIN_FAMILY_SELECTOR_EVM; - - bool isNewChain = destChainConfigArgs.destChainSelector != DEST_CHAIN_SELECTOR; - - FeeQuoter.DestChainConfigArgs[] memory newDestChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](1); - newDestChainConfigArgs[0] = destChainConfigArgs; - - if (isNewChain) { - vm.expectEmit(); - emit FeeQuoter.DestChainAdded(destChainConfigArgs.destChainSelector, destChainConfigArgs.destChainConfig); - } else { - vm.expectEmit(); - emit FeeQuoter.DestChainConfigUpdated(destChainConfigArgs.destChainSelector, destChainConfigArgs.destChainConfig); - } - - s_feeQuoter.applyDestChainConfigUpdates(newDestChainConfigArgs); - - _assertFeeQuoterDestChainConfigsEqual( - destChainConfigArgs.destChainConfig, s_feeQuoter.getDestChainConfig(destChainConfigArgs.destChainSelector) - ); - } - - function test_applyDestChainConfigUpdates_Success() public { - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](2); - destChainConfigArgs[0] = _generateFeeQuoterDestChainConfigArgs()[0]; - destChainConfigArgs[0].destChainConfig.isEnabled = false; - destChainConfigArgs[1] = _generateFeeQuoterDestChainConfigArgs()[0]; - destChainConfigArgs[1].destChainSelector = DEST_CHAIN_SELECTOR + 1; - - vm.expectEmit(); - emit FeeQuoter.DestChainConfigUpdated(DEST_CHAIN_SELECTOR, destChainConfigArgs[0].destChainConfig); - vm.expectEmit(); - emit FeeQuoter.DestChainAdded(DEST_CHAIN_SELECTOR + 1, destChainConfigArgs[1].destChainConfig); - - vm.recordLogs(); - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - FeeQuoter.DestChainConfig memory gotDestChainConfig0 = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - FeeQuoter.DestChainConfig memory gotDestChainConfig1 = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR + 1); - - assertEq(vm.getRecordedLogs().length, 2); - _assertFeeQuoterDestChainConfigsEqual(destChainConfigArgs[0].destChainConfig, gotDestChainConfig0); - _assertFeeQuoterDestChainConfigsEqual(destChainConfigArgs[1].destChainConfig, gotDestChainConfig1); - } - - function test_applyDestChainConfigUpdatesZeroInput_Success() public { - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](0); - - vm.recordLogs(); - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - assertEq(vm.getRecordedLogs().length, 0); - } - - // Reverts - - function test_applyDestChainConfigUpdatesDefaultTxGasLimitEqZero_Revert() public { - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; - - destChainConfigArg.destChainConfig.defaultTxGasLimit = 0; - vm.expectRevert( - abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) - ); - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - } - - function test_applyDestChainConfigUpdatesDefaultTxGasLimitGtMaxPerMessageGasLimit_Revert() public { - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; - - // Allow setting to the max value - destChainConfigArg.destChainConfig.defaultTxGasLimit = destChainConfigArg.destChainConfig.maxPerMsgGasLimit; - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - // Revert when exceeding max value - destChainConfigArg.destChainConfig.defaultTxGasLimit = destChainConfigArg.destChainConfig.maxPerMsgGasLimit + 1; - vm.expectRevert( - abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) - ); - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - } - - function test_InvalidDestChainConfigDestChainSelectorEqZero_Revert() public { - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; - - destChainConfigArg.destChainSelector = 0; - vm.expectRevert( - abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) - ); - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - } - - function test_InvalidChainFamilySelector_Revert() public { - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; - - destChainConfigArg.destChainConfig.chainFamilySelector = bytes4(uint32(1)); - - vm.expectRevert( - abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) - ); - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - } -} - -contract FeeQuoter_getDataAvailabilityCost is FeeQuoterSetup { - function test_EmptyMessageCalculatesDataAvailabilityCost_Success() public { - uint256 dataAvailabilityCostUSD = - s_feeQuoter.getDataAvailabilityCost(DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, 0, 0, 0); - - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - - uint256 dataAvailabilityGas = destChainConfig.destDataAvailabilityOverheadGas - + destChainConfig.destGasPerDataAvailabilityByte * Internal.MESSAGE_FIXED_BYTES; - uint256 expectedDataAvailabilityCostUSD = - USD_PER_DATA_AVAILABILITY_GAS * dataAvailabilityGas * destChainConfig.destDataAvailabilityMultiplierBps * 1e14; - - assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD); - - // Test that the cost is destination chain specific - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - destChainConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR + 1; - destChainConfigArgs[0].destChainConfig.destDataAvailabilityOverheadGas = - destChainConfig.destDataAvailabilityOverheadGas * 2; - destChainConfigArgs[0].destChainConfig.destGasPerDataAvailabilityByte = - destChainConfig.destGasPerDataAvailabilityByte * 2; - destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = - destChainConfig.destDataAvailabilityMultiplierBps * 2; - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR + 1); - uint256 dataAvailabilityCostUSD2 = - s_feeQuoter.getDataAvailabilityCost(DEST_CHAIN_SELECTOR + 1, USD_PER_DATA_AVAILABILITY_GAS, 0, 0, 0); - dataAvailabilityGas = destChainConfig.destDataAvailabilityOverheadGas - + destChainConfig.destGasPerDataAvailabilityByte * Internal.MESSAGE_FIXED_BYTES; - expectedDataAvailabilityCostUSD = - USD_PER_DATA_AVAILABILITY_GAS * dataAvailabilityGas * destChainConfig.destDataAvailabilityMultiplierBps * 1e14; - - assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD2); - assertFalse(dataAvailabilityCostUSD == dataAvailabilityCostUSD2); - } - - function test_SimpleMessageCalculatesDataAvailabilityCost_Success() public view { - uint256 dataAvailabilityCostUSD = - s_feeQuoter.getDataAvailabilityCost(DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, 100, 5, 50); - - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - - uint256 dataAvailabilityLengthBytes = - Internal.MESSAGE_FIXED_BYTES + 100 + (5 * Internal.MESSAGE_FIXED_BYTES_PER_TOKEN) + 50; - uint256 dataAvailabilityGas = destChainConfig.destDataAvailabilityOverheadGas - + destChainConfig.destGasPerDataAvailabilityByte * dataAvailabilityLengthBytes; - uint256 expectedDataAvailabilityCostUSD = - USD_PER_DATA_AVAILABILITY_GAS * dataAvailabilityGas * destChainConfig.destDataAvailabilityMultiplierBps * 1e14; - - assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD); - } - - function test_SimpleMessageCalculatesDataAvailabilityCostUnsupportedDestChainSelector_Success() public view { - uint256 dataAvailabilityCostUSD = s_feeQuoter.getDataAvailabilityCost(0, USD_PER_DATA_AVAILABILITY_GAS, 100, 5, 50); - - assertEq(dataAvailabilityCostUSD, 0); - } - - function test_Fuzz_ZeroDataAvailabilityGasPriceAlwaysCalculatesZeroDataAvailabilityCost_Success( - uint64 messageDataLength, - uint32 numberOfTokens, - uint32 tokenTransferBytesOverhead - ) public view { - uint256 dataAvailabilityCostUSD = s_feeQuoter.getDataAvailabilityCost( - DEST_CHAIN_SELECTOR, 0, messageDataLength, numberOfTokens, tokenTransferBytesOverhead - ); - - assertEq(0, dataAvailabilityCostUSD); - } - - function test_Fuzz_CalculateDataAvailabilityCost_Success( - uint64 destChainSelector, - uint32 destDataAvailabilityOverheadGas, - uint16 destGasPerDataAvailabilityByte, - uint16 destDataAvailabilityMultiplierBps, - uint112 dataAvailabilityGasPrice, - uint64 messageDataLength, - uint32 numberOfTokens, - uint32 tokenTransferBytesOverhead - ) public { - vm.assume(destChainSelector != 0); - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](1); - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(destChainSelector); - destChainConfigArgs[0] = - FeeQuoter.DestChainConfigArgs({destChainSelector: destChainSelector, destChainConfig: destChainConfig}); - destChainConfigArgs[0].destChainConfig.destDataAvailabilityOverheadGas = destDataAvailabilityOverheadGas; - destChainConfigArgs[0].destChainConfig.destGasPerDataAvailabilityByte = destGasPerDataAvailabilityByte; - destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = destDataAvailabilityMultiplierBps; - destChainConfigArgs[0].destChainConfig.defaultTxGasLimit = GAS_LIMIT; - destChainConfigArgs[0].destChainConfig.maxPerMsgGasLimit = GAS_LIMIT; - destChainConfigArgs[0].destChainConfig.chainFamilySelector = Internal.CHAIN_FAMILY_SELECTOR_EVM; - - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - uint256 dataAvailabilityCostUSD = s_feeQuoter.getDataAvailabilityCost( - destChainConfigArgs[0].destChainSelector, - dataAvailabilityGasPrice, - messageDataLength, - numberOfTokens, - tokenTransferBytesOverhead - ); - - uint256 dataAvailabilityLengthBytes = Internal.MESSAGE_FIXED_BYTES + messageDataLength - + (numberOfTokens * Internal.MESSAGE_FIXED_BYTES_PER_TOKEN) + tokenTransferBytesOverhead; - - uint256 dataAvailabilityGas = - destDataAvailabilityOverheadGas + destGasPerDataAvailabilityByte * dataAvailabilityLengthBytes; - uint256 expectedDataAvailabilityCostUSD = - dataAvailabilityGasPrice * dataAvailabilityGas * destDataAvailabilityMultiplierBps * 1e14; - - assertEq(expectedDataAvailabilityCostUSD, dataAvailabilityCostUSD); - } -} - -contract FeeQuoter_applyPremiumMultiplierWeiPerEthUpdates is FeeQuoterSetup { - function test_Fuzz_applyPremiumMultiplierWeiPerEthUpdates_Success( - FeeQuoter.PremiumMultiplierWeiPerEthArgs memory premiumMultiplierWeiPerEthArg - ) public { - FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs = - new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](1); - premiumMultiplierWeiPerEthArgs[0] = premiumMultiplierWeiPerEthArg; - - vm.expectEmit(); - emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( - premiumMultiplierWeiPerEthArg.token, premiumMultiplierWeiPerEthArg.premiumMultiplierWeiPerEth - ); - - s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); - - assertEq( - premiumMultiplierWeiPerEthArg.premiumMultiplierWeiPerEth, - s_feeQuoter.getPremiumMultiplierWeiPerEth(premiumMultiplierWeiPerEthArg.token) - ); - } - - function test_applyPremiumMultiplierWeiPerEthUpdatesSingleToken_Success() public { - FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs = - new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](1); - premiumMultiplierWeiPerEthArgs[0] = s_feeQuoterPremiumMultiplierWeiPerEthArgs[0]; - premiumMultiplierWeiPerEthArgs[0].token = vm.addr(1); - - vm.expectEmit(); - emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( - vm.addr(1), premiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth - ); - - s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); - - assertEq( - s_feeQuoterPremiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth, - s_feeQuoter.getPremiumMultiplierWeiPerEth(vm.addr(1)) - ); - } - - function test_applyPremiumMultiplierWeiPerEthUpdatesMultipleTokens_Success() public { - FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs = - new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](2); - premiumMultiplierWeiPerEthArgs[0] = s_feeQuoterPremiumMultiplierWeiPerEthArgs[0]; - premiumMultiplierWeiPerEthArgs[0].token = vm.addr(1); - premiumMultiplierWeiPerEthArgs[1].token = vm.addr(2); - - vm.expectEmit(); - emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( - vm.addr(1), premiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth - ); - vm.expectEmit(); - emit FeeQuoter.PremiumMultiplierWeiPerEthUpdated( - vm.addr(2), premiumMultiplierWeiPerEthArgs[1].premiumMultiplierWeiPerEth - ); - - s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); - - assertEq( - premiumMultiplierWeiPerEthArgs[0].premiumMultiplierWeiPerEth, - s_feeQuoter.getPremiumMultiplierWeiPerEth(vm.addr(1)) - ); - assertEq( - premiumMultiplierWeiPerEthArgs[1].premiumMultiplierWeiPerEth, - s_feeQuoter.getPremiumMultiplierWeiPerEth(vm.addr(2)) - ); - } - - function test_applyPremiumMultiplierWeiPerEthUpdatesZeroInput() public { - vm.recordLogs(); - s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](0)); - - assertEq(vm.getRecordedLogs().length, 0); - } - - // Reverts - - function test_OnlyCallableByOwnerOrAdmin_Revert() public { - FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory premiumMultiplierWeiPerEthArgs; - vm.startPrank(STRANGER); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - - s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(premiumMultiplierWeiPerEthArgs); - } -} - -contract FeeQuoter_applyTokenTransferFeeConfigUpdates is FeeQuoterSetup { - function test_Fuzz_ApplyTokenTransferFeeConfig_Success( - FeeQuoter.TokenTransferFeeConfig[2] memory tokenTransferFeeConfigs - ) public { - // To prevent Invalid Fee Range error from the fuzzer, bound the results to a valid range that - // where minFee < maxFee - tokenTransferFeeConfigs[0].minFeeUSDCents = - uint32(bound(tokenTransferFeeConfigs[0].minFeeUSDCents, 0, type(uint8).max)); - tokenTransferFeeConfigs[1].minFeeUSDCents = - uint32(bound(tokenTransferFeeConfigs[1].minFeeUSDCents, 0, type(uint8).max)); - - tokenTransferFeeConfigs[0].maxFeeUSDCents = uint32( - bound(tokenTransferFeeConfigs[0].maxFeeUSDCents, tokenTransferFeeConfigs[0].minFeeUSDCents + 1, type(uint32).max) - ); - tokenTransferFeeConfigs[1].maxFeeUSDCents = uint32( - bound(tokenTransferFeeConfigs[1].maxFeeUSDCents, tokenTransferFeeConfigs[1].minFeeUSDCents + 1, type(uint32).max) - ); - - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(2, 2); - tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - tokenTransferFeeConfigArgs[1].destChainSelector = DEST_CHAIN_SELECTOR + 1; - - for (uint256 i = 0; i < tokenTransferFeeConfigArgs.length; ++i) { - for (uint256 j = 0; j < tokenTransferFeeConfigs.length; ++j) { - tokenTransferFeeConfigs[j].destBytesOverhead = uint32( - bound(tokenTransferFeeConfigs[j].destBytesOverhead, Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES, type(uint32).max) - ); - address feeToken = s_sourceTokens[j]; - tokenTransferFeeConfigArgs[i].tokenTransferFeeConfigs[j].token = feeToken; - tokenTransferFeeConfigArgs[i].tokenTransferFeeConfigs[j].tokenTransferFeeConfig = tokenTransferFeeConfigs[j]; - - vm.expectEmit(); - emit FeeQuoter.TokenTransferFeeConfigUpdated( - tokenTransferFeeConfigArgs[i].destChainSelector, feeToken, tokenTransferFeeConfigs[j] - ); - } - } - - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - - for (uint256 i = 0; i < tokenTransferFeeConfigs.length; ++i) { - _assertTokenTransferFeeConfigEqual( - tokenTransferFeeConfigs[i], - s_feeQuoter.getTokenTransferFeeConfig( - tokenTransferFeeConfigArgs[0].destChainSelector, - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[i].token - ) - ); - } - } - - function test_ApplyTokenTransferFeeConfig_Success() public { - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 2); - tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = address(5); - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ - minFeeUSDCents: 6, - maxFeeUSDCents: 7, - deciBps: 8, - destGasOverhead: 9, - destBytesOverhead: 312, - isEnabled: true - }); - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token = address(11); - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ - minFeeUSDCents: 12, - maxFeeUSDCents: 13, - deciBps: 14, - destGasOverhead: 15, - destBytesOverhead: 394, - isEnabled: true - }); - - vm.expectEmit(); - emit FeeQuoter.TokenTransferFeeConfigUpdated( - tokenTransferFeeConfigArgs[0].destChainSelector, - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token, - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig - ); - vm.expectEmit(); - emit FeeQuoter.TokenTransferFeeConfigUpdated( - tokenTransferFeeConfigArgs[0].destChainSelector, - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token, - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig - ); - - FeeQuoter.TokenTransferFeeConfigRemoveArgs[] memory tokensToRemove = - new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0); - s_feeQuoter.applyTokenTransferFeeConfigUpdates(tokenTransferFeeConfigArgs, tokensToRemove); - - FeeQuoter.TokenTransferFeeConfig memory config0 = s_feeQuoter.getTokenTransferFeeConfig( - tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token - ); - FeeQuoter.TokenTransferFeeConfig memory config1 = s_feeQuoter.getTokenTransferFeeConfig( - tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token - ); - - _assertTokenTransferFeeConfigEqual( - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig, config0 - ); - _assertTokenTransferFeeConfigEqual( - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig, config1 - ); - - // Remove only the first token and validate only the first token is removed - tokensToRemove = new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](1); - tokensToRemove[0] = FeeQuoter.TokenTransferFeeConfigRemoveArgs({ - destChainSelector: tokenTransferFeeConfigArgs[0].destChainSelector, - token: tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token - }); - - vm.expectEmit(); - emit FeeQuoter.TokenTransferFeeConfigDeleted( - tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token - ); - - s_feeQuoter.applyTokenTransferFeeConfigUpdates(new FeeQuoter.TokenTransferFeeConfigArgs[](0), tokensToRemove); - - config0 = s_feeQuoter.getTokenTransferFeeConfig( - tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token - ); - config1 = s_feeQuoter.getTokenTransferFeeConfig( - tokenTransferFeeConfigArgs[0].destChainSelector, tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].token - ); - - FeeQuoter.TokenTransferFeeConfig memory emptyConfig; - - _assertTokenTransferFeeConfigEqual(emptyConfig, config0); - _assertTokenTransferFeeConfigEqual( - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig, config1 - ); - } - - function test_ApplyTokenTransferFeeZeroInput() public { - vm.recordLogs(); - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - new FeeQuoter.TokenTransferFeeConfigArgs[](0), new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - - assertEq(vm.getRecordedLogs().length, 0); - } - - // Reverts - - function test_OnlyCallableByOwnerOrAdmin_Revert() public { - vm.startPrank(STRANGER); - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs; - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - } - - function test_InvalidDestBytesOverhead_Revert() public { - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); - tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = address(5); - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ - minFeeUSDCents: 6, - maxFeeUSDCents: 7, - deciBps: 8, - destGasOverhead: 9, - destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES - 1), - isEnabled: true - }); - - vm.expectRevert( - abi.encodeWithSelector( - FeeQuoter.InvalidDestBytesOverhead.selector, - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token, - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.destBytesOverhead - ) - ); - - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - } -} - -contract FeeQuoter_getTokenTransferCost is FeeQuoterFeeSetup { - using USDPriceWith18Decimals for uint224; - - function test_NoTokenTransferChargesZeroFee_Success() public view { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - assertEq(0, feeUSDWei); - assertEq(0, destGasOverhead); - assertEq(0, destBytesOverhead); - } - - function test_getTokenTransferCost_selfServeUsesDefaults_Success() public view { - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_selfServeTokenDefaultPricing, 1000); - - // Get config to assert it isn't set - FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); - - assertFalse(transferFeeConfig.isEnabled); - - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - // Assert that the default values are used - assertEq(uint256(DEFAULT_TOKEN_FEE_USD_CENTS) * 1e16, feeUSDWei); - assertEq(DEFAULT_TOKEN_DEST_GAS_OVERHEAD, destGasOverhead); - assertEq(DEFAULT_TOKEN_BYTES_OVERHEAD, destBytesOverhead); - } - - function test_SmallTokenTransferChargesMinFeeAndGas_Success() public view { - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 1000); - FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); - - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - assertEq(_configUSDCentToWei(transferFeeConfig.minFeeUSDCents), feeUSDWei); - assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); - assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); - } - - function test_ZeroAmountTokenTransferChargesMinFeeAndGas_Success() public view { - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 0); - FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); - - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - assertEq(_configUSDCentToWei(transferFeeConfig.minFeeUSDCents), feeUSDWei); - assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); - assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); - } - - function test_LargeTokenTransferChargesMaxFeeAndGas_Success() public view { - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 1e36); - FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); - - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - assertEq(_configUSDCentToWei(transferFeeConfig.maxFeeUSDCents), feeUSDWei); - assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); - assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); - } - - function test_FeeTokenBpsFee_Success() public view { - uint256 tokenAmount = 10000e18; - - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, tokenAmount); - FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); - - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - uint256 usdWei = _calcUSDValueFromTokenAmount(s_feeTokenPrice, tokenAmount); - uint256 bpsUSDWei = _applyBpsRatio( - usdWei, s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.deciBps - ); - - assertEq(bpsUSDWei, feeUSDWei); - assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); - assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); - } - - function test_CustomTokenBpsFee_Success() public view { - uint256 tokenAmount = 200000e18; - - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(OWNER), - data: "", - tokenAmounts: new Client.EVMTokenAmount[](1), - feeToken: s_sourceFeeToken, - extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT})) - }); - message.tokenAmounts[0] = Client.EVMTokenAmount({token: CUSTOM_TOKEN, amount: tokenAmount}); - - FeeQuoter.TokenTransferFeeConfig memory transferFeeConfig = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token); - - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - uint256 usdWei = _calcUSDValueFromTokenAmount(s_customTokenPrice, tokenAmount); - uint256 bpsUSDWei = _applyBpsRatio( - usdWei, s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[1].tokenTransferFeeConfig.deciBps - ); - - assertEq(bpsUSDWei, feeUSDWei); - assertEq(transferFeeConfig.destGasOverhead, destGasOverhead); - assertEq(transferFeeConfig.destBytesOverhead, destBytesOverhead); - } - - function test_ZeroFeeConfigChargesMinFee_Success() public { - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); - tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = s_sourceFeeToken; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ - minFeeUSDCents: 0, - maxFeeUSDCents: 1, - deciBps: 0, - destGasOverhead: 0, - destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES), - isEnabled: true - }); - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, 1e36); - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_feeTokenPrice, message.tokenAmounts); - - // if token charges 0 bps, it should cost minFee to transfer - assertEq( - _configUSDCentToWei( - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.minFeeUSDCents - ), - feeUSDWei - ); - assertEq(0, destGasOverhead); - assertEq(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES, destBytesOverhead); - } - - function test_Fuzz_TokenTransferFeeDuplicateTokens_Success(uint256 transfers, uint256 amount) public view { - // It shouldn't be possible to pay materially lower fees by splitting up the transfers. - // Note it is possible to pay higher fees since the minimum fees are added. - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - transfers = bound(transfers, 1, destChainConfig.maxNumberOfTokensPerMsg); - // Cap amount to avoid overflow - amount = bound(amount, 0, 1e36); - Client.EVMTokenAmount[] memory multiple = new Client.EVMTokenAmount[](transfers); - for (uint256 i = 0; i < transfers; ++i) { - multiple[i] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: amount}); - } - Client.EVMTokenAmount[] memory single = new Client.EVMTokenAmount[](1); - single[0] = Client.EVMTokenAmount({token: s_sourceTokens[0], amount: amount * transfers}); - - address feeToken = s_sourceRouter.getWrappedNative(); - - (uint256 feeSingleUSDWei, uint32 gasOverheadSingle, uint32 bytesOverheadSingle) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, feeToken, s_wrappedTokenPrice, single); - (uint256 feeMultipleUSDWei, uint32 gasOverheadMultiple, uint32 bytesOverheadMultiple) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, feeToken, s_wrappedTokenPrice, multiple); - - // Note that there can be a rounding error once per split. - assertGe(feeMultipleUSDWei, (feeSingleUSDWei - destChainConfig.maxNumberOfTokensPerMsg)); - assertEq(gasOverheadMultiple, gasOverheadSingle * transfers); - assertEq(bytesOverheadMultiple, bytesOverheadSingle * transfers); - } - - function test_MixedTokenTransferFee_Success() public view { - address[3] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative(), CUSTOM_TOKEN]; - uint224[3] memory tokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice, s_customTokenPrice]; - FeeQuoter.TokenTransferFeeConfig[3] memory tokenTransferFeeConfigs = [ - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[0]), - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[1]), - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[2]) - ]; - - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(OWNER), - data: "", - tokenAmounts: new Client.EVMTokenAmount[](3), - feeToken: s_sourceRouter.getWrappedNative(), - extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT})) - }); - uint256 expectedTotalGas = 0; - uint256 expectedTotalBytes = 0; - - // Start with small token transfers, total bps fee is lower than min token transfer fee - for (uint256 i = 0; i < testTokens.length; ++i) { - message.tokenAmounts[i] = Client.EVMTokenAmount({token: testTokens[i], amount: 1e14}); - FeeQuoter.TokenTransferFeeConfig memory tokenTransferFeeConfig = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, testTokens[i]); - - expectedTotalGas += tokenTransferFeeConfig.destGasOverhead == 0 - ? DEFAULT_TOKEN_DEST_GAS_OVERHEAD - : tokenTransferFeeConfig.destGasOverhead; - expectedTotalBytes += tokenTransferFeeConfig.destBytesOverhead == 0 - ? DEFAULT_TOKEN_BYTES_OVERHEAD - : tokenTransferFeeConfig.destBytesOverhead; - } - (uint256 feeUSDWei, uint32 destGasOverhead, uint32 destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_wrappedTokenPrice, message.tokenAmounts); - - uint256 expectedFeeUSDWei = 0; - for (uint256 i = 0; i < testTokens.length; ++i) { - expectedFeeUSDWei += _configUSDCentToWei( - tokenTransferFeeConfigs[i].minFeeUSDCents == 0 - ? DEFAULT_TOKEN_FEE_USD_CENTS - : tokenTransferFeeConfigs[i].minFeeUSDCents - ); - } - - assertEq(expectedFeeUSDWei, feeUSDWei, "wrong feeUSDWei 1"); - assertEq(expectedTotalGas, destGasOverhead, "wrong destGasOverhead 1"); - assertEq(expectedTotalBytes, destBytesOverhead, "wrong destBytesOverhead 1"); - - // Set 1st token transfer to a meaningful amount so its bps fee is now between min and max fee - message.tokenAmounts[0] = Client.EVMTokenAmount({token: testTokens[0], amount: 10000e18}); - - uint256 token0USDWei = _applyBpsRatio( - _calcUSDValueFromTokenAmount(tokenPrices[0], message.tokenAmounts[0].amount), tokenTransferFeeConfigs[0].deciBps - ); - uint256 token1USDWei = _configUSDCentToWei(DEFAULT_TOKEN_FEE_USD_CENTS); - - (feeUSDWei, destGasOverhead, destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_wrappedTokenPrice, message.tokenAmounts); - expectedFeeUSDWei = token0USDWei + token1USDWei + _configUSDCentToWei(tokenTransferFeeConfigs[2].minFeeUSDCents); - - assertEq(expectedFeeUSDWei, feeUSDWei, "wrong feeUSDWei 2"); - assertEq(expectedTotalGas, destGasOverhead, "wrong destGasOverhead 2"); - assertEq(expectedTotalBytes, destBytesOverhead, "wrong destBytesOverhead 2"); - - // Set 2nd token transfer to a large amount that is higher than maxFeeUSD - message.tokenAmounts[2] = Client.EVMTokenAmount({token: testTokens[2], amount: 1e36}); - - (feeUSDWei, destGasOverhead, destBytesOverhead) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, s_wrappedTokenPrice, message.tokenAmounts); - expectedFeeUSDWei = token0USDWei + token1USDWei + _configUSDCentToWei(tokenTransferFeeConfigs[2].maxFeeUSDCents); - - assertEq(expectedFeeUSDWei, feeUSDWei, "wrong feeUSDWei 3"); - assertEq(expectedTotalGas, destGasOverhead, "wrong destGasOverhead 3"); - assertEq(expectedTotalBytes, destBytesOverhead, "wrong destBytesOverhead 3"); - } -} - -contract FeeQuoter_getValidatedFee is FeeQuoterFeeSetup { - using USDPriceWith18Decimals for uint224; - - function test_EmptyMessage_Success() public view { - address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; - uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; - - for (uint256 i = 0; i < feeTokenPrices.length; ++i) { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = testTokens[i]; - uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - - uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - - uint256 gasUsed = GAS_LIMIT + DEST_GAS_OVERHEAD; - uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); - uint256 messageFeeUSD = (_configUSDCentToWei(destChainConfig.networkFeeUSDCents) * premiumMultiplierWeiPerEth); - uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( - DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, message.data.length, message.tokenAmounts.length, 0 - ); - - uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; - assertEq(totalPriceInFeeToken, feeAmount); - } - } - - function test_ZeroDataAvailabilityMultiplier_Success() public { - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = new FeeQuoter.DestChainConfigArgs[](1); - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - destChainConfigArgs[0] = - FeeQuoter.DestChainConfigArgs({destChainSelector: DEST_CHAIN_SELECTOR, destChainConfig: destChainConfig}); - destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = 0; - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); - - uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - - uint256 gasUsed = GAS_LIMIT + DEST_GAS_OVERHEAD; - uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); - uint256 messageFeeUSD = (_configUSDCentToWei(destChainConfig.networkFeeUSDCents) * premiumMultiplierWeiPerEth); - - uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD) / s_feeTokenPrice; - assertEq(totalPriceInFeeToken, feeAmount); - } - - function test_HighGasMessage_Success() public view { - address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; - uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; - - uint256 customGasLimit = MAX_GAS_LIMIT; - uint256 customDataSize = MAX_DATA_SIZE; - for (uint256 i = 0; i < feeTokenPrices.length; ++i) { - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(OWNER), - data: new bytes(customDataSize), - tokenAmounts: new Client.EVMTokenAmount[](0), - feeToken: testTokens[i], - extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: customGasLimit})) - }); - - uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - - uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - uint256 gasUsed = customGasLimit + DEST_GAS_OVERHEAD + customDataSize * DEST_GAS_PER_PAYLOAD_BYTE; - uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); - uint256 messageFeeUSD = (_configUSDCentToWei(destChainConfig.networkFeeUSDCents) * premiumMultiplierWeiPerEth); - uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( - DEST_CHAIN_SELECTOR, USD_PER_DATA_AVAILABILITY_GAS, message.data.length, message.tokenAmounts.length, 0 - ); - - uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; - assertEq(totalPriceInFeeToken, feeAmount); - } - } - - function test_SingleTokenMessage_Success() public view { - address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; - uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; - - uint256 tokenAmount = 10000e18; - for (uint256 i = 0; i < feeTokenPrices.length; ++i) { - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, tokenAmount); - message.feeToken = testTokens[i]; - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - uint32 destBytesOverhead = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token).destBytesOverhead; - uint32 tokenBytesOverhead = - destBytesOverhead == 0 ? uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) : destBytesOverhead; - - uint256 feeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - - uint256 gasUsed = GAS_LIMIT + DEST_GAS_OVERHEAD - + s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[0].token).destGasOverhead; - uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); - (uint256 transferFeeUSD,,) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, feeTokenPrices[i], message.tokenAmounts); - uint256 messageFeeUSD = (transferFeeUSD * s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken)); - uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( - DEST_CHAIN_SELECTOR, - USD_PER_DATA_AVAILABILITY_GAS, - message.data.length, - message.tokenAmounts.length, - tokenBytesOverhead - ); - - uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; - assertEq(totalPriceInFeeToken, feeAmount); - } - } - - function test_MessageWithDataAndTokenTransfer_Success() public view { - address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; - uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; - - uint256 customGasLimit = 1_000_000; - for (uint256 i = 0; i < feeTokenPrices.length; ++i) { - Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ - receiver: abi.encode(OWNER), - data: "", - tokenAmounts: new Client.EVMTokenAmount[](2), - feeToken: testTokens[i], - extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: customGasLimit})) - }); - uint64 premiumMultiplierWeiPerEth = s_feeQuoter.getPremiumMultiplierWeiPerEth(message.feeToken); - FeeQuoter.DestChainConfig memory destChainConfig = s_feeQuoter.getDestChainConfig(DEST_CHAIN_SELECTOR); - - message.tokenAmounts[0] = Client.EVMTokenAmount({token: s_sourceFeeToken, amount: 10000e18}); // feeTokenAmount - message.tokenAmounts[1] = Client.EVMTokenAmount({token: CUSTOM_TOKEN, amount: 200000e18}); // customTokenAmount - message.data = "random bits and bytes that should be factored into the cost of the message"; - - uint32 tokenGasOverhead = 0; - uint32 tokenBytesOverhead = 0; - for (uint256 j = 0; j < message.tokenAmounts.length; ++j) { - tokenGasOverhead += - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[j].token).destGasOverhead; - uint32 destBytesOverhead = - s_feeQuoter.getTokenTransferFeeConfig(DEST_CHAIN_SELECTOR, message.tokenAmounts[j].token).destBytesOverhead; - tokenBytesOverhead += destBytesOverhead == 0 ? uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) : destBytesOverhead; - } - - uint256 gasUsed = - customGasLimit + DEST_GAS_OVERHEAD + message.data.length * DEST_GAS_PER_PAYLOAD_BYTE + tokenGasOverhead; - uint256 gasFeeUSD = (gasUsed * destChainConfig.gasMultiplierWeiPerEth * USD_PER_GAS); - (uint256 transferFeeUSD,,) = - s_feeQuoter.getTokenTransferCost(DEST_CHAIN_SELECTOR, message.feeToken, feeTokenPrices[i], message.tokenAmounts); - uint256 messageFeeUSD = (transferFeeUSD * premiumMultiplierWeiPerEth); - uint256 dataAvailabilityFeeUSD = s_feeQuoter.getDataAvailabilityCost( - DEST_CHAIN_SELECTOR, - USD_PER_DATA_AVAILABILITY_GAS, - message.data.length, - message.tokenAmounts.length, - tokenBytesOverhead - ); - - uint256 totalPriceInFeeToken = (gasFeeUSD + messageFeeUSD + dataAvailabilityFeeUSD) / feeTokenPrices[i]; - assertEq(totalPriceInFeeToken, s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message)); - } - } - - function test_Fuzz_EnforceOutOfOrder(bool enforce, bool allowOutOfOrderExecution) public { - // Update config to enforce allowOutOfOrderExecution = defaultVal. - vm.stopPrank(); - vm.startPrank(OWNER); - - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - destChainConfigArgs[0].destChainConfig.enforceOutOfOrder = enforce; - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = abi.encodeWithSelector( - Client.EVM_EXTRA_ARGS_V2_TAG, - Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: allowOutOfOrderExecution}) - ); - - // If enforcement is on, only true should be allowed. - if (enforce && !allowOutOfOrderExecution) { - vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); - } - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - } - - // Reverts - - function test_DestinationChainNotEnabled_Revert() public { - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.DestinationChainNotEnabled.selector, DEST_CHAIN_SELECTOR + 1)); - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR + 1, _generateEmptyMessage()); - } - - function test_EnforceOutOfOrder_Revert() public { - // Update config to enforce allowOutOfOrderExecution = true. - vm.stopPrank(); - vm.startPrank(OWNER); - - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - destChainConfigArgs[0].destChainConfig.enforceOutOfOrder = true; - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - vm.stopPrank(); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - // Empty extraArgs to should revert since it enforceOutOfOrder is true. - message.extraArgs = ""; - - vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - } - - function test_MessageTooLarge_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.data = new bytes(MAX_DATA_SIZE + 1); - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.MessageTooLarge.selector, MAX_DATA_SIZE, message.data.length)); - - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - } - - function test_TooManyTokens_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - uint256 tooMany = MAX_TOKENS_LENGTH + 1; - message.tokenAmounts = new Client.EVMTokenAmount[](tooMany); - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.UnsupportedNumberOfTokens.selector, tooMany, MAX_TOKENS_LENGTH)); - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - } - - // Asserts gasLimit must be <=maxGasLimit - function test_MessageGasLimitTooHigh_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: MAX_GAS_LIMIT + 1})); - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.MessageGasLimitTooHigh.selector)); - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - } - - function test_NotAFeeToken_Revert() public { - address notAFeeToken = address(0x111111); - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(notAFeeToken, 1); - message.feeToken = notAFeeToken; - - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.FeeTokenNotSupported.selector, notAFeeToken)); - - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - } - - function test_InvalidEVMAddress_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.receiver = abi.encode(type(uint208).max); - - vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, message.receiver)); - - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - } -} - -contract FeeQuoter_processMessageArgs is FeeQuoterFeeSetup { - using USDPriceWith18Decimals for uint224; - - function setUp() public virtual override { - super.setUp(); - } - - function test_processMessageArgs_WithLinkTokenAmount_Success() public view { - ( - uint256 msgFeeJuels, - /* bool isOutOfOrderExecution */ - , - /* bytes memory convertedExtraArgs */ - , - /* destExecDataPerToken */ - ) = s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - // LINK - s_sourceTokens[0], - MAX_MSG_FEES_JUELS, - "", - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - - assertEq(msgFeeJuels, MAX_MSG_FEES_JUELS); - } - - function test_processMessageArgs_WithConvertedTokenAmount_Success() public view { - address feeToken = s_sourceTokens[1]; - uint256 feeTokenAmount = 10_000 gwei; - uint256 expectedConvertedAmount = s_feeQuoter.convertTokenAmount(feeToken, feeTokenAmount, s_sourceTokens[0]); - - ( - uint256 msgFeeJuels, - /* bool isOutOfOrderExecution */ - , - /* bytes memory convertedExtraArgs */ - , - /* destExecDataPerToken */ - ) = s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - feeToken, - feeTokenAmount, - "", - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - - assertEq(msgFeeJuels, expectedConvertedAmount); - } - - function test_processMessageArgs_WithEmptyEVMExtraArgs_Success() public view { - ( - /* uint256 msgFeeJuels */ - , - bool isOutOfOrderExecution, - bytes memory convertedExtraArgs, - /* destExecDataPerToken */ - ) = s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - s_sourceTokens[0], - 0, - "", - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - - assertEq(isOutOfOrderExecution, false); - assertEq(convertedExtraArgs, Client._argsToBytes(s_feeQuoter.parseEVMExtraArgsFromBytes("", DEST_CHAIN_SELECTOR))); - } - - function test_processMessageArgs_WithEVMExtraArgsV1_Success() public view { - bytes memory extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 1000})); - - ( - /* uint256 msgFeeJuels */ - , - bool isOutOfOrderExecution, - bytes memory convertedExtraArgs, - /* destExecDataPerToken */ - ) = s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - s_sourceTokens[0], - 0, - extraArgs, - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - - assertEq(isOutOfOrderExecution, false); - assertEq( - convertedExtraArgs, Client._argsToBytes(s_feeQuoter.parseEVMExtraArgsFromBytes(extraArgs, DEST_CHAIN_SELECTOR)) - ); - } - - function test_processMessageArgs_WitEVMExtraArgsV2_Success() public view { - bytes memory extraArgs = Client._argsToBytes(Client.EVMExtraArgsV2({gasLimit: 0, allowOutOfOrderExecution: true})); - - ( - /* uint256 msgFeeJuels */ - , - bool isOutOfOrderExecution, - bytes memory convertedExtraArgs, - /* destExecDataPerToken */ - ) = s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - s_sourceTokens[0], - 0, - extraArgs, - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - - assertEq(isOutOfOrderExecution, true); - assertEq( - convertedExtraArgs, Client._argsToBytes(s_feeQuoter.parseEVMExtraArgsFromBytes(extraArgs, DEST_CHAIN_SELECTOR)) - ); - } - - // Reverts - - function test_processMessageArgs_MessageFeeTooHigh_Revert() public { - vm.expectRevert( - abi.encodeWithSelector(FeeQuoter.MessageFeeTooHigh.selector, MAX_MSG_FEES_JUELS + 1, MAX_MSG_FEES_JUELS) - ); - - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - s_sourceTokens[0], - MAX_MSG_FEES_JUELS + 1, - "", - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - } - - function test_processMessageArgs_InvalidExtraArgs_Revert() public { - vm.expectRevert(FeeQuoter.InvalidExtraArgsTag.selector); - - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - s_sourceTokens[0], - 0, - "wrong extra args", - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - } - - function test_processMessageArgs_MalformedEVMExtraArgs_Revert() public { - // abi.decode error - vm.expectRevert(); - - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - s_sourceTokens[0], - 0, - abi.encodeWithSelector(Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV1({gasLimit: 100})), - new Internal.EVM2AnyTokenTransfer[](0), - new Client.EVMTokenAmount[](0) - ); - } - - function test_processMessageArgs_WithCorrectPoolReturnData_Success() public view { - Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](2); - sourceTokenAmounts[0].amount = 1e18; - sourceTokenAmounts[0].token = s_sourceTokens[0]; - sourceTokenAmounts[1].amount = 1e18; - sourceTokenAmounts[1].token = CUSTOM_TOKEN_2; - - Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](2); - tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); - tokenAmounts[1] = _getSourceTokenData(sourceTokenAmounts[1], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); - bytes[] memory expectedDestExecData = new bytes[](2); - expectedDestExecData[0] = abi.encode( - s_feeQuoterTokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig.destGasOverhead - ); - expectedDestExecData[1] = abi.encode(DEFAULT_TOKEN_DEST_GAS_OVERHEAD); //expected return data should be abi.encoded default as isEnabled is false - - // No revert - successful - ( /* msgFeeJuels */ , /* isOutOfOrderExecution */, /* convertedExtraArgs */, bytes[] memory destExecData) = - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts - ); - - for (uint256 i = 0; i < destExecData.length; ++i) { - assertEq(destExecData[i], expectedDestExecData[i]); - } - } - - function test_processMessageArgs_TokenAmountArraysMismatching_Revert() public { - Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](2); - sourceTokenAmounts[0].amount = 1e18; - sourceTokenAmounts[0].token = s_sourceTokens[0]; - - Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](1); - tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); - - // Revert due to index out of bounds access - vm.expectRevert(); - - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, - s_sourceTokens[0], - MAX_MSG_FEES_JUELS, - "", - new Internal.EVM2AnyTokenTransfer[](1), - new Client.EVMTokenAmount[](0) - ); - } - - function test_applyTokensTransferFeeConfigUpdates_InvalidFeeRange_Revert() public { - address sourceETH = s_sourceTokens[1]; - - // Set token config to allow larger data - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); - tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = sourceETH; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ - minFeeUSDCents: 1, - maxFeeUSDCents: 0, - deciBps: 0, - destGasOverhead: 0, - destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32, - isEnabled: true - }); - - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.InvalidFeeRange.selector, 1, 0)); - - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - } - - function test_processMessageArgs_SourceTokenDataTooLarge_Revert() public { - address sourceETH = s_sourceTokens[1]; - - Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](1); - sourceTokenAmounts[0].amount = 1000; - sourceTokenAmounts[0].token = sourceETH; - - Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](1); - tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); - - // No data set, should succeed - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts - ); - - // Set max data length, should succeed - tokenAmounts[0].extraData = new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES); - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts - ); - - // Set data to max length +1, should revert - tokenAmounts[0].extraData = new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES + 1); - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts - ); - - // Set token config to allow larger data - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); - tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = sourceETH; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ - minFeeUSDCents: 0, - maxFeeUSDCents: 1, - deciBps: 0, - destGasOverhead: 0, - destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32, - isEnabled: true - }); - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts - ); - - // Set the token data larger than the configured token data, should revert - tokenAmounts[0].extraData = new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES + 32 + 1); - - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts - ); - } - - function test_processMessageArgs_InvalidEVMAddressDestToken_Revert() public { - bytes memory nonEvmAddress = abi.encode(type(uint208).max); - - Client.EVMTokenAmount[] memory sourceTokenAmounts = new Client.EVMTokenAmount[](1); - sourceTokenAmounts[0].amount = 1e18; - sourceTokenAmounts[0].token = s_sourceTokens[0]; - - Internal.EVM2AnyTokenTransfer[] memory tokenAmounts = new Internal.EVM2AnyTokenTransfer[](1); - tokenAmounts[0] = _getSourceTokenData(sourceTokenAmounts[0], s_tokenAdminRegistry, DEST_CHAIN_SELECTOR); - tokenAmounts[0].destTokenAddress = nonEvmAddress; - - vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, nonEvmAddress)); - s_feeQuoter.processMessageArgs( - DEST_CHAIN_SELECTOR, s_sourceTokens[0], MAX_MSG_FEES_JUELS, "", tokenAmounts, sourceTokenAmounts - ); - } -} - -contract FeeQuoter_validateDestFamilyAddress is FeeQuoterSetup { - function test_ValidEVMAddress_Success() public view { - bytes memory encodedAddress = abi.encode(address(10000)); - s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, encodedAddress); - } - - function test_ValidNonEVMAddress_Success() public view { - s_feeQuoter.validateDestFamilyAddress(bytes4(uint32(1)), abi.encode(type(uint208).max)); - } - - // Reverts - - function test_InvalidEVMAddress_Revert() public { - bytes memory invalidAddress = abi.encode(type(uint208).max); - vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, invalidAddress)); - s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress); - } - - function test_InvalidEVMAddressEncodePacked_Revert() public { - bytes memory invalidAddress = abi.encodePacked(address(234)); - vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, invalidAddress)); - s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress); - } - - function test_InvalidEVMAddressPrecompiles_Revert() public { - for (uint160 i = 0; i < Internal.PRECOMPILE_SPACE; ++i) { - bytes memory invalidAddress = abi.encode(address(i)); - vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, invalidAddress)); - s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress); - } - - s_feeQuoter.validateDestFamilyAddress( - Internal.CHAIN_FAMILY_SELECTOR_EVM, abi.encode(address(uint160(Internal.PRECOMPILE_SPACE))) - ); - } -} - -contract FeeQuoter_parseEVMExtraArgsFromBytes is FeeQuoterSetup { - FeeQuoter.DestChainConfig private s_destChainConfig; - - function setUp() public virtual override { - super.setUp(); - s_destChainConfig = _generateFeeQuoterDestChainConfigArgs()[0].destChainConfig; - } - - function test_EVMExtraArgsV1_Success() public view { - Client.EVMExtraArgsV1 memory inputArgs = Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT}); - bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); - Client.EVMExtraArgsV2 memory expectedOutputArgs = - Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: false}); - - vm.assertEq( - abi.encode(s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig)), - abi.encode(expectedOutputArgs) - ); - } - - function test_EVMExtraArgsV2_Success() public view { - Client.EVMExtraArgsV2 memory inputArgs = - Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: true}); - bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); - - vm.assertEq( - abi.encode(s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig)), abi.encode(inputArgs) - ); - } - - function test_EVMExtraArgsDefault_Success() public view { - Client.EVMExtraArgsV2 memory expectedOutputArgs = - Client.EVMExtraArgsV2({gasLimit: s_destChainConfig.defaultTxGasLimit, allowOutOfOrderExecution: false}); - - vm.assertEq( - abi.encode(s_feeQuoter.parseEVMExtraArgsFromBytes("", s_destChainConfig)), abi.encode(expectedOutputArgs) - ); - } - - // Reverts - - function test_EVMExtraArgsInvalidExtraArgsTag_Revert() public { - Client.EVMExtraArgsV2 memory inputArgs = - Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: true}); - bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); - // Invalidate selector - inputExtraArgs[0] = bytes1(uint8(0)); - - vm.expectRevert(FeeQuoter.InvalidExtraArgsTag.selector); - s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); - } - - function test_EVMExtraArgsEnforceOutOfOrder_Revert() public { - Client.EVMExtraArgsV2 memory inputArgs = - Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT, allowOutOfOrderExecution: false}); - bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); - s_destChainConfig.enforceOutOfOrder = true; - - vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); - s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); - } - - function test_EVMExtraArgsGasLimitTooHigh_Revert() public { - Client.EVMExtraArgsV2 memory inputArgs = - Client.EVMExtraArgsV2({gasLimit: s_destChainConfig.maxPerMsgGasLimit + 1, allowOutOfOrderExecution: true}); - bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); - - vm.expectRevert(FeeQuoter.MessageGasLimitTooHigh.selector); - s_feeQuoter.parseEVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); - } -} - -contract FeeQuoter_KeystoneSetup is FeeQuoterSetup { - address internal constant FORWARDER_1 = address(0x1); - address internal constant WORKFLOW_OWNER_1 = address(0x3); - bytes10 internal constant WORKFLOW_NAME_1 = "workflow1"; - bytes2 internal constant REPORT_NAME_1 = "01"; - address internal s_onReportTestToken1; - address internal s_onReportTestToken2; - - function setUp() public virtual override { - super.setUp(); - s_onReportTestToken1 = s_sourceTokens[0]; - s_onReportTestToken2 = _deploySourceToken("onReportTestToken2", 0, 20); - - KeystoneFeedsPermissionHandler.Permission[] memory permissions = new KeystoneFeedsPermissionHandler.Permission[](1); - permissions[0] = KeystoneFeedsPermissionHandler.Permission({ - forwarder: FORWARDER_1, - workflowOwner: WORKFLOW_OWNER_1, - workflowName: WORKFLOW_NAME_1, - reportName: REPORT_NAME_1, - isAllowed: true - }); - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeeds = new FeeQuoter.TokenPriceFeedUpdate[](2); - tokenPriceFeeds[0] = FeeQuoter.TokenPriceFeedUpdate({ - sourceToken: s_onReportTestToken1, - feedConfig: FeeQuoter.TokenPriceFeedConfig({dataFeedAddress: address(0x0), tokenDecimals: 18, isEnabled: true}) - }); - tokenPriceFeeds[1] = FeeQuoter.TokenPriceFeedUpdate({ - sourceToken: s_onReportTestToken2, - feedConfig: FeeQuoter.TokenPriceFeedConfig({dataFeedAddress: address(0x0), tokenDecimals: 20, isEnabled: true}) - }); - s_feeQuoter.setReportPermissions(permissions); - s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeeds); - } -} - -contract FeeQuoter_onReport is FeeQuoter_KeystoneSetup { - function test_onReport_Success() public { - bytes memory encodedPermissionsMetadata = - abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); - - FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](2); - report[0] = - FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp)}); - report[1] = - FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken2, price: 4e18, timestamp: uint32(block.timestamp)}); - - uint224 expectedStoredToken1Price = s_feeQuoter.calculateRebasedValue(18, 18, report[0].price); - uint224 expectedStoredToken2Price = s_feeQuoter.calculateRebasedValue(18, 20, report[1].price); - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken1, expectedStoredToken1Price, block.timestamp); - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken2, expectedStoredToken2Price, block.timestamp); - - changePrank(FORWARDER_1); - s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); - - vm.assertEq(s_feeQuoter.getTokenPrice(report[0].token).value, expectedStoredToken1Price); - vm.assertEq(s_feeQuoter.getTokenPrice(report[0].token).timestamp, report[0].timestamp); - - vm.assertEq(s_feeQuoter.getTokenPrice(report[1].token).value, expectedStoredToken2Price); - vm.assertEq(s_feeQuoter.getTokenPrice(report[1].token).timestamp, report[1].timestamp); - } - - function test_OnReport_StaleUpdate_SkipPriceUpdate_Success() public { - //Creating a correct report - bytes memory encodedPermissionsMetadata = - abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); - - FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); - report[0] = - FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp)}); - - uint224 expectedStoredTokenPrice = s_feeQuoter.calculateRebasedValue(18, 18, report[0].price); - - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken1, expectedStoredTokenPrice, block.timestamp); - - changePrank(FORWARDER_1); - //setting the correct price and time with the correct report - s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); - - //create a stale report - report[0] = FeeQuoter.ReceivedCCIPFeedReport({ - token: s_onReportTestToken1, - price: 4e18, - timestamp: uint32(block.timestamp - 1) - }); - - //record logs to check no events were emitted - vm.recordLogs(); - - s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); - - //no logs should have been emitted - assertEq(vm.getRecordedLogs().length, 0); - } - - function test_onReport_TokenNotSupported_Revert() public { - bytes memory encodedPermissionsMetadata = - abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); - FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); - report[0] = - FeeQuoter.ReceivedCCIPFeedReport({token: s_sourceTokens[1], price: 4e18, timestamp: uint32(block.timestamp)}); - - // Revert due to token config not being set with the isEnabled flag - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, s_sourceTokens[1])); - vm.startPrank(FORWARDER_1); - s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); - } - - function test_onReport_InvalidForwarder_Reverts() public { - bytes memory encodedPermissionsMetadata = - abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); - FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); - report[0] = - FeeQuoter.ReceivedCCIPFeedReport({token: s_sourceTokens[0], price: 4e18, timestamp: uint32(block.timestamp)}); - - vm.expectRevert( - abi.encodeWithSelector( - KeystoneFeedsPermissionHandler.ReportForwarderUnauthorized.selector, - STRANGER, - WORKFLOW_OWNER_1, - WORKFLOW_NAME_1, - REPORT_NAME_1 - ) - ); - changePrank(STRANGER); - s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); - } - - function test_onReport_UnsupportedToken_Reverts() public { - bytes memory encodedPermissionsMetadata = - abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1); - FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); - report[0] = - FeeQuoter.ReceivedCCIPFeedReport({token: s_sourceTokens[1], price: 4e18, timestamp: uint32(block.timestamp)}); - - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, s_sourceTokens[1])); - changePrank(FORWARDER_1); - s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); - } -} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.updatePrices.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.updatePrices.t.sol new file mode 100644 index 00000000000..d40ac7d33ad --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.updatePrices.t.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {AuthorizedCallers} from "../../../shared/access/AuthorizedCallers.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_updatePrices is FeeQuoterSetup { + function test_OnlyTokenPrice_Success() public { + Internal.PriceUpdates memory update = Internal.PriceUpdates({ + tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), + gasPriceUpdates: new Internal.GasPriceUpdate[](0) + }); + update.tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: 4e18}); + + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated( + update.tokenPriceUpdates[0].sourceToken, update.tokenPriceUpdates[0].usdPerToken, block.timestamp + ); + + s_feeQuoter.updatePrices(update); + + assertEq(s_feeQuoter.getTokenPrice(s_sourceTokens[0]).value, update.tokenPriceUpdates[0].usdPerToken); + } + + function test_OnlyGasPrice_Success() public { + Internal.PriceUpdates memory update = Internal.PriceUpdates({ + tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), + gasPriceUpdates: new Internal.GasPriceUpdate[](1) + }); + update.gasPriceUpdates[0] = + Internal.GasPriceUpdate({destChainSelector: DEST_CHAIN_SELECTOR, usdPerUnitGas: 2000e18}); + + vm.expectEmit(); + emit FeeQuoter.UsdPerUnitGasUpdated( + update.gasPriceUpdates[0].destChainSelector, update.gasPriceUpdates[0].usdPerUnitGas, block.timestamp + ); + + s_feeQuoter.updatePrices(update); + + assertEq( + s_feeQuoter.getDestinationChainGasPrice(DEST_CHAIN_SELECTOR).value, update.gasPriceUpdates[0].usdPerUnitGas + ); + } + + function test_UpdateMultiplePrices_Success() public { + Internal.TokenPriceUpdate[] memory tokenPriceUpdates = new Internal.TokenPriceUpdate[](3); + tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: 4e18}); + tokenPriceUpdates[1] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[1], usdPerToken: 1800e18}); + tokenPriceUpdates[2] = Internal.TokenPriceUpdate({sourceToken: address(12345), usdPerToken: 1e18}); + + Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](3); + gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: DEST_CHAIN_SELECTOR, usdPerUnitGas: 2e6}); + gasPriceUpdates[1] = Internal.GasPriceUpdate({destChainSelector: SOURCE_CHAIN_SELECTOR, usdPerUnitGas: 2000e18}); + gasPriceUpdates[2] = Internal.GasPriceUpdate({destChainSelector: 12345, usdPerUnitGas: 1e18}); + + Internal.PriceUpdates memory update = + Internal.PriceUpdates({tokenPriceUpdates: tokenPriceUpdates, gasPriceUpdates: gasPriceUpdates}); + + for (uint256 i = 0; i < tokenPriceUpdates.length; ++i) { + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated( + update.tokenPriceUpdates[i].sourceToken, update.tokenPriceUpdates[i].usdPerToken, block.timestamp + ); + } + for (uint256 i = 0; i < gasPriceUpdates.length; ++i) { + vm.expectEmit(); + emit FeeQuoter.UsdPerUnitGasUpdated( + update.gasPriceUpdates[i].destChainSelector, update.gasPriceUpdates[i].usdPerUnitGas, block.timestamp + ); + } + + s_feeQuoter.updatePrices(update); + + for (uint256 i = 0; i < tokenPriceUpdates.length; ++i) { + assertEq( + s_feeQuoter.getTokenPrice(update.tokenPriceUpdates[i].sourceToken).value, tokenPriceUpdates[i].usdPerToken + ); + } + for (uint256 i = 0; i < gasPriceUpdates.length; ++i) { + assertEq( + s_feeQuoter.getDestinationChainGasPrice(update.gasPriceUpdates[i].destChainSelector).value, + gasPriceUpdates[i].usdPerUnitGas + ); + } + } + + function test_UpdatableByAuthorizedCaller_Success() public { + Internal.PriceUpdates memory priceUpdates = Internal.PriceUpdates({ + tokenPriceUpdates: new Internal.TokenPriceUpdate[](1), + gasPriceUpdates: new Internal.GasPriceUpdate[](0) + }); + priceUpdates.tokenPriceUpdates[0] = Internal.TokenPriceUpdate({sourceToken: s_sourceTokens[0], usdPerToken: 4e18}); + + // Revert when caller is not authorized + vm.startPrank(STRANGER); + vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); + s_feeQuoter.updatePrices(priceUpdates); + + address[] memory priceUpdaters = new address[](1); + priceUpdaters[0] = STRANGER; + vm.startPrank(OWNER); + s_feeQuoter.applyAuthorizedCallerUpdates( + AuthorizedCallers.AuthorizedCallerArgs({addedCallers: priceUpdaters, removedCallers: new address[](0)}) + ); + + // Stranger is now an authorized caller to update prices + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated( + priceUpdates.tokenPriceUpdates[0].sourceToken, priceUpdates.tokenPriceUpdates[0].usdPerToken, block.timestamp + ); + s_feeQuoter.updatePrices(priceUpdates); + + assertEq(s_feeQuoter.getTokenPrice(s_sourceTokens[0]).value, priceUpdates.tokenPriceUpdates[0].usdPerToken); + + vm.startPrank(OWNER); + s_feeQuoter.applyAuthorizedCallerUpdates( + AuthorizedCallers.AuthorizedCallerArgs({addedCallers: new address[](0), removedCallers: priceUpdaters}) + ); + + // Revert when authorized caller is removed + vm.startPrank(STRANGER); + vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); + s_feeQuoter.updatePrices(priceUpdates); + } + + // Reverts + + function test_OnlyCallableByUpdater_Revert() public { + Internal.PriceUpdates memory priceUpdates = Internal.PriceUpdates({ + tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), + gasPriceUpdates: new Internal.GasPriceUpdate[](0) + }); + + vm.startPrank(STRANGER); + vm.expectRevert(abi.encodeWithSelector(AuthorizedCallers.UnauthorizedCaller.selector, STRANGER)); + s_feeQuoter.updatePrices(priceUpdates); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.updateTokenPriceFeeds.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.updateTokenPriceFeeds.t.sol new file mode 100644 index 00000000000..9341fab121b --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.updateTokenPriceFeeds.t.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +import {Vm} from "forge-std/Vm.sol"; + +contract FeeQuoter_updateTokenPriceFeeds is FeeQuoterSetup { + function test_ZeroFeeds_Success() public { + Vm.Log[] memory logEntries = vm.getRecordedLogs(); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](0); + vm.recordLogs(); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + // Verify no log emissions + assertEq(logEntries.length, 0); + } + + function test_SingleFeedUpdate_Success() public { + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = + _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); + + _assertTokenPriceFeedConfigNotConfigured(s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken)); + + vm.expectEmit(); + emit FeeQuoter.PriceFeedPerTokenUpdated(tokenPriceFeedUpdates[0].sourceToken, tokenPriceFeedUpdates[0].feedConfig); + + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + _assertTokenPriceFeedConfigEquality( + s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig + ); + } + + function test_MultipleFeedUpdate_Success() public { + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](2); + + for (uint256 i = 0; i < 2; ++i) { + tokenPriceFeedUpdates[i] = + _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[i], s_dataFeedByToken[s_sourceTokens[i]], 18); + + _assertTokenPriceFeedConfigNotConfigured( + s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[i].sourceToken) + ); + + vm.expectEmit(); + emit FeeQuoter.PriceFeedPerTokenUpdated(tokenPriceFeedUpdates[i].sourceToken, tokenPriceFeedUpdates[i].feedConfig); + } + + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + _assertTokenPriceFeedConfigEquality( + s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig + ); + _assertTokenPriceFeedConfigEquality( + s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[1].sourceToken), tokenPriceFeedUpdates[1].feedConfig + ); + } + + function test_FeedUnset_Success() public { + Internal.TimestampedPackedUint224 memory priceQueryInitial = s_feeQuoter.getTokenPrice(s_sourceTokens[0]); + assertFalse(priceQueryInitial.value == 0); + assertFalse(priceQueryInitial.timestamp == 0); + + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = + _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); + + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + _assertTokenPriceFeedConfigEquality( + s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig + ); + + tokenPriceFeedUpdates[0].feedConfig.dataFeedAddress = address(0); + vm.expectEmit(); + emit FeeQuoter.PriceFeedPerTokenUpdated(tokenPriceFeedUpdates[0].sourceToken, tokenPriceFeedUpdates[0].feedConfig); + + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + _assertTokenPriceFeedConfigEquality( + s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig + ); + + // Price data should remain after a feed has been set->unset + Internal.TimestampedPackedUint224 memory priceQueryPostUnsetFeed = s_feeQuoter.getTokenPrice(s_sourceTokens[0]); + assertEq(priceQueryPostUnsetFeed.value, priceQueryInitial.value); + assertEq(priceQueryPostUnsetFeed.timestamp, priceQueryInitial.timestamp); + } + + function test_FeedNotUpdated() public { + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = + _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); + + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + + _assertTokenPriceFeedConfigEquality( + s_feeQuoter.getTokenPriceFeedConfig(tokenPriceFeedUpdates[0].sourceToken), tokenPriceFeedUpdates[0].feedConfig + ); + } + + // Reverts + + function test_FeedUpdatedByNonOwner_Revert() public { + FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](1); + tokenPriceFeedUpdates[0] = + _getSingleTokenPriceFeedUpdateStruct(s_sourceTokens[0], s_dataFeedByToken[s_sourceTokens[0]], 18); + + vm.startPrank(STRANGER); + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + + s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeedUpdates); + } + + function _assertTokenPriceFeedConfigNotConfigured( + FeeQuoter.TokenPriceFeedConfig memory config + ) internal pure virtual { + _assertTokenPriceFeedConfigEquality( + config, FeeQuoter.TokenPriceFeedConfig({dataFeedAddress: address(0), tokenDecimals: 0, isEnabled: false}) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.validateDestFamilyAddress.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.validateDestFamilyAddress.t.sol new file mode 100644 index 00000000000..761cb7546a9 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.validateDestFamilyAddress.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_validateDestFamilyAddress is FeeQuoterSetup { + function test_ValidEVMAddress_Success() public view { + bytes memory encodedAddress = abi.encode(address(10000)); + s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, encodedAddress); + } + + function test_ValidNonEVMAddress_Success() public view { + s_feeQuoter.validateDestFamilyAddress(bytes4(uint32(1)), abi.encode(type(uint208).max)); + } + + // Reverts + + function test_InvalidEVMAddress_Revert() public { + bytes memory invalidAddress = abi.encode(type(uint208).max); + vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, invalidAddress)); + s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress); + } + + function test_InvalidEVMAddressEncodePacked_Revert() public { + bytes memory invalidAddress = abi.encodePacked(address(234)); + vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, invalidAddress)); + s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress); + } + + function test_InvalidEVMAddressPrecompiles_Revert() public { + for (uint160 i = 0; i < Internal.PRECOMPILE_SPACE; ++i) { + bytes memory invalidAddress = abi.encode(address(i)); + vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, invalidAddress)); + s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress); + } + + s_feeQuoter.validateDestFamilyAddress( + Internal.CHAIN_FAMILY_SELECTOR_EVM, abi.encode(address(uint160(Internal.PRECOMPILE_SPACE))) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol index 12f535f9985..a6551c554e6 100644 --- a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol @@ -13,11 +13,26 @@ contract FeeQuoterSetup is TokenSetup { uint112 internal constant USD_PER_GAS = 1e6; // 0.001 gwei uint112 internal constant USD_PER_DATA_AVAILABILITY_GAS = 1e9; // 1 gwei + address internal constant DUMMY_CONTRACT_ADDRESS = 0x1111111111111111111111111111111111111112; address internal constant CUSTOM_TOKEN = address(12345); address internal constant CUSTOM_TOKEN_2 = address(bytes20(keccak256("CUSTOM_TOKEN_2"))); + // Use 16 gas per data availability byte in our tests. + // This is an overestimation in OP stack, it ignores 4 gas per 0 byte rule. + // Arbitrum on the other hand, does always use 16 gas per data availability byte. + // This value may be substantially decreased after EIP 4844. + uint16 internal constant DEST_GAS_PER_DATA_AVAILABILITY_BYTE = 16; + + // Total L1 data availability overhead estimate is 33_596 gas. + // This value includes complete CommitStore and OffRamp call data. + uint32 internal constant DEST_DATA_AVAILABILITY_OVERHEAD_GAS = 188 // Fixed data availability overhead in OP stack. + + (32 * 31 + 4) * DEST_GAS_PER_DATA_AVAILABILITY_BYTE // CommitStore single-root transmission takes up about 31 slots, plus selector. + + (32 * 34 + 4) * DEST_GAS_PER_DATA_AVAILABILITY_BYTE; // OffRamp transmission excluding EVM2EVMMessage takes up about 34 slots, plus selector. + + // Multiples of bps, or 0.0001, use 6840 to be same as OP mainnet compression factor of 0.684. + uint16 internal constant DEST_GAS_DATA_AVAILABILITY_MULTIPLIER_BPS = 6840; + uint224 internal constant CUSTOM_TOKEN_PRICE = 1e17; // $0.1 CUSTOM - uint224 internal constant CUSTOM_TOKEN_PRICE_2 = 1e18; // $1 CUSTOM // Encode L1 gas price and L2 gas price into a packed price. // L1 gas price is left-shifted to the higher-order bits. @@ -31,8 +46,6 @@ contract FeeQuoterSetup is TokenSetup { address[] internal s_sourceFeeTokens; uint224[] internal s_sourceTokenPrices; - address[] internal s_destFeeTokens; - uint224[] internal s_destTokenPrices; FeeQuoter.PremiumMultiplierWeiPerEthArgs[] internal s_feeQuoterPremiumMultiplierWeiPerEthArgs; FeeQuoter.TokenTransferFeeConfigArgs[] internal s_feeQuoterTokenTransferFeeConfigArgs; @@ -40,7 +53,7 @@ contract FeeQuoterSetup is TokenSetup { mapping(address token => address dataFeedAddress) internal s_dataFeedByToken; function setUp() public virtual override { - TokenSetup.setUp(); + super.setUp(); _deployTokenPriceDataFeed(s_sourceFeeToken, 8, 1e8); @@ -63,13 +76,11 @@ contract FeeQuoterSetup is TokenSetup { destFeeTokens[0] = s_destTokens[0]; destFeeTokens[1] = s_destTokens[1]; destFeeTokens[2] = s_destRouter.getWrappedNative(); - s_destFeeTokens = destFeeTokens; uint224[] memory destTokenPrices = new uint224[](3); destTokenPrices[0] = 5e18; destTokenPrices[1] = 2000e18; destTokenPrices[2] = 2000e18; - s_destTokenPrices = destTokenPrices; uint256 sourceTokenCount = sourceFeeTokens.length; uint256 destTokenCount = destFeeTokens.length; @@ -96,7 +107,6 @@ contract FeeQuoterSetup is TokenSetup { address[] memory feeTokens = new address[](2); feeTokens[0] = s_sourceTokens[0]; feeTokens[1] = s_weth; - FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeedUpdates = new FeeQuoter.TokenPriceFeedUpdate[](0); s_feeQuoterPremiumMultiplierWeiPerEthArgs.push( FeeQuoter.PremiumMultiplierWeiPerEthArgs({ @@ -164,7 +174,7 @@ contract FeeQuoterSetup is TokenSetup { }), priceUpdaters, feeTokens, - tokenPriceFeedUpdates, + new FeeQuoter.TokenPriceFeedUpdate[](0), s_feeQuoterTokenTransferFeeConfigArgs, s_feeQuoterPremiumMultiplierWeiPerEthArgs, _generateFeeQuoterDestChainConfigArgs() @@ -194,13 +204,6 @@ contract FeeQuoterSetup is TokenSetup { return priceUpdates; } - function _getEmptyPriceUpdates() internal pure returns (Internal.PriceUpdates memory priceUpdates) { - return Internal.PriceUpdates({ - tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), - gasPriceUpdates: new Internal.GasPriceUpdate[](0) - }); - } - function _getSingleTokenPriceFeedUpdateStruct( address sourceToken, address dataFeedAddress, @@ -273,14 +276,6 @@ contract FeeQuoterSetup is TokenSetup { assertEq(config1.isEnabled, config2.isEnabled); } - function _assertTokenPriceFeedConfigNotConfigured( - FeeQuoter.TokenPriceFeedConfig memory config - ) internal pure virtual { - _assertTokenPriceFeedConfigEquality( - config, FeeQuoter.TokenPriceFeedConfig({dataFeedAddress: address(0), tokenDecimals: 0, isEnabled: false}) - ); - } - function _assertTokenTransferFeeConfigEqual( FeeQuoter.TokenTransferFeeConfig memory a, FeeQuoter.TokenTransferFeeConfig memory b @@ -293,14 +288,6 @@ contract FeeQuoterSetup is TokenSetup { assertEq(a.isEnabled, b.isEnabled); } - function _assertFeeQuoterStaticConfigsEqual( - FeeQuoter.StaticConfig memory a, - FeeQuoter.StaticConfig memory b - ) internal pure { - assertEq(a.linkToken, b.linkToken); - assertEq(a.maxFeeJuelsPerMsg, b.maxFeeJuelsPerMsg); - } - function _assertFeeQuoterDestChainConfigsEqual( FeeQuoter.DestChainConfig memory a, FeeQuoter.DestChainConfig memory b @@ -323,19 +310,12 @@ contract FeeQuoterSetup is TokenSetup { contract FeeQuoterFeeSetup is FeeQuoterSetup { uint224 internal s_feeTokenPrice; uint224 internal s_wrappedTokenPrice; - uint224 internal s_customTokenPrice; - - address internal s_selfServeTokenDefaultPricing = makeAddr("self-serve-token-default-pricing"); - - address internal s_destTokenPool = makeAddr("destTokenPool"); - address internal s_destToken = makeAddr("destToken"); function setUp() public virtual override { super.setUp(); s_feeTokenPrice = s_sourceTokenPrices[0]; s_wrappedTokenPrice = s_sourceTokenPrices[2]; - s_customTokenPrice = CUSTOM_TOKEN_PRICE; s_feeQuoter.updatePrices(_getSingleTokenPriceUpdateStruct(CUSTOM_TOKEN, CUSTOM_TOKEN_PRICE)); } @@ -366,48 +346,6 @@ contract FeeQuoterFeeSetup is FeeQuoterSetup { }); } - function _messageToEvent( - Client.EVM2AnyMessage memory message, - uint64 sourceChainSelector, - uint64 destChainSelector, - uint64 seqNum, - uint64 nonce, - uint256 feeTokenAmount, - uint256 feeValueJuels, - address originalSender, - bytes32 metadataHash, - TokenAdminRegistry tokenAdminRegistry - ) internal view returns (Internal.EVM2AnyRampMessage memory) { - Client.EVMExtraArgsV2 memory extraArgs = - s_feeQuoter.parseEVMExtraArgsFromBytes(message.extraArgs, destChainSelector); - - Internal.EVM2AnyRampMessage memory messageEvent = Internal.EVM2AnyRampMessage({ - header: Internal.RampMessageHeader({ - messageId: "", - sourceChainSelector: sourceChainSelector, - destChainSelector: destChainSelector, - sequenceNumber: seqNum, - nonce: extraArgs.allowOutOfOrderExecution ? 0 : nonce - }), - sender: originalSender, - data: message.data, - receiver: message.receiver, - extraArgs: Client._argsToBytes(extraArgs), - feeToken: message.feeToken, - feeTokenAmount: feeTokenAmount, - feeValueJuels: feeValueJuels, - tokenAmounts: new Internal.EVM2AnyTokenTransfer[](message.tokenAmounts.length) - }); - - for (uint256 i = 0; i < message.tokenAmounts.length; ++i) { - messageEvent.tokenAmounts[i] = - _getSourceTokenData(message.tokenAmounts[i], tokenAdminRegistry, DEST_CHAIN_SELECTOR); - } - - messageEvent.header.messageId = Internal._hash(messageEvent, metadataHash); - return messageEvent; - } - function _getSourceTokenData( Client.EVMTokenAmount memory tokenAmount, TokenAdminRegistry tokenAdminRegistry, @@ -430,14 +368,6 @@ contract FeeQuoterFeeSetup is FeeQuoterSetup { }); } - function _calcUSDValueFromTokenAmount(uint224 tokenPrice, uint256 tokenAmount) internal pure returns (uint256) { - return (tokenPrice * tokenAmount) / 1e18; - } - - function _applyBpsRatio(uint256 tokenAmount, uint16 ratio) internal pure returns (uint256) { - return (tokenAmount * ratio) / 1e5; - } - function _configUSDCentToWei( uint256 usdCent ) internal pure returns (uint256) { diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.batchExecute.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.batchExecute.t.sol index 6dade484aee..aef54612945 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.batchExecute.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.batchExecute.t.sol @@ -25,7 +25,7 @@ contract OffRamp_batchExecute is OffRampSetup { s_offRamp.batchExecute( _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -54,7 +54,7 @@ contract OffRamp_batchExecute is OffRampSetup { s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](2)); Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[0].header.sourceChainSelector, messages1[0].header.sequenceNumber, @@ -64,7 +64,7 @@ contract OffRamp_batchExecute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[1].header.sourceChainSelector, messages1[1].header.sequenceNumber, @@ -74,7 +74,7 @@ contract OffRamp_batchExecute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages2[0].header.sourceChainSelector, messages2[0].header.sequenceNumber, @@ -105,7 +105,7 @@ contract OffRamp_batchExecute is OffRampSetup { Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[0].header.sourceChainSelector, messages1[0].header.sequenceNumber, @@ -115,7 +115,7 @@ contract OffRamp_batchExecute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[1].header.sourceChainSelector, messages1[1].header.sequenceNumber, @@ -125,7 +125,7 @@ contract OffRamp_batchExecute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages2[0].header.sourceChainSelector, messages2[0].header.sequenceNumber, @@ -195,7 +195,7 @@ contract OffRamp_batchExecute is OffRampSetup { vm.recordLogs(); s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](2)); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.ccipReceive.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.ccipReceive.t.sol index f4e6be1b8aa..c05d8ec476a 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.ccipReceive.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.ccipReceive.t.sol @@ -6,8 +6,7 @@ import {OffRampSetup} from "./OffRampSetup.t.sol"; contract OffRamp_ccipReceive is OffRampSetup { function test_RevertWhen_Always() public { - Client.Any2EVMMessage memory message = - _convertToGeneralMessage(_generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1)); + Client.Any2EVMMessage memory message; vm.expectRevert(); diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.constructor.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.constructor.t.sol index da23daac0ed..bd7bb94344c 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.constructor.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.constructor.t.sol @@ -188,7 +188,7 @@ contract OffRamp_constructor is OffRampSetup { s_offRamp = new OffRampHelper( OffRamp.StaticConfig({ chainSelector: DEST_CHAIN_SELECTOR, - rmnRemote: IRMNRemote(ZERO_ADDRESS), + rmnRemote: IRMNRemote(address(0)), tokenAdminRegistry: address(s_tokenAdminRegistry), nonceManager: address(s_inboundNonceManager) }), @@ -229,7 +229,7 @@ contract OffRamp_constructor is OffRampSetup { OffRamp.StaticConfig({ chainSelector: DEST_CHAIN_SELECTOR, rmnRemote: s_mockRMNRemote, - tokenAdminRegistry: ZERO_ADDRESS, + tokenAdminRegistry: address(0), nonceManager: address(s_inboundNonceManager) }), _generateDynamicOffRampConfig(address(s_feeQuoter)), @@ -250,7 +250,7 @@ contract OffRamp_constructor is OffRampSetup { chainSelector: DEST_CHAIN_SELECTOR, rmnRemote: s_mockRMNRemote, tokenAdminRegistry: address(s_tokenAdminRegistry), - nonceManager: ZERO_ADDRESS + nonceManager: address(0) }), _generateDynamicOffRampConfig(address(s_feeQuoter)), sourceChainConfigs diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.execute.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.execute.t.sol index b1c33efb106..9fd2499ef28 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.execute.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.execute.t.sol @@ -32,7 +32,7 @@ contract OffRamp_execute is OffRampSetup { _execute(reports); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -64,7 +64,7 @@ contract OffRamp_execute is OffRampSetup { Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[0].header.sourceChainSelector, messages1[0].header.sequenceNumber, @@ -74,7 +74,7 @@ contract OffRamp_execute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[1].header.sourceChainSelector, messages1[1].header.sequenceNumber, @@ -84,7 +84,7 @@ contract OffRamp_execute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages2[0].header.sourceChainSelector, messages2[0].header.sequenceNumber, @@ -118,7 +118,7 @@ contract OffRamp_execute is OffRampSetup { for (uint64 i = 0; i < reports.length; ++i) { for (uint64 j = 0; j < reports[i].messages.length; ++j) { - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, reports[i].messages[j].header.sourceChainSelector, reports[i].messages[j].header.sequenceNumber, @@ -158,7 +158,7 @@ contract OffRamp_execute is OffRampSetup { Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[0].header.sourceChainSelector, messages1[0].header.sequenceNumber, @@ -171,7 +171,7 @@ contract OffRamp_execute is OffRampSetup { ) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages1[1].header.sourceChainSelector, messages1[1].header.sequenceNumber, @@ -181,7 +181,7 @@ contract OffRamp_execute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, messages2[0].header.sourceChainSelector, messages2[0].header.sequenceNumber, diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleReport.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleReport.t.sol index aa5b2e93d5a..e651ad3836a 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleReport.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleReport.t.sol @@ -30,7 +30,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -48,7 +48,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -71,7 +71,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -95,7 +95,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -151,7 +151,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -196,7 +196,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -214,7 +214,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -241,7 +241,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -271,7 +271,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -291,7 +291,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { vm.resumeGasMetering(); vm.recordLogs(); s_offRamp.executeSingleReport(report, new OffRamp.GasLimitOverride[](0)); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -316,7 +316,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport(report, new OffRamp.GasLimitOverride[](0)); Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, @@ -326,7 +326,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[1].header.sequenceNumber, @@ -353,7 +353,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, @@ -362,7 +362,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { Internal.MessageExecutionState.SUCCESS, "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[1].header.sequenceNumber, @@ -410,7 +410,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { uint256(Internal.MessageExecutionState.SUCCESS) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[i].header.sequenceNumber, @@ -440,7 +440,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -623,7 +623,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { vm.recordLogs(); s_offRamp.executeSingleReport(executionReport, new OffRamp.GasLimitOverride[](0)); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -649,7 +649,7 @@ contract OffRamp_executeSingleReport is OffRampSetup { s_offRamp.executeSingleReport( _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber, messages[0].header.messageId, diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.manuallyExecute.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.manuallyExecute.t.sol index 91afbdfac8c..0422053bdd7 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.manuallyExecute.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.manuallyExecute.t.sol @@ -13,6 +13,8 @@ import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/ import {Vm} from "forge-std/Vm.sol"; contract OffRamp_manuallyExecute is OffRampSetup { + uint32 internal constant MAX_TOKEN_POOL_RELEASE_OR_MINT_GAS = 200_000; + function setUp() public virtual override { super.setUp(); _setupMultipleOffRamps(); @@ -37,7 +39,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { vm.recordLogs(); s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -63,7 +65,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { gasLimitOverrides[0][0].receiverExecutionGasLimit += 1; vm.recordLogs(); s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -90,7 +92,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { vm.recordLogs(); s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -148,7 +150,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { Vm.Log[] memory logs = vm.getRecordedLogs(); for (uint256 j = 0; j < 3; ++j) { - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages1[j].header.sequenceNumber, @@ -160,7 +162,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { } for (uint256 k = 0; k < 2; ++k) { - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_3, messages2[k].header.sequenceNumber, @@ -189,7 +191,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, @@ -199,7 +201,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { "" ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[1].header.sequenceNumber, @@ -212,7 +214,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { ) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( logs, SOURCE_CHAIN_SELECTOR_1, messages[2].header.sequenceNumber, @@ -234,7 +236,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { vm.recordLogs(); s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, newMessages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -255,7 +257,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { s_offRamp.batchExecute( _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) ); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -273,7 +275,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { vm.recordLogs(); s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, @@ -516,7 +518,7 @@ contract OffRamp_manuallyExecute is OffRampSetup { vm.recordLogs(); s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( + _assertExecutionStateChangedEventLogs( SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber, messages[0].header.messageId, diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintTokens.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintTokens.t.sol index 40a4514eb70..22f82bdf694 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintTokens.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintTokens.t.sol @@ -257,4 +257,20 @@ contract OffRamp_releaseOrMintTokens is OffRampSetup { } } } + + function _getDefaultSourceTokenData( + Client.EVMTokenAmount[] memory srcTokenAmounts + ) internal view returns (Internal.Any2EVMTokenTransfer[] memory) { + Internal.Any2EVMTokenTransfer[] memory sourceTokenData = new Internal.Any2EVMTokenTransfer[](srcTokenAmounts.length); + for (uint256 i = 0; i < srcTokenAmounts.length; ++i) { + sourceTokenData[i] = Internal.Any2EVMTokenTransfer({ + sourcePoolAddress: abi.encode(s_sourcePoolByToken[srcTokenAmounts[i].token]), + destTokenAddress: s_destTokenBySourceToken[srcTokenAmounts[i].token], + extraData: "", + amount: srcTokenAmounts[i].amount, + destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD + }); + } + return sourceTokenData; + } } diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.setDynamicConfig.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.setDynamicConfig.t.sol index 2b67f09c0e1..384d9b446aa 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.setDynamicConfig.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.setDynamicConfig.t.sol @@ -43,7 +43,7 @@ contract OffRamp_setDynamicConfig is OffRampSetup { } function test_FeeQuoterZeroAddress_Revert() public { - OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(ZERO_ADDRESS); + OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(address(0)); vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRampSetup.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRampSetup.t.sol index 7b9d422df15..68b32390c0a 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRampSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRampSetup.t.sol @@ -11,7 +11,6 @@ import {Client} from "../../../libraries/Client.sol"; import {Internal} from "../../../libraries/Internal.sol"; import {MultiOCR3Base} from "../../../ocr/MultiOCR3Base.sol"; import {OffRamp} from "../../../offRamp/OffRamp.sol"; -import {TokenPool} from "../../../pools/TokenPool.sol"; import {FeeQuoterSetup} from "../../feeQuoter/FeeQuoterSetup.t.sol"; import {MaybeRevertingBurnMintTokenPool} from "../../helpers/MaybeRevertingBurnMintTokenPool.sol"; import {MessageInterceptorHelper} from "../../helpers/MessageInterceptorHelper.sol"; @@ -25,12 +24,12 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { uint64 internal constant SOURCE_CHAIN_SELECTOR_2 = 6433500567565415381; uint64 internal constant SOURCE_CHAIN_SELECTOR_3 = 4051577828743386545; + address internal constant ON_RAMP_ADDRESS = 0x11118e64e1FB0c487f25dD6D3601FF6aF8d32E4e; + bytes internal constant ON_RAMP_ADDRESS_1 = abi.encode(ON_RAMP_ADDRESS); bytes internal constant ON_RAMP_ADDRESS_2 = abi.encode(0xaA3f843Cf8E33B1F02dd28303b6bD87B1aBF8AE4); bytes internal constant ON_RAMP_ADDRESS_3 = abi.encode(0x71830C37Cb193e820de488Da111cfbFcC680a1b9); - address internal constant BLESS_VOTE_ADDR = address(8888); - IAny2EVMMessageReceiver internal s_receiver; IAny2EVMMessageReceiver internal s_secondary_receiver; MaybeRevertMessageReceiver internal s_reverting_receiver; @@ -40,11 +39,9 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { OffRampHelper internal s_offRamp; MessageInterceptorHelper internal s_inboundMessageInterceptor; NonceManager internal s_inboundNonceManager; - address internal s_sourceTokenPool = makeAddr("sourceTokenPool"); bytes32 internal s_configDigestExec; bytes32 internal s_configDigestCommit; - uint64 internal constant OFFCHAIN_CONFIG_VERSION = 3; uint8 internal constant F = 1; uint64 internal s_latestSequenceNumber; @@ -165,44 +162,17 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { s_destRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); } - uint32 internal constant MAX_TOKEN_POOL_RELEASE_OR_MINT_GAS = 200_000; - uint32 internal constant MAX_TOKEN_POOL_TRANSFER_GAS = 50_000; - function _generateDynamicOffRampConfig( address feeQuoter ) internal pure returns (OffRamp.DynamicConfig memory) { return OffRamp.DynamicConfig({ feeQuoter: feeQuoter, - permissionLessExecutionThresholdSeconds: PERMISSION_LESS_EXECUTION_THRESHOLD_SECONDS, + permissionLessExecutionThresholdSeconds: 60 * 60, isRMNVerificationDisabled: false, messageInterceptor: address(0) }); } - function _convertToGeneralMessage( - Internal.Any2EVMRampMessage memory original - ) internal view returns (Client.Any2EVMMessage memory message) { - uint256 numberOfTokens = original.tokenAmounts.length; - Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](numberOfTokens); - - for (uint256 i = 0; i < numberOfTokens; ++i) { - Internal.Any2EVMTokenTransfer memory tokenAmount = original.tokenAmounts[i]; - - address destPoolAddress = tokenAmount.destTokenAddress; - TokenPool pool = TokenPool(destPoolAddress); - destTokenAmounts[i].token = address(pool.getToken()); - destTokenAmounts[i].amount = tokenAmount.amount; - } - - return Client.Any2EVMMessage({ - messageId: original.header.messageId, - sourceChainSelector: original.header.sourceChainSelector, - sender: abi.encode(original.sender), - data: original.data, - destTokenAmounts: destTokenAmounts - }); - } - function _generateAny2EVMMessageNoTokens( uint64 sourceChainSelector, bytes memory onRamp, @@ -290,6 +260,18 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { return messages; } + function _getCastedSourceEVMTokenAmountsWithZeroAmounts() + internal + view + returns (Client.EVMTokenAmount[] memory tokenAmounts) + { + tokenAmounts = new Client.EVMTokenAmount[](s_sourceTokens.length); + for (uint256 i = 0; i < tokenAmounts.length; ++i) { + tokenAmounts[i].token = s_sourceTokens[i]; + } + return tokenAmounts; + } + function _generateReportFromMessages( uint64 sourceChainSelector, Internal.Any2EVMRampMessage[] memory messages @@ -345,22 +327,6 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { assertEq(address(config1.router), address(config2.router)); } - function _getDefaultSourceTokenData( - Client.EVMTokenAmount[] memory srcTokenAmounts - ) internal view returns (Internal.Any2EVMTokenTransfer[] memory) { - Internal.Any2EVMTokenTransfer[] memory sourceTokenData = new Internal.Any2EVMTokenTransfer[](srcTokenAmounts.length); - for (uint256 i = 0; i < srcTokenAmounts.length; ++i) { - sourceTokenData[i] = Internal.Any2EVMTokenTransfer({ - sourcePoolAddress: abi.encode(s_sourcePoolByToken[srcTokenAmounts[i].token]), - destTokenAddress: s_destTokenBySourceToken[srcTokenAmounts[i].token], - extraData: "", - amount: srcTokenAmounts[i].amount, - destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD - }); - } - return sourceTokenData; - } - function _enableInboundMessageInterceptor() internal { OffRamp.DynamicConfig memory dynamicConfig = s_offRamp.getDynamicConfig(); dynamicConfig.messageInterceptor = address(s_inboundMessageInterceptor); @@ -412,14 +378,14 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { s_offRamp.execute(reportContext, abi.encode(reports)); } - function assertExecutionStateChangedEventLogs( + function _assertExecutionStateChangedEventLogs( uint64 sourceChainSelector, uint64 sequenceNumber, bytes32 messageId, bytes32 messageHash, Internal.MessageExecutionState state, bytes memory returnData - ) public { + ) internal { Vm.Log[] memory logs = vm.getRecordedLogs(); for (uint256 i = 0; i < logs.length; ++i) { if (logs[i].topics[0] == OffRamp.ExecutionStateChanged.selector) { @@ -440,7 +406,7 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { } } - function assertExecutionStateChangedEventLogs( + function _assertExecutionStateChangedEventLogs( Vm.Log[] memory logs, uint64 sourceChainSelector, uint64 sequenceNumber, @@ -448,7 +414,7 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { bytes32 messageHash, Internal.MessageExecutionState state, bytes memory returnData - ) public pure { + ) internal pure { for (uint256 i = 0; i < logs.length; ++i) { if (logs[i].topics[0] == OffRamp.ExecutionStateChanged.selector) { uint64 logSourceChainSelector = uint64(uint256(logs[i].topics[1])); @@ -473,7 +439,7 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { ) internal { Vm.Log[] memory logs = vm.getRecordedLogs(); - for (uint256 i = 0; i < logs.length; i++) { + for (uint256 i = 0; i < logs.length; ++i) { assertTrue(logs[i].topics[0] != eventSelector); } } @@ -494,4 +460,11 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { ) ); } + + function _getEmptyPriceUpdates() internal pure returns (Internal.PriceUpdates memory priceUpdates) { + return Internal.PriceUpdates({ + tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), + gasPriceUpdates: new Internal.GasPriceUpdate[](0) + }); + } } diff --git a/contracts/src/v0.8/ccip/test/onRamp/OnRamp.t.sol b/contracts/src/v0.8/ccip/test/onRamp/OnRamp.t.sol deleted file mode 100644 index b2687063ea6..00000000000 --- a/contracts/src/v0.8/ccip/test/onRamp/OnRamp.t.sol +++ /dev/null @@ -1,1087 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {IMessageInterceptor} from "../../interfaces/IMessageInterceptor.sol"; -import {IRMNRemote} from "../../interfaces/IRMNRemote.sol"; -import {IRouter} from "../../interfaces/IRouter.sol"; - -import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; -import {BurnMintERC677} from "../../../shared/token/ERC677/BurnMintERC677.sol"; -import {FeeQuoter} from "../../FeeQuoter.sol"; -import {Client} from "../../libraries/Client.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {Pool} from "../../libraries/Pool.sol"; -import {USDPriceWith18Decimals} from "../../libraries/USDPriceWith18Decimals.sol"; -import {OnRamp} from "../../onRamp/OnRamp.sol"; -import {TokenPool} from "../../pools/TokenPool.sol"; -import {MaybeRevertingBurnMintTokenPool} from "../helpers/MaybeRevertingBurnMintTokenPool.sol"; -import {OnRampHelper} from "../helpers/OnRampHelper.sol"; -import {OnRampSetup} from "./OnRampSetup.t.sol"; - -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; - -contract OnRamp_constructor is OnRampSetup { - function test_Constructor_Success() public { - OnRamp.StaticConfig memory staticConfig = OnRamp.StaticConfig({ - chainSelector: SOURCE_CHAIN_SELECTOR, - rmnRemote: s_mockRMNRemote, - nonceManager: address(s_outboundNonceManager), - tokenAdminRegistry: address(s_tokenAdminRegistry) - }); - OnRamp.DynamicConfig memory dynamicConfig = _generateDynamicOnRampConfig(address(s_feeQuoter)); - - vm.expectEmit(); - emit OnRamp.ConfigSet(staticConfig, dynamicConfig); - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); - - _deployOnRamp(SOURCE_CHAIN_SELECTOR, s_sourceRouter, address(s_outboundNonceManager), address(s_tokenAdminRegistry)); - - OnRamp.StaticConfig memory gotStaticConfig = s_onRamp.getStaticConfig(); - _assertStaticConfigsEqual(staticConfig, gotStaticConfig); - - OnRamp.DynamicConfig memory gotDynamicConfig = s_onRamp.getDynamicConfig(); - _assertDynamicConfigsEqual(dynamicConfig, gotDynamicConfig); - - // Initial values - assertEq("OnRamp 1.6.0-dev", s_onRamp.typeAndVersion()); - assertEq(OWNER, s_onRamp.owner()); - assertEq(1, s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR)); - } - - function test_Constructor_EnableAllowList_ForwardFromRouter_Reverts() public { - OnRamp.StaticConfig memory staticConfig = OnRamp.StaticConfig({ - chainSelector: SOURCE_CHAIN_SELECTOR, - rmnRemote: s_mockRMNRemote, - nonceManager: address(s_outboundNonceManager), - tokenAdminRegistry: address(s_tokenAdminRegistry) - }); - - OnRamp.DynamicConfig memory dynamicConfig = _generateDynamicOnRampConfig(address(s_feeQuoter)); - - // Creating a DestChainConfig and setting allowlistEnabled : true - OnRamp.DestChainConfigArgs[] memory destChainConfigs = new OnRamp.DestChainConfigArgs[](1); - destChainConfigs[0] = OnRamp.DestChainConfigArgs({ - destChainSelector: DEST_CHAIN_SELECTOR, - router: s_sourceRouter, - allowlistEnabled: true - }); - - vm.expectEmit(); - emit OnRamp.ConfigSet(staticConfig, dynamicConfig); - - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, true); - - OnRampHelper tempOnRamp = new OnRampHelper(staticConfig, dynamicConfig, destChainConfigs); - - // Sending a message and expecting revert as allowlist is enabled with no address in allowlist - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - vm.startPrank(address(s_sourceRouter)); - vm.expectRevert(abi.encodeWithSelector(OnRamp.SenderNotAllowed.selector, OWNER)); - tempOnRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - } - - function test_Constructor_InvalidConfigChainSelectorEqZero_Revert() public { - vm.expectRevert(OnRamp.InvalidConfig.selector); - new OnRampHelper( - OnRamp.StaticConfig({ - chainSelector: 0, - rmnRemote: s_mockRMNRemote, - nonceManager: address(s_outboundNonceManager), - tokenAdminRegistry: address(s_tokenAdminRegistry) - }), - _generateDynamicOnRampConfig(address(s_feeQuoter)), - _generateDestChainConfigArgs(IRouter(address(0))) - ); - } - - function test_Constructor_InvalidConfigRMNProxyEqAddressZero_Revert() public { - vm.expectRevert(OnRamp.InvalidConfig.selector); - s_onRamp = new OnRampHelper( - OnRamp.StaticConfig({ - chainSelector: SOURCE_CHAIN_SELECTOR, - rmnRemote: IRMNRemote(address(0)), - nonceManager: address(s_outboundNonceManager), - tokenAdminRegistry: address(s_tokenAdminRegistry) - }), - _generateDynamicOnRampConfig(address(s_feeQuoter)), - _generateDestChainConfigArgs(IRouter(address(0))) - ); - } - - function test_Constructor_InvalidConfigNonceManagerEqAddressZero_Revert() public { - vm.expectRevert(OnRamp.InvalidConfig.selector); - new OnRampHelper( - OnRamp.StaticConfig({ - chainSelector: SOURCE_CHAIN_SELECTOR, - rmnRemote: s_mockRMNRemote, - nonceManager: address(0), - tokenAdminRegistry: address(s_tokenAdminRegistry) - }), - _generateDynamicOnRampConfig(address(s_feeQuoter)), - _generateDestChainConfigArgs(IRouter(address(0))) - ); - } - - function test_Constructor_InvalidConfigTokenAdminRegistryEqAddressZero_Revert() public { - vm.expectRevert(OnRamp.InvalidConfig.selector); - new OnRampHelper( - OnRamp.StaticConfig({ - chainSelector: SOURCE_CHAIN_SELECTOR, - rmnRemote: s_mockRMNRemote, - nonceManager: address(s_outboundNonceManager), - tokenAdminRegistry: address(0) - }), - _generateDynamicOnRampConfig(address(s_feeQuoter)), - _generateDestChainConfigArgs(IRouter(address(0))) - ); - } -} - -contract OnRamp_forwardFromRouter is OnRampSetup { - struct LegacyExtraArgs { - uint256 gasLimit; - bool strict; - } - - function setUp() public virtual override { - super.setUp(); - - address[] memory feeTokens = new address[](1); - feeTokens[0] = s_sourceTokens[1]; - s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); - - uint64[] memory destinationChainSelectors = new uint64[](1); - destinationChainSelectors[0] = DEST_CHAIN_SELECTOR; - address[] memory addAllowedList = new address[](1); - addAllowedList[0] = OWNER; - OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ - allowlistEnabled: true, - destChainSelector: DEST_CHAIN_SELECTOR, - addedAllowlistedSenders: addAllowedList, - removedAllowlistedSenders: new address[](0) - }); - OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); - applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; - s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); - - // Since we'll mostly be testing for valid calls from the router we'll - // mock all calls to be originating from the router and re-mock in - // tests that require failure. - vm.startPrank(address(s_sourceRouter)); - } - - function test_ForwardFromRouterSuccessCustomExtraArgs() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_ForwardFromRouter_Success_ConfigurableSourceRouter() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - // Change the source router for this lane - IRouter newRouter = IRouter(makeAddr("NEW ROUTER")); - vm.stopPrank(); - vm.prank(OWNER); - s_onRamp.applyDestChainConfigUpdates(_generateDestChainConfigArgs(newRouter)); - - // forward fails from wrong router - vm.prank(address(s_sourceRouter)); - vm.expectRevert(OnRamp.MustBeCalledByRouter.selector); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - - // forward succeeds from correct router - vm.prank(address(newRouter)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_ForwardFromRouterSuccessLegacyExtraArgs() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = - abi.encodeWithSelector(Client.EVM_EXTRA_ARGS_V1_TAG, LegacyExtraArgs({gasLimit: GAS_LIMIT * 2, strict: true})); - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - vm.expectEmit(); - // We expect the message to be emitted with strict = false. - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_ForwardFromRouterSuccessEmptyExtraArgs() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = ""; - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - vm.expectEmit(); - // We expect the message to be emitted with strict = false. - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_ForwardFromRouter_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_ForwardFromRouterExtraArgsV2_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = abi.encodeWithSelector( - Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: false}) - ); - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_ForwardFromRouterExtraArgsV2AllowOutOfOrderTrue_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = abi.encodeWithSelector( - Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: true}) - ); - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_ShouldIncrementSeqNumAndNonce_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - for (uint64 i = 1; i < 4; ++i) { - uint64 nonceBefore = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); - uint64 sequenceNumberBefore = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, i, _messageToEvent(message, i, i, 0, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - - uint64 nonceAfter = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); - uint64 sequenceNumberAfter = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; - assertEq(nonceAfter, nonceBefore + 1); - assertEq(sequenceNumberAfter, sequenceNumberBefore + 1); - } - } - - function test_ShouldIncrementNonceOnlyOnOrdered_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = abi.encodeWithSelector( - Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: true}) - ); - - for (uint64 i = 1; i < 4; ++i) { - uint64 nonceBefore = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); - uint64 sequenceNumberBefore = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, i, _messageToEvent(message, i, i, 0, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - - uint64 nonceAfter = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); - uint64 sequenceNumberAfter = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; - assertEq(nonceAfter, nonceBefore); - assertEq(sequenceNumberAfter, sequenceNumberBefore + 1); - } - } - - function test_ShouldStoreLinkFees() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - uint256 feeAmount = 1234567890; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - - assertEq(IERC20(s_sourceFeeToken).balanceOf(address(s_onRamp)), feeAmount); - } - - function test_ShouldStoreNonLinkFees() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = s_sourceTokens[1]; - - uint256 feeAmount = 1234567890; - IERC20(s_sourceTokens[1]).transferFrom(OWNER, address(s_onRamp), feeAmount); - - // Calculate conversion done by prices contract - uint256 feeTokenPrice = s_feeQuoter.getTokenPrice(s_sourceTokens[1]).value; - uint256 linkTokenPrice = s_feeQuoter.getTokenPrice(s_sourceFeeToken).value; - uint256 conversionRate = (feeTokenPrice * 1e18) / linkTokenPrice; - uint256 expectedJuels = (feeAmount * conversionRate) / 1e18; - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, expectedJuels, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - - assertEq(IERC20(s_sourceTokens[1]).balanceOf(address(s_onRamp)), feeAmount); - } - - // Make sure any valid sender, receiver and feeAmount can be handled. - // @TODO Temporarily setting lower fuzz run as 256 triggers snapshot gas off by 1 error. - // https://github.com/foundry-rs/foundry/issues/5689 - /// forge-dynamicConfig: default.fuzz.runs = 32 - /// forge-dynamicConfig: ccip.fuzz.runs = 32 - function test_Fuzz_ForwardFromRouter_Success(address originalSender, address receiver, uint96 feeTokenAmount) public { - // To avoid RouterMustSetOriginalSender - vm.assume(originalSender != address(0)); - vm.assume(uint160(receiver) >= Internal.PRECOMPILE_SPACE); - feeTokenAmount = uint96(bound(feeTokenAmount, 0, MAX_MSG_FEES_JUELS)); - vm.stopPrank(); - - vm.startPrank(OWNER); - uint64[] memory destinationChainSelectors = new uint64[](1); - destinationChainSelectors[0] = uint64(DEST_CHAIN_SELECTOR); - address[] memory addAllowedList = new address[](1); - addAllowedList[0] = originalSender; - OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ - allowlistEnabled: true, - destChainSelector: DEST_CHAIN_SELECTOR, - addedAllowlistedSenders: addAllowedList, - removedAllowlistedSenders: new address[](0) - }); - OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); - applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; - s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); - vm.stopPrank(); - - vm.startPrank(address(s_sourceRouter)); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.receiver = abi.encode(receiver); - - // Make sure the tokens are in the contract - deal(s_sourceFeeToken, address(s_onRamp), feeTokenAmount); - - Internal.EVM2AnyRampMessage memory expectedEvent = _messageToEvent(message, 1, 1, feeTokenAmount, originalSender); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, expectedEvent.header.sequenceNumber, expectedEvent); - - // Assert the message Id is correct - assertEq( - expectedEvent.header.messageId, - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeTokenAmount, originalSender) - ); - } - - function test_forwardFromRouter_WithInterception_Success() public { - _enableOutboundMessageInterceptor(); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); - uint256 feeAmount = 1234567890; - message.tokenAmounts = new Client.EVMTokenAmount[](1); - message.tokenAmounts[0].amount = 1e18; - message.tokenAmounts[0].token = s_sourceTokens[0]; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - s_outboundMessageInterceptor.setMessageIdValidationState(keccak256(abi.encode(message)), false); - - vm.expectEmit(); - emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - // Reverts - - function test_Paused_Revert() public { - // We pause by disabling the whitelist - vm.stopPrank(); - vm.startPrank(OWNER); - s_onRamp.setDynamicConfig(_generateDynamicOnRampConfig(address(2))); - vm.expectRevert(OnRamp.MustBeCalledByRouter.selector); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, OWNER); - } - - function test_InvalidExtraArgsTag_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = bytes("bad args"); - - vm.expectRevert(FeeQuoter.InvalidExtraArgsTag.selector); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - } - - function test_Permissions_Revert() public { - vm.stopPrank(); - vm.startPrank(OWNER); - vm.expectRevert(OnRamp.MustBeCalledByRouter.selector); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, OWNER); - } - - function test_OriginalSender_Revert() public { - vm.expectRevert(OnRamp.RouterMustSetOriginalSender.selector); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, address(0)); - } - - function test_UnAllowedOriginalSender_Revert() public { - vm.stopPrank(); - vm.startPrank(STRANGER); - vm.expectRevert(abi.encodeWithSelector(OnRamp.SenderNotAllowed.selector, STRANGER)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, STRANGER); - } - - function test_MessageInterceptionError_Revert() public { - _enableOutboundMessageInterceptor(); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); - uint256 feeAmount = 1234567890; - message.tokenAmounts = new Client.EVMTokenAmount[](1); - message.tokenAmounts[0].amount = 1e18; - message.tokenAmounts[0].token = s_sourceTokens[0]; - IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); - s_outboundMessageInterceptor.setMessageIdValidationState(keccak256(abi.encode(message)), true); - - vm.expectRevert( - abi.encodeWithSelector(IMessageInterceptor.MessageValidationError.selector, bytes("Invalid message")) - ); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - - function test_MultiCannotSendZeroTokens_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.tokenAmounts = new Client.EVMTokenAmount[](1); - message.tokenAmounts[0].amount = 0; - message.tokenAmounts[0].token = s_sourceTokens[0]; - vm.expectRevert(OnRamp.CannotSendZeroTokens.selector); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - } - - function test_UnsupportedToken_Revert() public { - address wrongToken = address(1); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.tokenAmounts = new Client.EVMTokenAmount[](1); - message.tokenAmounts[0].token = wrongToken; - message.tokenAmounts[0].amount = 1; - - // We need to set the price of this new token to be able to reach - // the proper revert point. This must be called by the owner. - vm.stopPrank(); - vm.startPrank(OWNER); - - Internal.PriceUpdates memory priceUpdates = _getSingleTokenPriceUpdateStruct(wrongToken, 1); - s_feeQuoter.updatePrices(priceUpdates); - - // Change back to the router - vm.startPrank(address(s_sourceRouter)); - vm.expectRevert(abi.encodeWithSelector(OnRamp.UnsupportedToken.selector, wrongToken)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - } - - function test_forwardFromRouter_UnsupportedToken_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.tokenAmounts = new Client.EVMTokenAmount[](1); - message.tokenAmounts[0].amount = 1; - message.tokenAmounts[0].token = address(1); - - vm.expectRevert(abi.encodeWithSelector(OnRamp.UnsupportedToken.selector, message.tokenAmounts[0].token)); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - } - - function test_MesssageFeeTooHigh_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - vm.expectRevert( - abi.encodeWithSelector(FeeQuoter.MessageFeeTooHigh.selector, MAX_MSG_FEES_JUELS + 1, MAX_MSG_FEES_JUELS) - ); - - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, MAX_MSG_FEES_JUELS + 1, OWNER); - } - - function test_SourceTokenDataTooLarge_Revert() public { - address sourceETH = s_sourceTokens[1]; - vm.stopPrank(); - vm.startPrank(OWNER); - - MaybeRevertingBurnMintTokenPool newPool = new MaybeRevertingBurnMintTokenPool( - BurnMintERC677(sourceETH), new address[](0), address(s_mockRMNRemote), address(s_sourceRouter) - ); - BurnMintERC677(sourceETH).grantMintAndBurnRoles(address(newPool)); - deal(address(sourceETH), address(newPool), type(uint256).max); - - // Add TokenPool to OnRamp - s_tokenAdminRegistry.setPool(sourceETH, address(newPool)); - - // Allow chain in TokenPool - TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); - chainUpdates[0] = TokenPool.ChainUpdate({ - remoteChainSelector: DEST_CHAIN_SELECTOR, - remotePoolAddress: abi.encode(s_destTokenPool), - remoteTokenAddress: abi.encode(s_destToken), - allowed: true, - outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), - inboundRateLimiterConfig: _getInboundRateLimiterConfig() - }); - newPool.applyChainUpdates(chainUpdates); - - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(address(sourceETH), 1000); - - // No data set, should succeed - vm.startPrank(address(s_sourceRouter)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - - // Set max data length, should succeed - vm.startPrank(OWNER); - newPool.setSourceTokenData(new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES)); - - vm.startPrank(address(s_sourceRouter)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - - // Set data to max length +1, should revert - vm.startPrank(OWNER); - newPool.setSourceTokenData(new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES + 1)); - - vm.startPrank(address(s_sourceRouter)); - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - - // Set token config to allow larger data - vm.startPrank(OWNER); - FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); - tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = sourceETH; - tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ - minFeeUSDCents: 0, - maxFeeUSDCents: 1, - deciBps: 0, - destGasOverhead: 0, - destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32, - isEnabled: true - }); - s_feeQuoter.applyTokenTransferFeeConfigUpdates( - tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) - ); - - vm.startPrank(address(s_sourceRouter)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - - // Set the token data larger than the configured token data, should revert - vm.startPrank(OWNER); - newPool.setSourceTokenData(new bytes(uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32 + 1)); - - vm.startPrank(address(s_sourceRouter)); - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); - } -} - -contract OnRamp_getSupportedTokens is OnRampSetup { - function test_GetSupportedTokens_Revert() public { - vm.expectRevert(OnRamp.GetSupportedTokensFunctionalityRemovedCheckAdminRegistry.selector); - s_onRamp.getSupportedTokens(DEST_CHAIN_SELECTOR); - } -} - -contract OnRamp_getFee is OnRampSetup { - using USDPriceWith18Decimals for uint224; - - function test_EmptyMessage_Success() public view { - address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; - uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; - - for (uint256 i = 0; i < feeTokenPrices.length; ++i) { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = testTokens[i]; - - uint256 feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); - uint256 expectedFeeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - - assertEq(expectedFeeAmount, feeAmount); - } - } - - function test_SingleTokenMessage_Success() public view { - address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; - uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; - - uint256 tokenAmount = 10000e18; - for (uint256 i = 0; i < feeTokenPrices.length; ++i) { - Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, tokenAmount); - message.feeToken = testTokens[i]; - - uint256 feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); - uint256 expectedFeeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); - - assertEq(expectedFeeAmount, feeAmount); - } - } - - function test_GetFeeOfZeroForTokenMessage_Success() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - - uint256 feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); - assertTrue(feeAmount > 0); - - FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory tokenMults = new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](1); - tokenMults[0] = FeeQuoter.PremiumMultiplierWeiPerEthArgs({token: message.feeToken, premiumMultiplierWeiPerEth: 0}); - s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(tokenMults); - - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = 0; - destChainConfigArgs[0].destChainConfig.gasMultiplierWeiPerEth = 0; - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - - feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); - - assertEq(0, feeAmount); - } - - // Reverts - - function test_Unhealthy_Revert() public { - _setMockRMNChainCurse(DEST_CHAIN_SELECTOR, true); - vm.expectRevert(abi.encodeWithSelector(OnRamp.CursedByRMN.selector, DEST_CHAIN_SELECTOR)); - s_onRamp.getFee(DEST_CHAIN_SELECTOR, _generateEmptyMessage()); - } - - function test_EnforceOutOfOrder_Revert() public { - // Update dynamic config to enforce allowOutOfOrderExecution = true. - vm.stopPrank(); - vm.startPrank(OWNER); - - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - destChainConfigArgs[0].destChainConfig.enforceOutOfOrder = true; - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - vm.stopPrank(); - - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - // Empty extraArgs to should revert since it enforceOutOfOrder is true. - message.extraArgs = ""; - - vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); - s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); - } - - function test_NotAFeeTokenButPricedToken_Revert() public { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = s_sourceTokens[1]; - - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.FeeTokenNotSupported.selector, message.feeToken)); - - s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); - } -} - -contract OnRamp_setDynamicConfig is OnRampSetup { - function test_setDynamicConfig_Success() public { - OnRamp.StaticConfig memory staticConfig = s_onRamp.getStaticConfig(); - OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ - feeQuoter: address(23423), - reentrancyGuardEntered: false, - messageInterceptor: makeAddr("messageInterceptor"), - feeAggregator: FEE_AGGREGATOR, - allowlistAdmin: address(0) - }); - - vm.expectEmit(); - emit OnRamp.ConfigSet(staticConfig, newConfig); - - s_onRamp.setDynamicConfig(newConfig); - - OnRamp.DynamicConfig memory gotDynamicConfig = s_onRamp.getDynamicConfig(); - assertEq(newConfig.feeQuoter, gotDynamicConfig.feeQuoter); - } - - // Reverts - - function test_setDynamicConfig_InvalidConfigFeeQuoterEqAddressZero_Revert() public { - OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ - feeQuoter: address(0), - reentrancyGuardEntered: false, - feeAggregator: FEE_AGGREGATOR, - messageInterceptor: makeAddr("messageInterceptor"), - allowlistAdmin: address(0) - }); - - vm.expectRevert(OnRamp.InvalidConfig.selector); - s_onRamp.setDynamicConfig(newConfig); - } - - function test_setDynamicConfig_InvalidConfigInvalidConfig_Revert() public { - OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ - feeQuoter: address(23423), - reentrancyGuardEntered: false, - messageInterceptor: address(0), - feeAggregator: FEE_AGGREGATOR, - allowlistAdmin: address(0) - }); - - // Invalid price reg reverts. - newConfig.feeQuoter = address(0); - vm.expectRevert(OnRamp.InvalidConfig.selector); - s_onRamp.setDynamicConfig(newConfig); - } - - function test_setDynamicConfig_InvalidConfigFeeAggregatorEqAddressZero_Revert() public { - OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ - feeQuoter: address(23423), - reentrancyGuardEntered: false, - messageInterceptor: address(0), - feeAggregator: address(0), - allowlistAdmin: address(0) - }); - - vm.expectRevert(OnRamp.InvalidConfig.selector); - s_onRamp.setDynamicConfig(newConfig); - } - - function test_setDynamicConfig_InvalidConfigOnlyOwner_Revert() public { - vm.startPrank(STRANGER); - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_onRamp.setDynamicConfig(_generateDynamicOnRampConfig(address(2))); - vm.startPrank(ADMIN); - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - s_onRamp.setDynamicConfig(_generateDynamicOnRampConfig(address(2))); - } - - function test_setDynamicConfig_InvalidConfigReentrancyGuardEnteredEqTrue_Revert() public { - OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ - feeQuoter: address(23423), - reentrancyGuardEntered: true, - messageInterceptor: makeAddr("messageInterceptor"), - feeAggregator: FEE_AGGREGATOR, - allowlistAdmin: address(0) - }); - - vm.expectRevert(OnRamp.InvalidConfig.selector); - s_onRamp.setDynamicConfig(newConfig); - } -} - -contract OnRamp_withdrawFeeTokens is OnRampSetup { - mapping(address => uint256) internal s_nopFees; - - function setUp() public virtual override { - super.setUp(); - - // Since we'll mostly be testing for valid calls from the router we'll - // mock all calls to be originating from the router and re-mock in - // tests that require failure. - vm.startPrank(address(s_sourceRouter)); - - uint256 feeAmount = 1234567890; - - // Send a bunch of messages, increasing the juels in the contract - for (uint256 i = 0; i < s_sourceFeeTokens.length; ++i) { - Client.EVM2AnyMessage memory message = _generateEmptyMessage(); - message.feeToken = s_sourceFeeTokens[i % s_sourceFeeTokens.length]; - uint256 newFeeTokenBalance = IERC20(message.feeToken).balanceOf(address(s_onRamp)) + feeAmount; - deal(message.feeToken, address(s_onRamp), newFeeTokenBalance); - s_nopFees[message.feeToken] = newFeeTokenBalance; - s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); - } - } - - function test_Fuzz_WithdrawFeeTokens_Success( - uint256[5] memory amounts - ) public { - vm.startPrank(OWNER); - address[] memory feeTokens = new address[](amounts.length); - for (uint256 i = 0; i < amounts.length; ++i) { - vm.assume(amounts[i] > 0); - feeTokens[i] = _deploySourceToken("", amounts[i], 18); - IERC20(feeTokens[i]).transfer(address(s_onRamp), amounts[i]); - } - - s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); - - for (uint256 i = 0; i < feeTokens.length; ++i) { - vm.expectEmit(); - emit OnRamp.FeeTokenWithdrawn(FEE_AGGREGATOR, feeTokens[i], amounts[i]); - } - - s_onRamp.withdrawFeeTokens(feeTokens); - - for (uint256 i = 0; i < feeTokens.length; ++i) { - assertEq(IERC20(feeTokens[i]).balanceOf(FEE_AGGREGATOR), amounts[i]); - assertEq(IERC20(feeTokens[i]).balanceOf(address(s_onRamp)), 0); - } - } - - function test_WithdrawFeeTokens_Success() public { - vm.expectEmit(); - emit OnRamp.FeeTokenWithdrawn(FEE_AGGREGATOR, s_sourceFeeToken, s_nopFees[s_sourceFeeToken]); - - s_onRamp.withdrawFeeTokens(s_sourceFeeTokens); - - assertEq(IERC20(s_sourceFeeToken).balanceOf(FEE_AGGREGATOR), s_nopFees[s_sourceFeeToken]); - assertEq(IERC20(s_sourceFeeToken).balanceOf(address(s_onRamp)), 0); - } -} - -contract OnRamp_getTokenPool is OnRampSetup { - function test_GetTokenPool_Success() public view { - assertEq( - s_sourcePoolByToken[s_sourceTokens[0]], - address(s_onRamp.getPoolBySourceToken(DEST_CHAIN_SELECTOR, IERC20(s_sourceTokens[0]))) - ); - assertEq( - s_sourcePoolByToken[s_sourceTokens[1]], - address(s_onRamp.getPoolBySourceToken(DEST_CHAIN_SELECTOR, IERC20(s_sourceTokens[1]))) - ); - - address wrongToken = address(123); - address nonExistentPool = address(s_onRamp.getPoolBySourceToken(DEST_CHAIN_SELECTOR, IERC20(wrongToken))); - - assertEq(address(0), nonExistentPool); - } -} - -contract OnRamp_applyDestChainConfigUpdates is OnRampSetup { - function test_ApplyDestChainConfigUpdates_Success() external { - OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](1); - configArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; - - // supports disabling a lane by setting a router to zero - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, IRouter(address(0)), false); - - s_onRamp.applyDestChainConfigUpdates(configArgs); - - (,, address router) = s_onRamp.getDestChainConfig(DEST_CHAIN_SELECTOR); - assertEq(address(0), router); - - // supports updating and adding lanes simultaneously - configArgs = new OnRamp.DestChainConfigArgs[](2); - configArgs[0] = OnRamp.DestChainConfigArgs({ - destChainSelector: DEST_CHAIN_SELECTOR, - router: s_sourceRouter, - allowlistEnabled: false - }); - uint64 newDestChainSelector = 99999; - address newRouter = makeAddr("newRouter"); - - configArgs[1] = OnRamp.DestChainConfigArgs({ - destChainSelector: newDestChainSelector, - router: IRouter(newRouter), - allowlistEnabled: false - }); - - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(newDestChainSelector, 0, IRouter(newRouter), false); - - s_onRamp.applyDestChainConfigUpdates(configArgs); - - (,, address newGotRouter) = s_onRamp.getDestChainConfig(newDestChainSelector); - assertEq(newRouter, newGotRouter); - - // handles empty list - uint256 numLogs = vm.getRecordedLogs().length; - configArgs = new OnRamp.DestChainConfigArgs[](0); - s_onRamp.applyDestChainConfigUpdates(configArgs); - assertEq(numLogs, vm.getRecordedLogs().length); // indicates no changes made - } - - function test_ApplyDestChainConfigUpdates_WithInvalidChainSelector_Revert() external { - OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](1); - configArgs[0].destChainSelector = 0; // invalid - vm.expectRevert(abi.encodeWithSelector(OnRamp.InvalidDestChainConfig.selector, 0)); - s_onRamp.applyDestChainConfigUpdates(configArgs); - } -} - -contract OnRamp_applyAllowlistUpdates is OnRampSetup { - function test_applyAllowlistUpdates_Success() public { - OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](2); - configArgs[0] = OnRamp.DestChainConfigArgs({ - destChainSelector: DEST_CHAIN_SELECTOR, - router: s_sourceRouter, - allowlistEnabled: false - }); - configArgs[1] = - OnRamp.DestChainConfigArgs({destChainSelector: 9999, router: IRouter(address(9999)), allowlistEnabled: false}); - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(9999, 0, IRouter(address(9999)), false); - s_onRamp.applyDestChainConfigUpdates(configArgs); - - (uint64 sequenceNumber, bool allowlistEnabled, address router) = s_onRamp.getDestChainConfig(9999); - assertEq(sequenceNumber, 0); - assertEq(allowlistEnabled, false); - assertEq(router, address(9999)); - - uint64[] memory destinationChainSelectors = new uint64[](2); - destinationChainSelectors[0] = DEST_CHAIN_SELECTOR; - destinationChainSelectors[1] = uint64(99999); - - address[] memory addedAllowlistedSenders = new address[](4); - addedAllowlistedSenders[0] = vm.addr(1); - addedAllowlistedSenders[1] = vm.addr(2); - addedAllowlistedSenders[2] = vm.addr(3); - addedAllowlistedSenders[3] = vm.addr(4); - - vm.expectEmit(); - emit OnRamp.AllowListSendersAdded(DEST_CHAIN_SELECTOR, addedAllowlistedSenders); - - OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ - allowlistEnabled: true, - destChainSelector: DEST_CHAIN_SELECTOR, - addedAllowlistedSenders: addedAllowlistedSenders, - removedAllowlistedSenders: new address[](0) - }); - - OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); - applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; - - s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); - - (bool isActive, address[] memory gotAllowList) = s_onRamp.getAllowedSendersList(DEST_CHAIN_SELECTOR); - assertEq(4, gotAllowList.length); - assertEq(addedAllowlistedSenders, gotAllowList); - assertEq(true, isActive); - - address[] memory removedAllowlistedSenders = new address[](1); - removedAllowlistedSenders[0] = vm.addr(2); - - vm.expectEmit(); - emit OnRamp.AllowListSendersRemoved(DEST_CHAIN_SELECTOR, removedAllowlistedSenders); - - allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ - allowlistEnabled: false, - destChainSelector: DEST_CHAIN_SELECTOR, - addedAllowlistedSenders: new address[](0), - removedAllowlistedSenders: removedAllowlistedSenders - }); - - OnRamp.AllowlistConfigArgs[] memory allowlistConfigArgsItems_2 = new OnRamp.AllowlistConfigArgs[](1); - allowlistConfigArgsItems_2[0] = allowlistConfigArgs; - - s_onRamp.applyAllowlistUpdates(allowlistConfigArgsItems_2); - (isActive, gotAllowList) = s_onRamp.getAllowedSendersList(DEST_CHAIN_SELECTOR); - assertEq(3, gotAllowList.length); - assertFalse(isActive); - - addedAllowlistedSenders = new address[](2); - addedAllowlistedSenders[0] = vm.addr(5); - addedAllowlistedSenders[1] = vm.addr(6); - - removedAllowlistedSenders = new address[](2); - removedAllowlistedSenders[0] = vm.addr(1); - removedAllowlistedSenders[1] = vm.addr(3); - - vm.expectEmit(); - emit OnRamp.AllowListSendersAdded(DEST_CHAIN_SELECTOR, addedAllowlistedSenders); - emit OnRamp.AllowListSendersRemoved(DEST_CHAIN_SELECTOR, removedAllowlistedSenders); - - allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ - allowlistEnabled: true, - destChainSelector: DEST_CHAIN_SELECTOR, - addedAllowlistedSenders: addedAllowlistedSenders, - removedAllowlistedSenders: removedAllowlistedSenders - }); - - OnRamp.AllowlistConfigArgs[] memory allowlistConfigArgsItems_3 = new OnRamp.AllowlistConfigArgs[](1); - allowlistConfigArgsItems_3[0] = allowlistConfigArgs; - - s_onRamp.applyAllowlistUpdates(allowlistConfigArgsItems_3); - (isActive, gotAllowList) = s_onRamp.getAllowedSendersList(DEST_CHAIN_SELECTOR); - - assertEq(3, gotAllowList.length); - assertTrue(isActive); - } - - function test_applyAllowlistUpdates_Revert() public { - OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](2); - configArgs[0] = OnRamp.DestChainConfigArgs({ - destChainSelector: DEST_CHAIN_SELECTOR, - router: s_sourceRouter, - allowlistEnabled: false - }); - configArgs[1] = - OnRamp.DestChainConfigArgs({destChainSelector: 9999, router: IRouter(address(9999)), allowlistEnabled: false}); - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); - vm.expectEmit(); - emit OnRamp.DestChainConfigSet(9999, 0, IRouter(address(9999)), false); - s_onRamp.applyDestChainConfigUpdates(configArgs); - - uint64[] memory destinationChainSelectors = new uint64[](2); - destinationChainSelectors[0] = DEST_CHAIN_SELECTOR; - destinationChainSelectors[1] = uint64(99999); - - address[] memory addedAllowlistedSenders = new address[](4); - addedAllowlistedSenders[0] = vm.addr(1); - addedAllowlistedSenders[1] = vm.addr(2); - addedAllowlistedSenders[2] = vm.addr(3); - addedAllowlistedSenders[3] = vm.addr(4); - - OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ - allowlistEnabled: true, - destChainSelector: DEST_CHAIN_SELECTOR, - addedAllowlistedSenders: addedAllowlistedSenders, - removedAllowlistedSenders: new address[](0) - }); - - OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); - applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; - - vm.startPrank(STRANGER); - vm.expectRevert(OnRamp.OnlyCallableByOwnerOrAllowlistAdmin.selector); - s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); - vm.stopPrank(); - - applyAllowlistConfigArgsItems[0].addedAllowlistedSenders[0] = address(0); - vm.expectRevert(abi.encodeWithSelector(OnRamp.InvalidAllowListRequest.selector, DEST_CHAIN_SELECTOR)); - vm.startPrank(OWNER); - s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); - vm.stopPrank(); - } - - function test_applyAllowlistUpdates_InvalidAllowListRequestDisabledAllowListWithAdds() public { - address[] memory addedAllowlistedSenders = new address[](1); - addedAllowlistedSenders[0] = vm.addr(1); - - OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ - allowlistEnabled: false, - destChainSelector: DEST_CHAIN_SELECTOR, - addedAllowlistedSenders: addedAllowlistedSenders, - removedAllowlistedSenders: new address[](0) - }); - OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); - applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; - - vm.expectRevert(abi.encodeWithSelector(OnRamp.InvalidAllowListRequest.selector, DEST_CHAIN_SELECTOR)); - s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); - } -} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.applyDestChainConfigUpdates.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.applyDestChainConfigUpdates.t.sol new file mode 100644 index 00000000000..2b99fd423be --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.applyDestChainConfigUpdates.t.sol @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IRouter} from "../../../interfaces/IRouter.sol"; + +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +contract OnRamp_applyDestChainConfigUpdates is OnRampSetup { + function test_ApplyDestChainConfigUpdates_Success() external { + OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](1); + configArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + + // supports disabling a lane by setting a router to zero + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, IRouter(address(0)), false); + + s_onRamp.applyDestChainConfigUpdates(configArgs); + + (,, address router) = s_onRamp.getDestChainConfig(DEST_CHAIN_SELECTOR); + assertEq(address(0), router); + + // supports updating and adding lanes simultaneously + configArgs = new OnRamp.DestChainConfigArgs[](2); + configArgs[0] = OnRamp.DestChainConfigArgs({ + destChainSelector: DEST_CHAIN_SELECTOR, + router: s_sourceRouter, + allowlistEnabled: false + }); + uint64 newDestChainSelector = 99999; + address newRouter = makeAddr("newRouter"); + + configArgs[1] = OnRamp.DestChainConfigArgs({ + destChainSelector: newDestChainSelector, + router: IRouter(newRouter), + allowlistEnabled: false + }); + + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(newDestChainSelector, 0, IRouter(newRouter), false); + + s_onRamp.applyDestChainConfigUpdates(configArgs); + + (,, address newGotRouter) = s_onRamp.getDestChainConfig(newDestChainSelector); + assertEq(newRouter, newGotRouter); + + // handles empty list + uint256 numLogs = vm.getRecordedLogs().length; + configArgs = new OnRamp.DestChainConfigArgs[](0); + s_onRamp.applyDestChainConfigUpdates(configArgs); + assertEq(numLogs, vm.getRecordedLogs().length); // indicates no changes made + } + + function test_ApplyDestChainConfigUpdates_WithInvalidChainSelector_Revert() external { + OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](1); + configArgs[0].destChainSelector = 0; // invalid + vm.expectRevert(abi.encodeWithSelector(OnRamp.InvalidDestChainConfig.selector, 0)); + s_onRamp.applyDestChainConfigUpdates(configArgs); + } +} + +contract OnRamp_applyAllowlistUpdates is OnRampSetup { + function test_applyAllowlistUpdates_Success() public { + OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](2); + configArgs[0] = OnRamp.DestChainConfigArgs({ + destChainSelector: DEST_CHAIN_SELECTOR, + router: s_sourceRouter, + allowlistEnabled: false + }); + configArgs[1] = + OnRamp.DestChainConfigArgs({destChainSelector: 9999, router: IRouter(address(9999)), allowlistEnabled: false}); + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(9999, 0, IRouter(address(9999)), false); + s_onRamp.applyDestChainConfigUpdates(configArgs); + + (uint64 sequenceNumber, bool allowlistEnabled, address router) = s_onRamp.getDestChainConfig(9999); + assertEq(sequenceNumber, 0); + assertEq(allowlistEnabled, false); + assertEq(router, address(9999)); + + uint64[] memory destinationChainSelectors = new uint64[](2); + destinationChainSelectors[0] = DEST_CHAIN_SELECTOR; + destinationChainSelectors[1] = uint64(99999); + + address[] memory addedAllowlistedSenders = new address[](4); + addedAllowlistedSenders[0] = vm.addr(1); + addedAllowlistedSenders[1] = vm.addr(2); + addedAllowlistedSenders[2] = vm.addr(3); + addedAllowlistedSenders[3] = vm.addr(4); + + vm.expectEmit(); + emit OnRamp.AllowListSendersAdded(DEST_CHAIN_SELECTOR, addedAllowlistedSenders); + + OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ + allowlistEnabled: true, + destChainSelector: DEST_CHAIN_SELECTOR, + addedAllowlistedSenders: addedAllowlistedSenders, + removedAllowlistedSenders: new address[](0) + }); + + OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); + applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; + + s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); + + (bool isActive, address[] memory gotAllowList) = s_onRamp.getAllowedSendersList(DEST_CHAIN_SELECTOR); + assertEq(4, gotAllowList.length); + assertEq(addedAllowlistedSenders, gotAllowList); + assertEq(true, isActive); + + address[] memory removedAllowlistedSenders = new address[](1); + removedAllowlistedSenders[0] = vm.addr(2); + + vm.expectEmit(); + emit OnRamp.AllowListSendersRemoved(DEST_CHAIN_SELECTOR, removedAllowlistedSenders); + + allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ + allowlistEnabled: false, + destChainSelector: DEST_CHAIN_SELECTOR, + addedAllowlistedSenders: new address[](0), + removedAllowlistedSenders: removedAllowlistedSenders + }); + + OnRamp.AllowlistConfigArgs[] memory allowlistConfigArgsItems_2 = new OnRamp.AllowlistConfigArgs[](1); + allowlistConfigArgsItems_2[0] = allowlistConfigArgs; + + s_onRamp.applyAllowlistUpdates(allowlistConfigArgsItems_2); + (isActive, gotAllowList) = s_onRamp.getAllowedSendersList(DEST_CHAIN_SELECTOR); + assertEq(3, gotAllowList.length); + assertFalse(isActive); + + addedAllowlistedSenders = new address[](2); + addedAllowlistedSenders[0] = vm.addr(5); + addedAllowlistedSenders[1] = vm.addr(6); + + removedAllowlistedSenders = new address[](2); + removedAllowlistedSenders[0] = vm.addr(1); + removedAllowlistedSenders[1] = vm.addr(3); + + vm.expectEmit(); + emit OnRamp.AllowListSendersAdded(DEST_CHAIN_SELECTOR, addedAllowlistedSenders); + emit OnRamp.AllowListSendersRemoved(DEST_CHAIN_SELECTOR, removedAllowlistedSenders); + + allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ + allowlistEnabled: true, + destChainSelector: DEST_CHAIN_SELECTOR, + addedAllowlistedSenders: addedAllowlistedSenders, + removedAllowlistedSenders: removedAllowlistedSenders + }); + + OnRamp.AllowlistConfigArgs[] memory allowlistConfigArgsItems_3 = new OnRamp.AllowlistConfigArgs[](1); + allowlistConfigArgsItems_3[0] = allowlistConfigArgs; + + s_onRamp.applyAllowlistUpdates(allowlistConfigArgsItems_3); + (isActive, gotAllowList) = s_onRamp.getAllowedSendersList(DEST_CHAIN_SELECTOR); + + assertEq(3, gotAllowList.length); + assertTrue(isActive); + } + + function test_applyAllowlistUpdates_Revert() public { + OnRamp.DestChainConfigArgs[] memory configArgs = new OnRamp.DestChainConfigArgs[](2); + configArgs[0] = OnRamp.DestChainConfigArgs({ + destChainSelector: DEST_CHAIN_SELECTOR, + router: s_sourceRouter, + allowlistEnabled: false + }); + configArgs[1] = + OnRamp.DestChainConfigArgs({destChainSelector: 9999, router: IRouter(address(9999)), allowlistEnabled: false}); + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(9999, 0, IRouter(address(9999)), false); + s_onRamp.applyDestChainConfigUpdates(configArgs); + + uint64[] memory destinationChainSelectors = new uint64[](2); + destinationChainSelectors[0] = DEST_CHAIN_SELECTOR; + destinationChainSelectors[1] = uint64(99999); + + address[] memory addedAllowlistedSenders = new address[](4); + addedAllowlistedSenders[0] = vm.addr(1); + addedAllowlistedSenders[1] = vm.addr(2); + addedAllowlistedSenders[2] = vm.addr(3); + addedAllowlistedSenders[3] = vm.addr(4); + + OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ + allowlistEnabled: true, + destChainSelector: DEST_CHAIN_SELECTOR, + addedAllowlistedSenders: addedAllowlistedSenders, + removedAllowlistedSenders: new address[](0) + }); + + OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); + applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; + + vm.startPrank(STRANGER); + vm.expectRevert(OnRamp.OnlyCallableByOwnerOrAllowlistAdmin.selector); + s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); + vm.stopPrank(); + + applyAllowlistConfigArgsItems[0].addedAllowlistedSenders[0] = address(0); + vm.expectRevert(abi.encodeWithSelector(OnRamp.InvalidAllowListRequest.selector, DEST_CHAIN_SELECTOR)); + vm.startPrank(OWNER); + s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); + vm.stopPrank(); + } + + function test_applyAllowlistUpdates_InvalidAllowListRequestDisabledAllowListWithAdds() public { + address[] memory addedAllowlistedSenders = new address[](1); + addedAllowlistedSenders[0] = vm.addr(1); + + OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ + allowlistEnabled: false, + destChainSelector: DEST_CHAIN_SELECTOR, + addedAllowlistedSenders: addedAllowlistedSenders, + removedAllowlistedSenders: new address[](0) + }); + OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); + applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; + + vm.expectRevert(abi.encodeWithSelector(OnRamp.InvalidAllowListRequest.selector, DEST_CHAIN_SELECTOR)); + s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.constructor.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.constructor.t.sol new file mode 100644 index 00000000000..1e31a2a1377 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.constructor.t.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IRMNRemote} from "../../../interfaces/IRMNRemote.sol"; +import {IRouter} from "../../../interfaces/IRouter.sol"; + +import {Client} from "../../../libraries/Client.sol"; +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {OnRampHelper} from "../../helpers/OnRampHelper.sol"; +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +contract OnRamp_constructor is OnRampSetup { + function test_Constructor_Success() public { + OnRamp.StaticConfig memory staticConfig = OnRamp.StaticConfig({ + chainSelector: SOURCE_CHAIN_SELECTOR, + rmnRemote: s_mockRMNRemote, + nonceManager: address(s_outboundNonceManager), + tokenAdminRegistry: address(s_tokenAdminRegistry) + }); + OnRamp.DynamicConfig memory dynamicConfig = _generateDynamicOnRampConfig(address(s_feeQuoter)); + + vm.expectEmit(); + emit OnRamp.ConfigSet(staticConfig, dynamicConfig); + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, false); + + _deployOnRamp(SOURCE_CHAIN_SELECTOR, s_sourceRouter, address(s_outboundNonceManager), address(s_tokenAdminRegistry)); + + OnRamp.StaticConfig memory gotStaticConfig = s_onRamp.getStaticConfig(); + + assertEq(staticConfig.chainSelector, gotStaticConfig.chainSelector); + assertEq(address(staticConfig.rmnRemote), address(gotStaticConfig.rmnRemote)); + assertEq(staticConfig.tokenAdminRegistry, gotStaticConfig.tokenAdminRegistry); + + OnRamp.DynamicConfig memory gotDynamicConfig = s_onRamp.getDynamicConfig(); + assertEq(dynamicConfig.feeQuoter, gotDynamicConfig.feeQuoter); + + // Initial values + assertEq("OnRamp 1.6.0-dev", s_onRamp.typeAndVersion()); + assertEq(OWNER, s_onRamp.owner()); + assertEq(1, s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR)); + } + + function test_Constructor_EnableAllowList_ForwardFromRouter_Reverts() public { + OnRamp.StaticConfig memory staticConfig = OnRamp.StaticConfig({ + chainSelector: SOURCE_CHAIN_SELECTOR, + rmnRemote: s_mockRMNRemote, + nonceManager: address(s_outboundNonceManager), + tokenAdminRegistry: address(s_tokenAdminRegistry) + }); + + OnRamp.DynamicConfig memory dynamicConfig = _generateDynamicOnRampConfig(address(s_feeQuoter)); + + // Creating a DestChainConfig and setting allowlistEnabled : true + OnRamp.DestChainConfigArgs[] memory destChainConfigs = new OnRamp.DestChainConfigArgs[](1); + destChainConfigs[0] = OnRamp.DestChainConfigArgs({ + destChainSelector: DEST_CHAIN_SELECTOR, + router: s_sourceRouter, + allowlistEnabled: true + }); + + vm.expectEmit(); + emit OnRamp.ConfigSet(staticConfig, dynamicConfig); + + vm.expectEmit(); + emit OnRamp.DestChainConfigSet(DEST_CHAIN_SELECTOR, 0, s_sourceRouter, true); + + OnRampHelper tempOnRamp = new OnRampHelper(staticConfig, dynamicConfig, destChainConfigs); + + // Sending a message and expecting revert as allowlist is enabled with no address in allowlist + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + vm.startPrank(address(s_sourceRouter)); + vm.expectRevert(abi.encodeWithSelector(OnRamp.SenderNotAllowed.selector, OWNER)); + tempOnRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + } + + function test_Constructor_InvalidConfigChainSelectorEqZero_Revert() public { + vm.expectRevert(OnRamp.InvalidConfig.selector); + new OnRampHelper( + OnRamp.StaticConfig({ + chainSelector: 0, + rmnRemote: s_mockRMNRemote, + nonceManager: address(s_outboundNonceManager), + tokenAdminRegistry: address(s_tokenAdminRegistry) + }), + _generateDynamicOnRampConfig(address(s_feeQuoter)), + _generateDestChainConfigArgs(IRouter(address(0))) + ); + } + + function test_Constructor_InvalidConfigRMNProxyEqAddressZero_Revert() public { + vm.expectRevert(OnRamp.InvalidConfig.selector); + s_onRamp = new OnRampHelper( + OnRamp.StaticConfig({ + chainSelector: SOURCE_CHAIN_SELECTOR, + rmnRemote: IRMNRemote(address(0)), + nonceManager: address(s_outboundNonceManager), + tokenAdminRegistry: address(s_tokenAdminRegistry) + }), + _generateDynamicOnRampConfig(address(s_feeQuoter)), + _generateDestChainConfigArgs(IRouter(address(0))) + ); + } + + function test_Constructor_InvalidConfigNonceManagerEqAddressZero_Revert() public { + vm.expectRevert(OnRamp.InvalidConfig.selector); + new OnRampHelper( + OnRamp.StaticConfig({ + chainSelector: SOURCE_CHAIN_SELECTOR, + rmnRemote: s_mockRMNRemote, + nonceManager: address(0), + tokenAdminRegistry: address(s_tokenAdminRegistry) + }), + _generateDynamicOnRampConfig(address(s_feeQuoter)), + _generateDestChainConfigArgs(IRouter(address(0))) + ); + } + + function test_Constructor_InvalidConfigTokenAdminRegistryEqAddressZero_Revert() public { + vm.expectRevert(OnRamp.InvalidConfig.selector); + new OnRampHelper( + OnRamp.StaticConfig({ + chainSelector: SOURCE_CHAIN_SELECTOR, + rmnRemote: s_mockRMNRemote, + nonceManager: address(s_outboundNonceManager), + tokenAdminRegistry: address(0) + }), + _generateDynamicOnRampConfig(address(s_feeQuoter)), + _generateDestChainConfigArgs(IRouter(address(0))) + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.forwardFromRouter.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.forwardFromRouter.t.sol new file mode 100644 index 00000000000..076377c34c5 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.forwardFromRouter.t.sol @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IMessageInterceptor} from "../../../interfaces/IMessageInterceptor.sol"; +import {IRouter} from "../../../interfaces/IRouter.sol"; + +import {BurnMintERC677} from "../../../../shared/token/ERC677/BurnMintERC677.sol"; +import {FeeQuoter} from "../../../FeeQuoter.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {Pool} from "../../../libraries/Pool.sol"; +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {MaybeRevertingBurnMintTokenPool} from "../../helpers/MaybeRevertingBurnMintTokenPool.sol"; +import {MessageInterceptorHelper} from "../../helpers/MessageInterceptorHelper.sol"; +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +contract OnRamp_forwardFromRouter is OnRampSetup { + struct LegacyExtraArgs { + uint256 gasLimit; + bool strict; + } + + MessageInterceptorHelper internal s_outboundMessageInterceptor; + + address internal s_destTokenPool = makeAddr("destTokenPool"); + address internal s_destToken = makeAddr("destToken"); + + function setUp() public virtual override { + super.setUp(); + s_outboundMessageInterceptor = new MessageInterceptorHelper(); + + address[] memory feeTokens = new address[](1); + feeTokens[0] = s_sourceTokens[1]; + s_feeQuoter.applyFeeTokensUpdates(feeTokens, new address[](0)); + + uint64[] memory destinationChainSelectors = new uint64[](1); + destinationChainSelectors[0] = DEST_CHAIN_SELECTOR; + address[] memory addAllowedList = new address[](1); + addAllowedList[0] = OWNER; + OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ + allowlistEnabled: true, + destChainSelector: DEST_CHAIN_SELECTOR, + addedAllowlistedSenders: addAllowedList, + removedAllowlistedSenders: new address[](0) + }); + OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); + applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; + s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); + + // Since we'll mostly be testing for valid calls from the router we'll + // mock all calls to be originating from the router and re-mock in + // tests that require failure. + vm.startPrank(address(s_sourceRouter)); + } + + function test_ForwardFromRouterSuccessCustomExtraArgs() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_ForwardFromRouter_Success_ConfigurableSourceRouter() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + // Change the source router for this lane + IRouter newRouter = IRouter(makeAddr("NEW ROUTER")); + vm.stopPrank(); + vm.prank(OWNER); + s_onRamp.applyDestChainConfigUpdates(_generateDestChainConfigArgs(newRouter)); + + // forward fails from wrong router + vm.prank(address(s_sourceRouter)); + vm.expectRevert(OnRamp.MustBeCalledByRouter.selector); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + + // forward succeeds from correct router + vm.prank(address(newRouter)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_ForwardFromRouterSuccessLegacyExtraArgs() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = + abi.encodeWithSelector(Client.EVM_EXTRA_ARGS_V1_TAG, LegacyExtraArgs({gasLimit: GAS_LIMIT * 2, strict: true})); + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + vm.expectEmit(); + // We expect the message to be emitted with strict = false. + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_ForwardFromRouterSuccessEmptyExtraArgs() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = ""; + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + vm.expectEmit(); + // We expect the message to be emitted with strict = false. + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_ForwardFromRouter_Success() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_ForwardFromRouterExtraArgsV2_Success() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = abi.encodeWithSelector( + Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: false}) + ); + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_ForwardFromRouterExtraArgsV2AllowOutOfOrderTrue_Success() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = abi.encodeWithSelector( + Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: true}) + ); + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_ShouldIncrementSeqNumAndNonce_Success() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + for (uint64 i = 1; i < 4; ++i) { + uint64 nonceBefore = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); + uint64 sequenceNumberBefore = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, i, _messageToEvent(message, i, i, 0, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + + uint64 nonceAfter = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); + uint64 sequenceNumberAfter = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; + assertEq(nonceAfter, nonceBefore + 1); + assertEq(sequenceNumberAfter, sequenceNumberBefore + 1); + } + } + + function test_ShouldIncrementNonceOnlyOnOrdered_Success() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = abi.encodeWithSelector( + Client.EVM_EXTRA_ARGS_V2_TAG, Client.EVMExtraArgsV2({gasLimit: GAS_LIMIT * 2, allowOutOfOrderExecution: true}) + ); + + for (uint64 i = 1; i < 4; ++i) { + uint64 nonceBefore = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); + uint64 sequenceNumberBefore = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, i, _messageToEvent(message, i, i, 0, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + + uint64 nonceAfter = s_outboundNonceManager.getOutboundNonce(DEST_CHAIN_SELECTOR, OWNER); + uint64 sequenceNumberAfter = s_onRamp.getExpectedNextSequenceNumber(DEST_CHAIN_SELECTOR) - 1; + assertEq(nonceAfter, nonceBefore); + assertEq(sequenceNumberAfter, sequenceNumberBefore + 1); + } + } + + function test_ShouldStoreLinkFees() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + uint256 feeAmount = 1234567890; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + + assertEq(IERC20(s_sourceFeeToken).balanceOf(address(s_onRamp)), feeAmount); + } + + function test_ShouldStoreNonLinkFees() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = s_sourceTokens[1]; + + uint256 feeAmount = 1234567890; + IERC20(s_sourceTokens[1]).transferFrom(OWNER, address(s_onRamp), feeAmount); + + // Calculate conversion done by prices contract + uint256 feeTokenPrice = s_feeQuoter.getTokenPrice(s_sourceTokens[1]).value; + uint256 linkTokenPrice = s_feeQuoter.getTokenPrice(s_sourceFeeToken).value; + uint256 conversionRate = (feeTokenPrice * 1e18) / linkTokenPrice; + uint256 expectedJuels = (feeAmount * conversionRate) / 1e18; + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, expectedJuels, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + + assertEq(IERC20(s_sourceTokens[1]).balanceOf(address(s_onRamp)), feeAmount); + } + + // Make sure any valid sender, receiver and feeAmount can be handled. + // @TODO Temporarily setting lower fuzz run as 256 triggers snapshot gas off by 1 error. + // https://github.com/foundry-rs/foundry/issues/5689 + /// forge-dynamicConfig: default.fuzz.runs = 32 + /// forge-dynamicConfig: ccip.fuzz.runs = 32 + function test_Fuzz_ForwardFromRouter_Success(address originalSender, address receiver, uint96 feeTokenAmount) public { + // To avoid RouterMustSetOriginalSender + vm.assume(originalSender != address(0)); + vm.assume(uint160(receiver) >= Internal.PRECOMPILE_SPACE); + feeTokenAmount = uint96(bound(feeTokenAmount, 0, MAX_MSG_FEES_JUELS)); + vm.stopPrank(); + + vm.startPrank(OWNER); + uint64[] memory destinationChainSelectors = new uint64[](1); + destinationChainSelectors[0] = uint64(DEST_CHAIN_SELECTOR); + address[] memory addAllowedList = new address[](1); + addAllowedList[0] = originalSender; + OnRamp.AllowlistConfigArgs memory allowlistConfigArgs = OnRamp.AllowlistConfigArgs({ + allowlistEnabled: true, + destChainSelector: DEST_CHAIN_SELECTOR, + addedAllowlistedSenders: addAllowedList, + removedAllowlistedSenders: new address[](0) + }); + OnRamp.AllowlistConfigArgs[] memory applyAllowlistConfigArgsItems = new OnRamp.AllowlistConfigArgs[](1); + applyAllowlistConfigArgsItems[0] = allowlistConfigArgs; + s_onRamp.applyAllowlistUpdates(applyAllowlistConfigArgsItems); + vm.stopPrank(); + + vm.startPrank(address(s_sourceRouter)); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.receiver = abi.encode(receiver); + + // Make sure the tokens are in the contract + deal(s_sourceFeeToken, address(s_onRamp), feeTokenAmount); + + Internal.EVM2AnyRampMessage memory expectedEvent = _messageToEvent(message, 1, 1, feeTokenAmount, originalSender); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, expectedEvent.header.sequenceNumber, expectedEvent); + + // Assert the message Id is correct + assertEq( + expectedEvent.header.messageId, + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeTokenAmount, originalSender) + ); + } + + function test_forwardFromRouter_WithInterception_Success() public { + _enableOutboundMessageInterceptor(); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); + uint256 feeAmount = 1234567890; + message.tokenAmounts = new Client.EVMTokenAmount[](1); + message.tokenAmounts[0].amount = 1e18; + message.tokenAmounts[0].token = s_sourceTokens[0]; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + s_outboundMessageInterceptor.setMessageIdValidationState(keccak256(abi.encode(message)), false); + + vm.expectEmit(); + emit OnRamp.CCIPMessageSent(DEST_CHAIN_SELECTOR, 1, _messageToEvent(message, 1, 1, feeAmount, OWNER)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + // Reverts + + function test_Paused_Revert() public { + // We pause by disabling the whitelist + vm.stopPrank(); + vm.startPrank(OWNER); + s_onRamp.setDynamicConfig(_generateDynamicOnRampConfig(address(2))); + vm.expectRevert(OnRamp.MustBeCalledByRouter.selector); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, OWNER); + } + + function test_InvalidExtraArgsTag_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = bytes("bad args"); + + vm.expectRevert(FeeQuoter.InvalidExtraArgsTag.selector); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + } + + function test_Permissions_Revert() public { + vm.stopPrank(); + vm.startPrank(OWNER); + vm.expectRevert(OnRamp.MustBeCalledByRouter.selector); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, OWNER); + } + + function test_OriginalSender_Revert() public { + vm.expectRevert(OnRamp.RouterMustSetOriginalSender.selector); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, address(0)); + } + + function test_UnAllowedOriginalSender_Revert() public { + vm.stopPrank(); + vm.startPrank(STRANGER); + vm.expectRevert(abi.encodeWithSelector(OnRamp.SenderNotAllowed.selector, STRANGER)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, _generateEmptyMessage(), 0, STRANGER); + } + + function test_MessageInterceptionError_Revert() public { + _enableOutboundMessageInterceptor(); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.extraArgs = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT * 2})); + uint256 feeAmount = 1234567890; + message.tokenAmounts = new Client.EVMTokenAmount[](1); + message.tokenAmounts[0].amount = 1e18; + message.tokenAmounts[0].token = s_sourceTokens[0]; + IERC20(s_sourceFeeToken).transferFrom(OWNER, address(s_onRamp), feeAmount); + s_outboundMessageInterceptor.setMessageIdValidationState(keccak256(abi.encode(message)), true); + + vm.expectRevert( + abi.encodeWithSelector(IMessageInterceptor.MessageValidationError.selector, bytes("Invalid message")) + ); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + + function test_MultiCannotSendZeroTokens_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.tokenAmounts = new Client.EVMTokenAmount[](1); + message.tokenAmounts[0].amount = 0; + message.tokenAmounts[0].token = s_sourceTokens[0]; + vm.expectRevert(OnRamp.CannotSendZeroTokens.selector); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + } + + function test_UnsupportedToken_Revert() public { + address wrongToken = address(1); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.tokenAmounts = new Client.EVMTokenAmount[](1); + message.tokenAmounts[0].token = wrongToken; + message.tokenAmounts[0].amount = 1; + + // We need to set the price of this new token to be able to reach + // the proper revert point. This must be called by the owner. + vm.stopPrank(); + vm.startPrank(OWNER); + + Internal.PriceUpdates memory priceUpdates = _getSingleTokenPriceUpdateStruct(wrongToken, 1); + s_feeQuoter.updatePrices(priceUpdates); + + // Change back to the router + vm.startPrank(address(s_sourceRouter)); + vm.expectRevert(abi.encodeWithSelector(OnRamp.UnsupportedToken.selector, wrongToken)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + } + + function test_forwardFromRouter_UnsupportedToken_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.tokenAmounts = new Client.EVMTokenAmount[](1); + message.tokenAmounts[0].amount = 1; + message.tokenAmounts[0].token = address(1); + + vm.expectRevert(abi.encodeWithSelector(OnRamp.UnsupportedToken.selector, message.tokenAmounts[0].token)); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + } + + function test_MesssageFeeTooHigh_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + vm.expectRevert( + abi.encodeWithSelector(FeeQuoter.MessageFeeTooHigh.selector, MAX_MSG_FEES_JUELS + 1, MAX_MSG_FEES_JUELS) + ); + + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, MAX_MSG_FEES_JUELS + 1, OWNER); + } + + function test_SourceTokenDataTooLarge_Revert() public { + address sourceETH = s_sourceTokens[1]; + vm.stopPrank(); + vm.startPrank(OWNER); + + MaybeRevertingBurnMintTokenPool newPool = new MaybeRevertingBurnMintTokenPool( + BurnMintERC677(sourceETH), new address[](0), address(s_mockRMNRemote), address(s_sourceRouter) + ); + BurnMintERC677(sourceETH).grantMintAndBurnRoles(address(newPool)); + deal(address(sourceETH), address(newPool), type(uint256).max); + + // Add TokenPool to OnRamp + s_tokenAdminRegistry.setPool(sourceETH, address(newPool)); + + // Allow chain in TokenPool + TokenPool.ChainUpdate[] memory chainUpdates = new TokenPool.ChainUpdate[](1); + chainUpdates[0] = TokenPool.ChainUpdate({ + remoteChainSelector: DEST_CHAIN_SELECTOR, + remotePoolAddress: abi.encode(s_destTokenPool), + remoteTokenAddress: abi.encode(s_destToken), + allowed: true, + outboundRateLimiterConfig: _getOutboundRateLimiterConfig(), + inboundRateLimiterConfig: _getInboundRateLimiterConfig() + }); + newPool.applyChainUpdates(chainUpdates); + + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(address(sourceETH), 1000); + + // No data set, should succeed + vm.startPrank(address(s_sourceRouter)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + + // Set max data length, should succeed + vm.startPrank(OWNER); + newPool.setSourceTokenData(new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES)); + + vm.startPrank(address(s_sourceRouter)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + + // Set data to max length +1, should revert + vm.startPrank(OWNER); + newPool.setSourceTokenData(new bytes(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES + 1)); + + vm.startPrank(address(s_sourceRouter)); + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + + // Set token config to allow larger data + vm.startPrank(OWNER); + FeeQuoter.TokenTransferFeeConfigArgs[] memory tokenTransferFeeConfigArgs = _generateTokenTransferFeeConfigArgs(1, 1); + tokenTransferFeeConfigArgs[0].destChainSelector = DEST_CHAIN_SELECTOR; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].token = sourceETH; + tokenTransferFeeConfigArgs[0].tokenTransferFeeConfigs[0].tokenTransferFeeConfig = FeeQuoter.TokenTransferFeeConfig({ + minFeeUSDCents: 0, + maxFeeUSDCents: 1, + deciBps: 0, + destGasOverhead: 0, + destBytesOverhead: uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32, + isEnabled: true + }); + s_feeQuoter.applyTokenTransferFeeConfigUpdates( + tokenTransferFeeConfigArgs, new FeeQuoter.TokenTransferFeeConfigRemoveArgs[](0) + ); + + vm.startPrank(address(s_sourceRouter)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + + // Set the token data larger than the configured token data, should revert + vm.startPrank(OWNER); + newPool.setSourceTokenData(new bytes(uint32(Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES) + 32 + 1)); + + vm.startPrank(address(s_sourceRouter)); + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.SourceTokenDataTooLarge.selector, sourceETH)); + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, 0, OWNER); + } + + function _enableOutboundMessageInterceptor() internal { + (, address msgSender,) = vm.readCallers(); + + bool resetPrank = false; + + if (msgSender != OWNER) { + vm.stopPrank(); + vm.startPrank(OWNER); + resetPrank = true; + } + + OnRamp.DynamicConfig memory dynamicConfig = s_onRamp.getDynamicConfig(); + dynamicConfig.messageInterceptor = address(s_outboundMessageInterceptor); + s_onRamp.setDynamicConfig(dynamicConfig); + + if (resetPrank) { + vm.stopPrank(); + vm.startPrank(msgSender); + } + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getFee.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getFee.t.sol new file mode 100644 index 00000000000..63a4c0c322e --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getFee.t.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {FeeQuoter} from "../../../FeeQuoter.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {USDPriceWith18Decimals} from "../../../libraries/USDPriceWith18Decimals.sol"; +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +contract OnRamp_getFee is OnRampSetup { + using USDPriceWith18Decimals for uint224; + + function test_EmptyMessage_Success() public view { + address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; + uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; + + for (uint256 i = 0; i < feeTokenPrices.length; ++i) { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = testTokens[i]; + + uint256 feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); + uint256 expectedFeeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + + assertEq(expectedFeeAmount, feeAmount); + } + } + + function test_SingleTokenMessage_Success() public view { + address[2] memory testTokens = [s_sourceFeeToken, s_sourceRouter.getWrappedNative()]; + uint224[2] memory feeTokenPrices = [s_feeTokenPrice, s_wrappedTokenPrice]; + + uint256 tokenAmount = 10000e18; + for (uint256 i = 0; i < feeTokenPrices.length; ++i) { + Client.EVM2AnyMessage memory message = _generateSingleTokenMessage(s_sourceFeeToken, tokenAmount); + message.feeToken = testTokens[i]; + + uint256 feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); + uint256 expectedFeeAmount = s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); + + assertEq(expectedFeeAmount, feeAmount); + } + } + + function test_GetFeeOfZeroForTokenMessage_Success() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + + uint256 feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); + assertTrue(feeAmount > 0); + + FeeQuoter.PremiumMultiplierWeiPerEthArgs[] memory tokenMults = new FeeQuoter.PremiumMultiplierWeiPerEthArgs[](1); + tokenMults[0] = FeeQuoter.PremiumMultiplierWeiPerEthArgs({token: message.feeToken, premiumMultiplierWeiPerEth: 0}); + s_feeQuoter.applyPremiumMultiplierWeiPerEthUpdates(tokenMults); + + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + destChainConfigArgs[0].destChainConfig.destDataAvailabilityMultiplierBps = 0; + destChainConfigArgs[0].destChainConfig.gasMultiplierWeiPerEth = 0; + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + + feeAmount = s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); + + assertEq(0, feeAmount); + } + + // Reverts + + function test_Unhealthy_Revert() public { + _setMockRMNChainCurse(DEST_CHAIN_SELECTOR, true); + vm.expectRevert(abi.encodeWithSelector(OnRamp.CursedByRMN.selector, DEST_CHAIN_SELECTOR)); + s_onRamp.getFee(DEST_CHAIN_SELECTOR, _generateEmptyMessage()); + } + + function test_EnforceOutOfOrder_Revert() public { + // Update dynamic config to enforce allowOutOfOrderExecution = true. + vm.stopPrank(); + vm.startPrank(OWNER); + + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + destChainConfigArgs[0].destChainConfig.enforceOutOfOrder = true; + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + vm.stopPrank(); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + // Empty extraArgs to should revert since it enforceOutOfOrder is true. + message.extraArgs = ""; + + vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); + s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); + } + + function test_NotAFeeTokenButPricedToken_Revert() public { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = s_sourceTokens[1]; + + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.FeeTokenNotSupported.selector, message.feeToken)); + + s_onRamp.getFee(DEST_CHAIN_SELECTOR, message); + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getSupportedTokens.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getSupportedTokens.t.sol new file mode 100644 index 00000000000..c04f3cf3d51 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getSupportedTokens.t.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +contract OnRamp_getSupportedTokens is OnRampSetup { + function test_GetSupportedTokens_Revert() public { + vm.expectRevert(OnRamp.GetSupportedTokensFunctionalityRemovedCheckAdminRegistry.selector); + s_onRamp.getSupportedTokens(DEST_CHAIN_SELECTOR); + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getTokenPool.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getTokenPool.t.sol new file mode 100644 index 00000000000..8612ce86e36 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.getTokenPool.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +contract OnRamp_getTokenPool is OnRampSetup { + function test_GetTokenPool_Success() public view { + assertEq( + s_sourcePoolByToken[s_sourceTokens[0]], + address(s_onRamp.getPoolBySourceToken(DEST_CHAIN_SELECTOR, IERC20(s_sourceTokens[0]))) + ); + assertEq( + s_sourcePoolByToken[s_sourceTokens[1]], + address(s_onRamp.getPoolBySourceToken(DEST_CHAIN_SELECTOR, IERC20(s_sourceTokens[1]))) + ); + + address wrongToken = address(123); + address nonExistentPool = address(s_onRamp.getPoolBySourceToken(DEST_CHAIN_SELECTOR, IERC20(wrongToken))); + + assertEq(address(0), nonExistentPool); + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.setDynamicConfig.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.setDynamicConfig.t.sol new file mode 100644 index 00000000000..057ed0a79dd --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.setDynamicConfig.t.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +contract OnRamp_setDynamicConfig is OnRampSetup { + function test_setDynamicConfig_Success() public { + OnRamp.StaticConfig memory staticConfig = s_onRamp.getStaticConfig(); + OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ + feeQuoter: address(23423), + reentrancyGuardEntered: false, + messageInterceptor: makeAddr("messageInterceptor"), + feeAggregator: FEE_AGGREGATOR, + allowlistAdmin: address(0) + }); + + vm.expectEmit(); + emit OnRamp.ConfigSet(staticConfig, newConfig); + + s_onRamp.setDynamicConfig(newConfig); + + OnRamp.DynamicConfig memory gotDynamicConfig = s_onRamp.getDynamicConfig(); + assertEq(newConfig.feeQuoter, gotDynamicConfig.feeQuoter); + } + + // Reverts + + function test_setDynamicConfig_InvalidConfigFeeQuoterEqAddressZero_Revert() public { + OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ + feeQuoter: address(0), + reentrancyGuardEntered: false, + feeAggregator: FEE_AGGREGATOR, + messageInterceptor: makeAddr("messageInterceptor"), + allowlistAdmin: address(0) + }); + + vm.expectRevert(OnRamp.InvalidConfig.selector); + s_onRamp.setDynamicConfig(newConfig); + } + + function test_setDynamicConfig_InvalidConfigInvalidConfig_Revert() public { + OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ + feeQuoter: address(23423), + reentrancyGuardEntered: false, + messageInterceptor: address(0), + feeAggregator: FEE_AGGREGATOR, + allowlistAdmin: address(0) + }); + + // Invalid price reg reverts. + newConfig.feeQuoter = address(0); + vm.expectRevert(OnRamp.InvalidConfig.selector); + s_onRamp.setDynamicConfig(newConfig); + } + + function test_setDynamicConfig_InvalidConfigFeeAggregatorEqAddressZero_Revert() public { + OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ + feeQuoter: address(23423), + reentrancyGuardEntered: false, + messageInterceptor: address(0), + feeAggregator: address(0), + allowlistAdmin: address(0) + }); + + vm.expectRevert(OnRamp.InvalidConfig.selector); + s_onRamp.setDynamicConfig(newConfig); + } + + function test_setDynamicConfig_InvalidConfigOnlyOwner_Revert() public { + vm.startPrank(STRANGER); + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + s_onRamp.setDynamicConfig(_generateDynamicOnRampConfig(address(2))); + } + + function test_setDynamicConfig_InvalidConfigReentrancyGuardEnteredEqTrue_Revert() public { + OnRamp.DynamicConfig memory newConfig = OnRamp.DynamicConfig({ + feeQuoter: address(23423), + reentrancyGuardEntered: true, + messageInterceptor: makeAddr("messageInterceptor"), + feeAggregator: FEE_AGGREGATOR, + allowlistAdmin: address(0) + }); + + vm.expectRevert(OnRamp.InvalidConfig.selector); + s_onRamp.setDynamicConfig(newConfig); + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.withdrawFeeTokens.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.withdrawFeeTokens.t.sol new file mode 100644 index 00000000000..d4a297c103c --- /dev/null +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRamp.withdrawFeeTokens.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Client} from "../../../libraries/Client.sol"; +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {OnRampSetup} from "./OnRampSetup.t.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +contract OnRamp_withdrawFeeTokens is OnRampSetup { + mapping(address => uint256) internal s_nopFees; + + function setUp() public virtual override { + super.setUp(); + + // Since we'll mostly be testing for valid calls from the router we'll + // mock all calls to be originating from the router and re-mock in + // tests that require failure. + vm.startPrank(address(s_sourceRouter)); + + uint256 feeAmount = 1234567890; + + // Send a bunch of messages, increasing the juels in the contract + for (uint256 i = 0; i < s_sourceFeeTokens.length; ++i) { + Client.EVM2AnyMessage memory message = _generateEmptyMessage(); + message.feeToken = s_sourceFeeTokens[i % s_sourceFeeTokens.length]; + uint256 newFeeTokenBalance = IERC20(message.feeToken).balanceOf(address(s_onRamp)) + feeAmount; + deal(message.feeToken, address(s_onRamp), newFeeTokenBalance); + s_nopFees[message.feeToken] = newFeeTokenBalance; + s_onRamp.forwardFromRouter(DEST_CHAIN_SELECTOR, message, feeAmount, OWNER); + } + } + + function test_Fuzz_WithdrawFeeTokens_Success( + uint256[5] memory amounts + ) public { + vm.startPrank(OWNER); + address[] memory feeTokens = new address[](amounts.length); + for (uint256 i = 0; i < amounts.length; ++i) { + vm.assume(amounts[i] > 0); + feeTokens[i] = _deploySourceToken("", amounts[i], 18); + IERC20(feeTokens[i]).transfer(address(s_onRamp), amounts[i]); + } + + s_feeQuoter.applyFeeTokensUpdates(new address[](0), feeTokens); + + for (uint256 i = 0; i < feeTokens.length; ++i) { + vm.expectEmit(); + emit OnRamp.FeeTokenWithdrawn(FEE_AGGREGATOR, feeTokens[i], amounts[i]); + } + + s_onRamp.withdrawFeeTokens(feeTokens); + + for (uint256 i = 0; i < feeTokens.length; ++i) { + assertEq(IERC20(feeTokens[i]).balanceOf(FEE_AGGREGATOR), amounts[i]); + assertEq(IERC20(feeTokens[i]).balanceOf(address(s_onRamp)), 0); + } + } + + function test_WithdrawFeeTokens_Success() public { + vm.expectEmit(); + emit OnRamp.FeeTokenWithdrawn(FEE_AGGREGATOR, s_sourceFeeToken, s_nopFees[s_sourceFeeToken]); + + s_onRamp.withdrawFeeTokens(s_sourceFeeTokens); + + assertEq(IERC20(s_sourceFeeToken).balanceOf(FEE_AGGREGATOR), s_nopFees[s_sourceFeeToken]); + assertEq(IERC20(s_sourceFeeToken).balanceOf(address(s_onRamp)), 0); + } +} diff --git a/contracts/src/v0.8/ccip/test/onRamp/OnRampSetup.t.sol b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRampSetup.t.sol similarity index 58% rename from contracts/src/v0.8/ccip/test/onRamp/OnRampSetup.t.sol rename to contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRampSetup.t.sol index 8254dd977f7..ead9e7088ce 100644 --- a/contracts/src/v0.8/ccip/test/onRamp/OnRampSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/onRamp/onRamp/OnRampSetup.t.sol @@ -1,48 +1,42 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; -import {IRouter} from "../../interfaces/IRouter.sol"; +import {IRouter} from "../../../interfaces/IRouter.sol"; -import {AuthorizedCallers} from "../../../shared/access/AuthorizedCallers.sol"; -import {NonceManager} from "../../NonceManager.sol"; -import {Router} from "../../Router.sol"; -import {Client} from "../../libraries/Client.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {OnRamp} from "../../onRamp/OnRamp.sol"; -import {FeeQuoterFeeSetup} from "../feeQuoter/FeeQuoterSetup.t.sol"; -import {MessageInterceptorHelper} from "../helpers/MessageInterceptorHelper.sol"; -import {OnRampHelper} from "../helpers/OnRampHelper.sol"; +import {AuthorizedCallers} from "../../../../shared/access/AuthorizedCallers.sol"; +import {NonceManager} from "../../../NonceManager.sol"; +import {Router} from "../../../Router.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {OnRamp} from "../../../onRamp/OnRamp.sol"; +import {TokenAdminRegistry} from "../../../tokenAdminRegistry/TokenAdminRegistry.sol"; +import {FeeQuoterFeeSetup} from "../../feeQuoter/FeeQuoterSetup.t.sol"; +import {OnRampHelper} from "../../helpers/OnRampHelper.sol"; -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; contract OnRampSetup is FeeQuoterFeeSetup { - uint256 internal immutable i_tokenAmount0 = 9; - uint256 internal immutable i_tokenAmount1 = 7; + address internal constant FEE_AGGREGATOR = 0xa33CDB32eAEce34F6affEfF4899cef45744EDea3; bytes32 internal s_metadataHash; OnRampHelper internal s_onRamp; - MessageInterceptorHelper internal s_outboundMessageInterceptor; - address[] internal s_offRamps; NonceManager internal s_outboundNonceManager; function setUp() public virtual override { super.setUp(); - s_outboundMessageInterceptor = new MessageInterceptorHelper(); s_outboundNonceManager = new NonceManager(new address[](0)); (s_onRamp, s_metadataHash) = _deployOnRamp( SOURCE_CHAIN_SELECTOR, s_sourceRouter, address(s_outboundNonceManager), address(s_tokenAdminRegistry) ); - s_offRamps = new address[](2); - s_offRamps[0] = address(10); - s_offRamps[1] = address(11); Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); - Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](2); onRampUpdates[0] = Router.OnRamp({destChainSelector: DEST_CHAIN_SELECTOR, onRamp: address(s_onRamp)}); - offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: s_offRamps[0]}); - offRampUpdates[1] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: s_offRamps[1]}); + + Router.OffRamp[] memory offRampUpdates = new Router.OffRamp[](2); + offRampUpdates[0] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: makeAddr("offRamp0")}); + offRampUpdates[1] = Router.OffRamp({sourceChainSelector: SOURCE_CHAIN_SELECTOR, offRamp: makeAddr("offRamp1")}); s_sourceRouter.applyRampUpdates(onRampUpdates, new Router.OffRamp[](0), offRampUpdates); // Pre approve the first token so the gas estimates of the tests @@ -51,19 +45,6 @@ contract OnRampSetup is FeeQuoterFeeSetup { IERC20(s_sourceTokens[1]).approve(address(s_sourceRouter), 2 ** 128); } - function _generateTokenMessage() public view returns (Client.EVM2AnyMessage memory) { - Client.EVMTokenAmount[] memory tokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); - tokenAmounts[0].amount = i_tokenAmount0; - tokenAmounts[1].amount = i_tokenAmount1; - return Client.EVM2AnyMessage({ - receiver: abi.encode(OWNER), - data: "", - tokenAmounts: tokenAmounts, - feeToken: s_sourceFeeToken, - extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT})) - }); - } - /// @dev a helper function to compose EVM2AnyRampMessage messages /// @dev it is assumed that LINK is the payment token because feeTokenAmount == feeValueJuels function _messageToEvent( @@ -105,6 +86,48 @@ contract OnRampSetup is FeeQuoterFeeSetup { ); } + function _messageToEvent( + Client.EVM2AnyMessage memory message, + uint64 sourceChainSelector, + uint64 destChainSelector, + uint64 seqNum, + uint64 nonce, + uint256 feeTokenAmount, + uint256 feeValueJuels, + address originalSender, + bytes32 metadataHash, + TokenAdminRegistry tokenAdminRegistry + ) internal view returns (Internal.EVM2AnyRampMessage memory) { + Client.EVMExtraArgsV2 memory extraArgs = + s_feeQuoter.parseEVMExtraArgsFromBytes(message.extraArgs, destChainSelector); + + Internal.EVM2AnyRampMessage memory messageEvent = Internal.EVM2AnyRampMessage({ + header: Internal.RampMessageHeader({ + messageId: "", + sourceChainSelector: sourceChainSelector, + destChainSelector: destChainSelector, + sequenceNumber: seqNum, + nonce: extraArgs.allowOutOfOrderExecution ? 0 : nonce + }), + sender: originalSender, + data: message.data, + receiver: message.receiver, + extraArgs: Client._argsToBytes(extraArgs), + feeToken: message.feeToken, + feeTokenAmount: feeTokenAmount, + feeValueJuels: feeValueJuels, + tokenAmounts: new Internal.EVM2AnyTokenTransfer[](message.tokenAmounts.length) + }); + + for (uint256 i = 0; i < message.tokenAmounts.length; ++i) { + messageEvent.tokenAmounts[i] = + _getSourceTokenData(message.tokenAmounts[i], tokenAdminRegistry, DEST_CHAIN_SELECTOR); + } + + messageEvent.header.messageId = Internal._hash(messageEvent, metadataHash); + return messageEvent; + } + function _generateDynamicOnRampConfig( address feeQuoter ) internal pure returns (OnRamp.DynamicConfig memory) { @@ -117,17 +140,6 @@ contract OnRampSetup is FeeQuoterFeeSetup { }); } - // Slicing is only available for calldata. So we have to build a new bytes array. - function _removeFirst4Bytes( - bytes memory data - ) internal pure returns (bytes memory) { - bytes memory result = new bytes(data.length - 4); - for (uint256 i = 4; i < data.length; ++i) { - result[i - 4] = data[i]; - } - return result; - } - function _generateDestChainConfigArgs( IRouter router ) internal pure returns (OnRamp.DestChainConfigArgs[] memory) { @@ -166,35 +178,4 @@ contract OnRampSetup is FeeQuoterFeeSetup { keccak256(abi.encode(Internal.EVM_2_ANY_MESSAGE_HASH, sourceChainSelector, DEST_CHAIN_SELECTOR, address(onRamp))) ); } - - function _enableOutboundMessageInterceptor() internal { - (, address msgSender,) = vm.readCallers(); - - bool resetPrank = false; - - if (msgSender != OWNER) { - vm.stopPrank(); - vm.startPrank(OWNER); - resetPrank = true; - } - - OnRamp.DynamicConfig memory dynamicConfig = s_onRamp.getDynamicConfig(); - dynamicConfig.messageInterceptor = address(s_outboundMessageInterceptor); - s_onRamp.setDynamicConfig(dynamicConfig); - - if (resetPrank) { - vm.stopPrank(); - vm.startPrank(msgSender); - } - } - - function _assertStaticConfigsEqual(OnRamp.StaticConfig memory a, OnRamp.StaticConfig memory b) internal pure { - assertEq(a.chainSelector, b.chainSelector); - assertEq(address(a.rmnRemote), address(b.rmnRemote)); - assertEq(a.tokenAdminRegistry, b.tokenAdminRegistry); - } - - function _assertDynamicConfigsEqual(OnRamp.DynamicConfig memory a, OnRamp.DynamicConfig memory b) internal pure { - assertEq(a.feeQuoter, b.feeQuoter); - } } diff --git a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol index 98ef26aeb08..ce1104246dd 100644 --- a/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool/LockReleaseTokenPoolSetup.t.sol @@ -29,7 +29,7 @@ contract LockReleaseTokenPoolSetup is RouterSetup { new LockReleaseTokenPool(s_token, new address[](0), address(s_mockRMN), true, address(s_sourceRouter)); s_allowedList.push(USER_1); - s_allowedList.push(DUMMY_CONTRACT_ADDRESS); + s_allowedList.push(OWNER); s_lockReleaseTokenPoolWithAllowList = new LockReleaseTokenPool(s_token, s_allowedList, address(s_mockRMN), true, address(s_sourceRouter)); diff --git a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolWithAllowListSetup.t.sol b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolWithAllowListSetup.t.sol index f2408af0fca..d441c11c352 100644 --- a/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolWithAllowListSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/pools/TokenPool/TokenPoolWithAllowListSetup.t.sol @@ -11,7 +11,7 @@ contract TokenPoolWithAllowListSetup is TokenPoolSetup { TokenPoolSetup.setUp(); s_allowedSenders.push(STRANGER); - s_allowedSenders.push(DUMMY_CONTRACT_ADDRESS); + s_allowedSenders.push(OWNER); s_tokenPool = new TokenPoolHelper(s_token, s_allowedSenders, address(s_mockRMN), address(s_sourceRouter)); } diff --git a/contracts/src/v0.8/ccip/test/router/Router.t.sol b/contracts/src/v0.8/ccip/test/router/Router.t.sol index a0fdfc3c2aa..9b2bb92b4e4 100644 --- a/contracts/src/v0.8/ccip/test/router/Router.t.sol +++ b/contracts/src/v0.8/ccip/test/router/Router.t.sol @@ -12,7 +12,7 @@ import {Internal} from "../../libraries/Internal.sol"; import {OnRamp} from "../../onRamp/OnRamp.sol"; import {MaybeRevertMessageReceiver} from "../helpers/receivers/MaybeRevertMessageReceiver.sol"; import {OffRampSetup} from "../offRamp/offRamp/OffRampSetup.t.sol"; -import {OnRampSetup} from "../onRamp/OnRampSetup.t.sol"; +import {OnRampSetup} from "../onRamp/onRamp/OnRampSetup.t.sol"; import {RouterSetup} from "../router/RouterSetup.t.sol"; import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; From 4f0491483c089ee2fd5e7875b0d58b65b304b111 Mon Sep 17 00:00:00 2001 From: Mateusz Sekara Date: Wed, 6 Nov 2024 13:28:46 +0100 Subject: [PATCH 10/11] CCIP - Token transfer test (#15130) * CCIP-3591: Adding e2e test for USDC transfer * debug * temp change * resolve conflict * Fixing chain reader init for USDC integration * Minor fix to the debug logs * Approving token * Send tokens in send request * Add registry module and register pool * Bump * Bump * Burn/Mint permissions * Proper addresses * Working test * Working test * Working test * Removing USDC part * Removing USDC part * Removing USDC part * Removing USDC part * Removing USDC part * Removing USDC part * Removing USDC part * Removing USDC part * Removing USDC part * Removing USDC part * Post review fixes --------- Co-authored-by: Balamurali Gopalswami --- .changeset/six-wombats-shake.md | 5 + .../capabilities/ccip/oraclecreator/plugin.go | 14 +- deployment/ccip/add_lane_test.go | 4 +- deployment/ccip/changeset/active_candidate.go | 1 - .../ccip/changeset/active_candidate_test.go | 3 +- deployment/ccip/changeset/add_chain.go | 1 - deployment/ccip/changeset/add_chain_test.go | 2 +- .../ccip/changeset/initial_deploy_test.go | 2 +- deployment/ccip/deploy.go | 41 ++- deployment/ccip/deploy_home_chain.go | 5 +- deployment/ccip/state.go | 17 +- deployment/ccip/test_helpers.go | 236 +++++++++++++++++- deployment/environment.go | 3 +- integration-tests/smoke/ccip_rmn_test.go | 2 +- integration-tests/smoke/ccip_test.go | 156 +++++++++++- 15 files changed, 449 insertions(+), 43 deletions(-) create mode 100644 .changeset/six-wombats-shake.md diff --git a/.changeset/six-wombats-shake.md b/.changeset/six-wombats-shake.md new file mode 100644 index 00000000000..448cf03bd51 --- /dev/null +++ b/.changeset/six-wombats-shake.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +CCIP token transfer integration tests #internal diff --git a/core/capabilities/ccip/oraclecreator/plugin.go b/core/capabilities/ccip/oraclecreator/plugin.go index 2b4fae933a3..ea08f150f10 100644 --- a/core/capabilities/ccip/oraclecreator/plugin.go +++ b/core/capabilities/ccip/oraclecreator/plugin.go @@ -335,7 +335,7 @@ func (i *pluginOracleCreator) createReadersAndWriters( return nil, nil, fmt.Errorf("failed to get chain selector from chain ID %s: %w", chainID.String(), err1) } - chainReaderConfig, err1 := getChainReaderConfig(chainID.Uint64(), destChainID, homeChainID, ofc, chainSelector) + chainReaderConfig, err1 := getChainReaderConfig(i.lggr, chainID.Uint64(), destChainID, homeChainID, ofc, chainSelector) if err1 != nil { return nil, nil, fmt.Errorf("failed to get chain reader config: %w", err1) } @@ -430,6 +430,7 @@ func (i *pluginOracleCreator) getChainID(chainSelector cciptypes.ChainSelector) } func getChainReaderConfig( + lggr logger.Logger, chainID uint64, destChainID uint64, homeChainID uint64, @@ -444,14 +445,17 @@ func getChainReaderConfig( } if !ofc.commitEmpty() && ofc.commit().PriceFeedChainSelector == chainSelector { + lggr.Debugw("Adding feed reader config", "chainID", chainID) chainReaderConfig = evmconfig.MergeReaderConfigs(chainReaderConfig, evmconfig.FeedReaderConfig) } - if isUSDCEnabled(chainID, destChainID, ofc) { + if isUSDCEnabled(ofc) { + lggr.Debugw("Adding USDC reader config", "chainID", chainID) chainReaderConfig = evmconfig.MergeReaderConfigs(chainReaderConfig, evmconfig.USDCReaderConfig) } if chainID == homeChainID { + lggr.Debugw("Adding home chain reader config", "chainID", chainID) chainReaderConfig = evmconfig.MergeReaderConfigs(chainReaderConfig, evmconfig.HomeChainReaderConfigRaw) } @@ -463,11 +467,7 @@ func getChainReaderConfig( return marshaledConfig, nil } -func isUSDCEnabled(chainID uint64, destChainID uint64, ofc offChainConfig) bool { - if chainID == destChainID { - return false - } - +func isUSDCEnabled(ofc offChainConfig) bool { if ofc.execEmpty() { return false } diff --git a/deployment/ccip/add_lane_test.go b/deployment/ccip/add_lane_test.go index de47aa8e627..a7618ecb712 100644 --- a/deployment/ccip/add_lane_test.go +++ b/deployment/ccip/add_lane_test.go @@ -93,7 +93,7 @@ func TestAddLane(t *testing.T) { startBlock := latesthdr.Number.Uint64() // Send traffic on the first lane and it should not be processed by the plugin as onRamp is disabled // we will check this by confirming that the message is not executed by the end of the test - seqNum1 := TestSendRequest(t, e.Env, state, chain1, chain2, false) + seqNum1 := TestSendRequest(t, e.Env, state, chain1, chain2, false, nil) require.Equal(t, uint64(1), seqNum1) // Add another lane @@ -103,7 +103,7 @@ func TestAddLane(t *testing.T) { latesthdr, err = e.Env.Chains[chain1].Client.HeaderByNumber(testcontext.Get(t), nil) require.NoError(t, err) startBlock2 := latesthdr.Number.Uint64() - seqNum2 := TestSendRequest(t, e.Env, state, chain2, chain1, false) + seqNum2 := TestSendRequest(t, e.Env, state, chain2, chain1, false, nil) require.Equal(t, uint64(1), seqNum2) require.NoError(t, ConfirmExecWithSeqNr(t, e.Env.Chains[chain2], e.Env.Chains[chain1], state.Chains[chain1].OffRamp, &startBlock2, seqNum2)) diff --git a/deployment/ccip/changeset/active_candidate.go b/deployment/ccip/changeset/active_candidate.go index 7d09f81049b..e1d67720d12 100644 --- a/deployment/ccip/changeset/active_candidate.go +++ b/deployment/ccip/changeset/active_candidate.go @@ -52,7 +52,6 @@ func SetCandidatePluginChangeset( pluginType cctypes.PluginType, ) (deployment.ChangesetOutput, error) { newDONArgs, err := ccdeploy.BuildOCR3ConfigForCCIPHome( - e.Logger, ocrSecrets, state.Chains[newChainSel].OffRamp, e.Chains[newChainSel], diff --git a/deployment/ccip/changeset/active_candidate_test.go b/deployment/ccip/changeset/active_candidate_test.go index a91b2104a60..ab27d4c96db 100644 --- a/deployment/ccip/changeset/active_candidate_test.go +++ b/deployment/ccip/changeset/active_candidate_test.go @@ -82,7 +82,7 @@ func TestActiveCandidate(t *testing.T) { require.NoError(t, err) block := latesthdr.Number.Uint64() startBlocks[dest] = &block - seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false) + seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false, nil) expectedSeqNum[dest] = seqNum } } @@ -145,7 +145,6 @@ func TestActiveCandidate(t *testing.T) { // commit and exec plugin we will be using rmnHomeAddress := state.Chains[homeCS].RMNHome.Address() ocr3ConfigMap, err := ccdeploy.BuildOCR3ConfigForCCIPHome( - e.Logger, deployment.XXXGenerateTestOCRSecrets(), state.Chains[destCS].OffRamp, e.Chains[destCS], diff --git a/deployment/ccip/changeset/add_chain.go b/deployment/ccip/changeset/add_chain.go index 9742de64bc6..aa88e0f112f 100644 --- a/deployment/ccip/changeset/add_chain.go +++ b/deployment/ccip/changeset/add_chain.go @@ -100,7 +100,6 @@ func AddDonAndSetCandidateChangeset( pluginType types.PluginType, ) (deployment.ChangesetOutput, error) { newDONArgs, err := ccipdeployment.BuildOCR3ConfigForCCIPHome( - e.Logger, ocrSecrets, state.Chains[newChainSel].OffRamp, e.Chains[newChainSel], diff --git a/deployment/ccip/changeset/add_chain_test.go b/deployment/ccip/changeset/add_chain_test.go index 8149b6e1b0b..c0d76875b6c 100644 --- a/deployment/ccip/changeset/add_chain_test.go +++ b/deployment/ccip/changeset/add_chain_test.go @@ -211,7 +211,7 @@ func TestAddChainInbound(t *testing.T) { latesthdr, err := e.Env.Chains[newChain].Client.HeaderByNumber(testcontext.Get(t), nil) require.NoError(t, err) startBlock := latesthdr.Number.Uint64() - seqNr := ccipdeployment.TestSendRequest(t, e.Env, state, initialDeploy[0], newChain, true) + seqNr := ccipdeployment.TestSendRequest(t, e.Env, state, initialDeploy[0], newChain, true, nil) require.NoError(t, ccipdeployment.ConfirmCommitWithExpectedSeqNumRange(t, e.Env.Chains[initialDeploy[0]], e.Env.Chains[newChain], state.Chains[newChain].OffRamp, &startBlock, cciptypes.SeqNumRange{ cciptypes.SeqNum(1), diff --git a/deployment/ccip/changeset/initial_deploy_test.go b/deployment/ccip/changeset/initial_deploy_test.go index 65b6bf51e62..c172f9f84c8 100644 --- a/deployment/ccip/changeset/initial_deploy_test.go +++ b/deployment/ccip/changeset/initial_deploy_test.go @@ -72,7 +72,7 @@ func TestInitialDeploy(t *testing.T) { require.NoError(t, err) block := latesthdr.Number.Uint64() startBlocks[dest] = &block - seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false) + seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false, nil) expectedSeqNum[dest] = seqNum } } diff --git a/deployment/ccip/deploy.go b/deployment/ccip/deploy.go index a0c61cd5f96..4d90422c843 100644 --- a/deployment/ccip/deploy.go +++ b/deployment/ccip/deploy.go @@ -9,17 +9,18 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" - owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/registry_module_owner_custom" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/aggregator_v3_interface" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/erc20" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/burn_mint_token_pool" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/maybe_revert_message_receiver" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/nonce_manager" @@ -42,6 +43,7 @@ var ( Router deployment.ContractType = "Router" CommitStore deployment.ContractType = "CommitStore" TokenAdminRegistry deployment.ContractType = "TokenAdminRegistry" + RegistryModule deployment.ContractType = "RegistryModuleOwnerCustom" NonceManager deployment.ContractType = "NonceManager" FeeQuoter deployment.ContractType = "FeeQuoter" AdminManyChainMultisig deployment.ContractType = "AdminManyChainMultiSig" @@ -57,8 +59,10 @@ var ( CapabilitiesRegistry deployment.ContractType = "CapabilitiesRegistry" PriceFeed deployment.ContractType = "PriceFeed" // Note test router maps to a regular router contract. - TestRouter deployment.ContractType = "TestRouter" - CCIPReceiver deployment.ContractType = "CCIPReceiver" + TestRouter deployment.ContractType = "TestRouter" + CCIPReceiver deployment.ContractType = "CCIPReceiver" + BurnMintToken deployment.ContractType = "BurnMintToken" + BurnMintTokenPool deployment.ContractType = "BurnMintTokenPool" ) type Contracts interface { @@ -70,6 +74,7 @@ type Contracts interface { *fee_quoter.FeeQuoter | *router.Router | *token_admin_registry.TokenAdminRegistry | + *registry_module_owner_custom.RegistryModuleOwnerCustom | *weth9.WETH9 | *rmn_remote.RMNRemote | *owner_helpers.ManyChainMultiSig | @@ -77,8 +82,10 @@ type Contracts interface { *offramp.OffRamp | *onramp.OnRamp | *burn_mint_erc677.BurnMintERC677 | + *burn_mint_token_pool.BurnMintTokenPool | *maybe_revert_message_receiver.MaybeRevertMessageReceiver | - *aggregator_v3_interface.AggregatorV3Interface + *aggregator_v3_interface.AggregatorV3Interface | + *erc20.ERC20 } type ContractDeploy[C Contracts] struct { @@ -233,7 +240,6 @@ func DeployCCIPContracts(e deployment.Environment, ab deployment.AddressBook, c if err != nil { return err } - // For each chain, we create a DON on the home chain (2 OCR instances) if err := AddDON( e.Logger, @@ -556,6 +562,29 @@ func DeployChainContracts( } e.Logger.Infow("deployed tokenAdminRegistry", "addr", tokenAdminRegistry) + customRegistryModule, err := deployContract(e.Logger, chain, ab, + func(chain deployment.Chain) ContractDeploy[*registry_module_owner_custom.RegistryModuleOwnerCustom] { + regModAddr, tx2, regMod, err2 := registry_module_owner_custom.DeployRegistryModuleOwnerCustom( + chain.DeployerKey, + chain.Client, + tokenAdminRegistry.Address) + return ContractDeploy[*registry_module_owner_custom.RegistryModuleOwnerCustom]{ + regModAddr, regMod, tx2, deployment.NewTypeAndVersion(RegistryModule, deployment.Version1_5_0), err2, + } + }) + if err != nil { + e.Logger.Errorw("Failed to deploy custom registry module", "err", err) + return err + } + e.Logger.Infow("deployed custom registry module", "addr", tokenAdminRegistry) + + tx, err = tokenAdminRegistry.Contract.AddRegistryModule(chain.DeployerKey, customRegistryModule.Address) + if err != nil { + e.Logger.Errorw("Failed to assign registry module on token admin registry", "err", err) + return err + } + e.Logger.Infow("assigned registry module on token admin registry") + nonceManager, err := deployContract(e.Logger, chain, ab, func(chain deployment.Chain) ContractDeploy[*nonce_manager.NonceManager] { nonceManagerAddr, tx2, nonceManager, err2 := nonce_manager.DeployNonceManager( diff --git a/deployment/ccip/deploy_home_chain.go b/deployment/ccip/deploy_home_chain.go index 2e306a45fd8..3f614b8510f 100644 --- a/deployment/ccip/deploy_home_chain.go +++ b/deployment/ccip/deploy_home_chain.go @@ -308,12 +308,10 @@ func AddChainConfig( } func BuildOCR3ConfigForCCIPHome( - lggr logger.Logger, ocrSecrets deployment.OCRSecrets, offRamp *offramp.OffRamp, dest deployment.Chain, feedChainSel uint64, - // Token address on Dest chain to aggregate address on feed chain tokenInfo map[ccipocr3.UnknownEncodedAddress]pluginconfig.TokenInfo, nodes deployment.Nodes, rmnHomeAddress common.Address, @@ -360,6 +358,7 @@ func BuildOCR3ConfigForCCIPHome( InflightCacheExpiry: *commonconfig.MustNewDuration(InflightCacheExpiry), RootSnoozeTime: *commonconfig.MustNewDuration(RootSnoozeTime), BatchingStrategyID: BatchingStrategyID, + TokenDataObservers: []pluginconfig.TokenDataObserverConfig{}, }) } if err2 != nil { @@ -891,7 +890,7 @@ func AddDON( home deployment.Chain, nodes deployment.Nodes, ) error { - ocrConfigs, err := BuildOCR3ConfigForCCIPHome(lggr, ocrSecrets, offRamp, dest, feedChainSel, tokenInfo, nodes, rmnHomeAddress) + ocrConfigs, err := BuildOCR3ConfigForCCIPHome(ocrSecrets, offRamp, dest, feedChainSel, tokenInfo, nodes, rmnHomeAddress) if err != nil { return err } diff --git a/deployment/ccip/state.go b/deployment/ccip/state.go index 528d21700cf..650f46d2b3a 100644 --- a/deployment/ccip/state.go +++ b/deployment/ccip/state.go @@ -9,17 +9,17 @@ import ( chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/view" "github.com/smartcontractkit/chainlink/deployment/ccip/view/v1_0" + "github.com/smartcontractkit/chainlink/deployment/ccip/view/v1_2" + "github.com/smartcontractkit/chainlink/deployment/ccip/view/v1_5" + "github.com/smartcontractkit/chainlink/deployment/ccip/view/v1_6" common_v1_0 "github.com/smartcontractkit/chainlink/deployment/common/view/v1_0" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_config" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/commit_store" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/registry_module_owner_custom" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" - "github.com/smartcontractkit/chainlink/deployment/ccip/view" - "github.com/smartcontractkit/chainlink/deployment/ccip/view/v1_2" - "github.com/smartcontractkit/chainlink/deployment/ccip/view/v1_5" - "github.com/smartcontractkit/chainlink/deployment/ccip/view/v1_6" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/maybe_revert_message_receiver" @@ -48,6 +48,7 @@ type CCIPChainState struct { RMNProxy *rmn_proxy_contract.RMNProxyContract NonceManager *nonce_manager.NonceManager TokenAdminRegistry *token_admin_registry.TokenAdminRegistry + RegistryModule *registry_module_owner_custom.RegistryModuleOwnerCustom Router *router.Router CommitStore *commit_store.CommitStore Weth9 *weth9.WETH9 @@ -320,6 +321,12 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type return state, err } state.TokenAdminRegistry = tm + case deployment.NewTypeAndVersion(RegistryModule, deployment.Version1_5_0).String(): + rm, err := registry_module_owner_custom.NewRegistryModuleOwnerCustom(common.HexToAddress(address), chain.Client) + if err != nil { + return state, err + } + state.RegistryModule = rm case deployment.NewTypeAndVersion(Router, deployment.Version1_2_0).String(): r, err := router.NewRouter(common.HexToAddress(address), chain.Client) if err != nil { diff --git a/deployment/ccip/test_helpers.go b/deployment/ccip/test_helpers.go index 3f9db87db15..de1ebd7e675 100644 --- a/deployment/ccip/test_helpers.go +++ b/deployment/ccip/test_helpers.go @@ -10,14 +10,17 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" - "github.com/stretchr/testify/require" - cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "go.uber.org/multierr" "go.uber.org/zap/zapcore" @@ -34,6 +37,7 @@ import ( "github.com/smartcontractkit/chainlink/deployment/environment/devenv" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/burn_mint_token_pool" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/mock_v3_aggregator_contract" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/aggregator_v3_interface" @@ -229,10 +233,10 @@ func CCIPSendRequest( return tx, blockNum, nil } -func TestSendRequest(t *testing.T, e deployment.Environment, state CCIPOnChainState, src, dest uint64, testRouter bool) uint64 { +func TestSendRequest(t *testing.T, e deployment.Environment, state CCIPOnChainState, src, dest uint64, testRouter bool, tokensAndAmounts []router.ClientEVMTokenAmount) uint64 { t.Logf("Sending CCIP request from chain selector %d to chain selector %d", src, dest) - tx, blockNum, err := CCIPSendRequest(e, state, src, dest, []byte("hello"), nil, common.HexToAddress("0x0"), testRouter, nil) + tx, blockNum, err := CCIPSendRequest(e, state, src, dest, []byte("hello"), tokensAndAmounts, common.HexToAddress("0x0"), testRouter, nil) require.NoError(t, err) it, err := state.Chains[src].OnRamp.FilterCCIPMessageSent(&bind.FilterOpts{ Start: blockNum, @@ -373,7 +377,7 @@ func ConfirmRequestOnSourceAndDest(t *testing.T, env deployment.Environment, sta require.NoError(t, err) startBlock := latesthdr.Number.Uint64() fmt.Printf("startblock %d", startBlock) - seqNum := TestSendRequest(t, env, state, sourceCS, destCS, false) + seqNum := TestSendRequest(t, env, state, sourceCS, destCS, false, nil) require.Equal(t, expectedSeqNr, seqNum) fmt.Printf("Request sent for seqnr %d", seqNum) @@ -417,3 +421,223 @@ func ProcessChangeset(t *testing.T, e deployment.Environment, c deployment.Chang require.NoError(t, err) } } + +func DeployTransferableToken( + lggr logger.Logger, + chains map[uint64]deployment.Chain, + src, dst uint64, + state CCIPOnChainState, + addresses deployment.AddressBook, + token string, +) (*burn_mint_erc677.BurnMintERC677, *burn_mint_token_pool.BurnMintTokenPool, *burn_mint_erc677.BurnMintERC677, *burn_mint_token_pool.BurnMintTokenPool, error) { + // Deploy token and pools + srcToken, srcPool, err := deployTransferTokenOneEnd(lggr, chains[src], addresses, token) + if err != nil { + return nil, nil, nil, nil, err + } + dstToken, dstPool, err := deployTransferTokenOneEnd(lggr, chains[dst], addresses, token) + if err != nil { + return nil, nil, nil, nil, err + } + + // Attach token pools to registry + if err := attachTokenToTheRegistry(chains[src], state.Chains[src], chains[src].DeployerKey, srcToken.Address(), srcPool.Address()); err != nil { + return nil, nil, nil, nil, err + } + + if err := attachTokenToTheRegistry(chains[dst], state.Chains[dst], chains[dst].DeployerKey, dstToken.Address(), dstPool.Address()); err != nil { + return nil, nil, nil, nil, err + } + + // Connect pool to each other + if err := setTokenPoolCounterPart(chains[src], srcPool, dst, dstToken.Address(), dstPool.Address()); err != nil { + return nil, nil, nil, nil, err + } + + if err := setTokenPoolCounterPart(chains[dst], dstPool, src, srcToken.Address(), srcPool.Address()); err != nil { + return nil, nil, nil, nil, err + } + + // Add burn/mint permissions + if err := grantMintBurnPermissions(chains[src], srcToken, srcPool.Address()); err != nil { + return nil, nil, nil, nil, err + } + + if err := grantMintBurnPermissions(chains[dst], dstToken, dstPool.Address()); err != nil { + return nil, nil, nil, nil, err + } + + return srcToken, srcPool, dstToken, dstPool, nil +} + +func grantMintBurnPermissions(chain deployment.Chain, token *burn_mint_erc677.BurnMintERC677, address common.Address) error { + tx, err := token.GrantBurnRole(chain.DeployerKey, address) + if err != nil { + return err + } + _, err = chain.Confirm(tx) + if err != nil { + return err + } + + tx, err = token.GrantMintRole(chain.DeployerKey, address) + if err != nil { + return err + } + _, err = chain.Confirm(tx) + return err +} + +func setTokenPoolCounterPart( + chain deployment.Chain, + tokenPool *burn_mint_token_pool.BurnMintTokenPool, + destChainSelector uint64, + destTokenAddress common.Address, + destTokenPoolAddress common.Address, +) error { + tx, err := tokenPool.ApplyChainUpdates( + chain.DeployerKey, + []burn_mint_token_pool.TokenPoolChainUpdate{ + { + RemoteChainSelector: destChainSelector, + Allowed: true, + RemotePoolAddress: common.LeftPadBytes(destTokenPoolAddress.Bytes(), 32), + RemoteTokenAddress: common.LeftPadBytes(destTokenAddress.Bytes(), 32), + OutboundRateLimiterConfig: burn_mint_token_pool.RateLimiterConfig{ + IsEnabled: false, + Capacity: big.NewInt(0), + Rate: big.NewInt(0), + }, + InboundRateLimiterConfig: burn_mint_token_pool.RateLimiterConfig{ + IsEnabled: false, + Capacity: big.NewInt(0), + Rate: big.NewInt(0), + }, + }, + }, + ) + if err != nil { + return err + } + + _, err = chain.Confirm(tx) + if err != nil { + return err + } + + tx, err = tokenPool.SetRemotePool( + chain.DeployerKey, + destChainSelector, + destTokenPoolAddress.Bytes(), + ) + return err +} + +func attachTokenToTheRegistry( + chain deployment.Chain, + state CCIPChainState, + owner *bind.TransactOpts, + token common.Address, + tokenPool common.Address, +) error { + tx, err := state.RegistryModule.RegisterAdminViaOwner(owner, token) + if err != nil { + return err + } + _, err = chain.Confirm(tx) + if err != nil { + return err + } + + tx, err = state.TokenAdminRegistry.AcceptAdminRole(owner, token) + if err != nil { + return err + } + _, err = chain.Confirm(tx) + if err != nil { + return err + } + + tx, err = state.TokenAdminRegistry.SetPool(owner, token, tokenPool) + if err != nil { + return err + } + _, err = chain.Confirm(tx) + if err != nil { + return err + } + return nil +} + +func deployTransferTokenOneEnd( + lggr logger.Logger, + chain deployment.Chain, + addressBook deployment.AddressBook, + tokenSymbol string, +) (*burn_mint_erc677.BurnMintERC677, *burn_mint_token_pool.BurnMintTokenPool, error) { + var rmnAddress, routerAddress string + chainAddresses, err := addressBook.AddressesForChain(chain.Selector) + if err != nil { + return nil, nil, err + } + for address, v := range chainAddresses { + if deployment.NewTypeAndVersion(ARMProxy, deployment.Version1_0_0) == v { + rmnAddress = address + } + if deployment.NewTypeAndVersion(Router, deployment.Version1_2_0) == v { + routerAddress = address + } + if rmnAddress != "" && routerAddress != "" { + break + } + } + + tokenContract, err := deployContract(lggr, chain, addressBook, + func(chain deployment.Chain) ContractDeploy[*burn_mint_erc677.BurnMintERC677] { + USDCTokenAddr, tx, token, err2 := burn_mint_erc677.DeployBurnMintERC677( + chain.DeployerKey, + chain.Client, + tokenSymbol, + tokenSymbol, + uint8(18), + big.NewInt(0).Mul(big.NewInt(1e9), big.NewInt(1e18)), + ) + return ContractDeploy[*burn_mint_erc677.BurnMintERC677]{ + USDCTokenAddr, token, tx, deployment.NewTypeAndVersion(BurnMintToken, deployment.Version1_0_0), err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy Token ERC677", "err", err) + return nil, nil, err + } + + tx, err := tokenContract.Contract.GrantMintRole(chain.DeployerKey, chain.DeployerKey.From) + if err != nil { + return nil, nil, err + } + _, err = chain.Confirm(tx) + if err != nil { + return nil, nil, err + } + + tokenPool, err := deployContract(lggr, chain, addressBook, + func(chain deployment.Chain) ContractDeploy[*burn_mint_token_pool.BurnMintTokenPool] { + tokenPoolAddress, tx, tokenPoolContract, err2 := burn_mint_token_pool.DeployBurnMintTokenPool( + chain.DeployerKey, + chain.Client, + tokenContract.Address, + []common.Address{}, + common.HexToAddress(rmnAddress), + common.HexToAddress(routerAddress), + ) + return ContractDeploy[*burn_mint_token_pool.BurnMintTokenPool]{ + tokenPoolAddress, tokenPoolContract, tx, deployment.NewTypeAndVersion(BurnMintTokenPool, deployment.Version1_0_0), err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy token pool", "err", err) + return nil, nil, err + } + + return tokenContract.Contract, tokenPool.Contract, nil +} diff --git a/deployment/environment.go b/deployment/environment.go index a975042dee2..104db4c5c37 100644 --- a/deployment/environment.go +++ b/deployment/environment.go @@ -18,12 +18,11 @@ import ( types3 "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "google.golang.org/grpc" + "github.com/smartcontractkit/chainlink-common/pkg/logger" csav1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/csa" jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" nodev1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) diff --git a/integration-tests/smoke/ccip_rmn_test.go b/integration-tests/smoke/ccip_rmn_test.go index 53bf25a3ac5..76f69afd99e 100644 --- a/integration-tests/smoke/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip_rmn_test.go @@ -184,7 +184,7 @@ func TestRMN(t *testing.T) { require.NoError(t, err) block := latesthdr.Number.Uint64() startBlocks[dest] = &block - seqNum := ccipdeployment.TestSendRequest(t, e, onChainState, src, dest, false) + seqNum := ccipdeployment.TestSendRequest(t, e, onChainState, src, dest, false, nil) expectedSeqNum[dest] = seqNum } } diff --git a/integration-tests/smoke/ccip_test.go b/integration-tests/smoke/ccip_test.go index 8d429b5a772..686f2c10299 100644 --- a/integration-tests/smoke/ccip_test.go +++ b/integration-tests/smoke/ccip_test.go @@ -1,23 +1,22 @@ package smoke import ( + "math/big" "testing" "github.com/stretchr/testify/require" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" - "github.com/smartcontractkit/chainlink-ccip/pluginconfig" - + jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink/deployment" ccdeploy "github.com/smartcontractkit/chainlink/deployment/ccip" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testsetups" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/logger" - - jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" ) func TestInitialDeployOnLocal(t *testing.T) { @@ -84,7 +83,7 @@ func TestInitialDeployOnLocal(t *testing.T) { require.NoError(t, err) block := latesthdr.Number.Uint64() startBlocks[dest] = &block - seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false) + seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false, nil) expectedSeqNum[dest] = seqNum } } @@ -106,3 +105,150 @@ func TestInitialDeployOnLocal(t *testing.T) { // TODO: Apply the proposal. } + +func TestTokenTransfer(t *testing.T) { + lggr := logger.TestLogger(t) + ctx := ccdeploy.Context(t) + tenv, _, _ := testsetups.NewLocalDevEnvironment(t, lggr) + + e := tenv.Env + state, err := ccdeploy.LoadOnchainState(e) + require.NoError(t, err) + + feeds := state.Chains[tenv.FeedChainSel].USDFeeds + tokenConfig := ccdeploy.NewTokenConfig() + tokenConfig.UpsertTokenInfo(ccdeploy.LinkSymbol, + pluginconfig.TokenInfo{ + AggregatorAddress: cciptypes.UnknownEncodedAddress(feeds[ccdeploy.LinkSymbol].Address().String()), + Decimals: ccdeploy.LinkDecimals, + DeviationPPB: cciptypes.NewBigIntFromInt64(1e9), + }, + ) + + // Apply migration + output, err := changeset.InitialDeploy(e, ccdeploy.DeployCCIPContractConfig{ + HomeChainSel: tenv.HomeChainSel, + FeedChainSel: tenv.FeedChainSel, + ChainsToDeploy: e.AllChainSelectors(), + TokenConfig: tokenConfig, + MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e), + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + }) + require.NoError(t, err) + require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) + // Get new state after migration and mock USDC token deployment. + state, err = ccdeploy.LoadOnchainState(e) + require.NoError(t, err) + + srcToken, _, dstToken, _, err := ccdeploy.DeployTransferableToken( + lggr, + tenv.Env.Chains, + tenv.HomeChainSel, + tenv.FeedChainSel, + state, + e.ExistingAddresses, + "MY_TOKEN", + ) + require.NoError(t, err) + + // Ensure capreg logs are up to date. + ccdeploy.ReplayLogs(t, e.Offchain, tenv.ReplayBlocks) + + // Apply the jobs. + for nodeID, jobs := range output.JobSpecs { + for _, job := range jobs { + // Note these auto-accept + _, err := e.Offchain.ProposeJob(ctx, + &jobv1.ProposeJobRequest{ + NodeId: nodeID, + Spec: job, + }) + require.NoError(t, err) + } + } + + // Add all lanes + require.NoError(t, ccdeploy.AddLanesForAll(e, state)) + // 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) + // Send a message from each chain to every other chain. + expectedSeqNum := make(map[uint64]uint64) + + twoCoins := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(2)) + tx, err := srcToken.Mint( + e.Chains[tenv.HomeChainSel].DeployerKey, + e.Chains[tenv.HomeChainSel].DeployerKey.From, + new(big.Int).Mul(twoCoins, big.NewInt(10)), + ) + require.NoError(t, err) + _, err = e.Chains[tenv.HomeChainSel].Confirm(tx) + require.NoError(t, err) + + tx, err = dstToken.Mint( + e.Chains[tenv.FeedChainSel].DeployerKey, + e.Chains[tenv.FeedChainSel].DeployerKey.From, + new(big.Int).Mul(twoCoins, big.NewInt(10)), + ) + require.NoError(t, err) + _, err = e.Chains[tenv.FeedChainSel].Confirm(tx) + require.NoError(t, err) + + tx, err = srcToken.Approve(e.Chains[tenv.HomeChainSel].DeployerKey, state.Chains[tenv.HomeChainSel].Router.Address(), twoCoins) + require.NoError(t, err) + _, err = e.Chains[tenv.HomeChainSel].Confirm(tx) + require.NoError(t, err) + tx, err = dstToken.Approve(e.Chains[tenv.FeedChainSel].DeployerKey, state.Chains[tenv.FeedChainSel].Router.Address(), twoCoins) + require.NoError(t, err) + _, err = e.Chains[tenv.FeedChainSel].Confirm(tx) + require.NoError(t, err) + + tokens := map[uint64][]router.ClientEVMTokenAmount{ + tenv.HomeChainSel: {{ + Token: srcToken.Address(), + Amount: twoCoins, + }}, + tenv.FeedChainSel: {{ + Token: dstToken.Address(), + Amount: twoCoins, + }}, + } + + for src := range e.Chains { + for dest, destChain := range e.Chains { + if src == dest { + continue + } + latesthdr, err := destChain.Client.HeaderByNumber(testcontext.Get(t), nil) + require.NoError(t, err) + block := latesthdr.Number.Uint64() + startBlocks[dest] = &block + + if src == tenv.HomeChainSel && dest == tenv.FeedChainSel { + seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false, tokens[src]) + expectedSeqNum[dest] = seqNum + } else { + seqNum := ccdeploy.TestSendRequest(t, e, state, src, dest, false, nil) + expectedSeqNum[dest] = seqNum + } + } + } + + // Wait for all commit reports to land. + ccdeploy.ConfirmCommitForAllWithExpectedSeqNums(t, e, state, expectedSeqNum, startBlocks) + + // After commit is reported on all chains, token prices should be updated in FeeQuoter. + for dest := range e.Chains { + linkAddress := state.Chains[dest].LinkToken.Address() + feeQuoter := state.Chains[dest].FeeQuoter + timestampedPrice, err := feeQuoter.GetTokenPrice(nil, linkAddress) + require.NoError(t, err) + require.Equal(t, ccdeploy.MockLinkPrice, timestampedPrice.Value) + } + + // Wait for all exec reports to land + ccdeploy.ConfirmExecWithSeqNrForAll(t, e, state, expectedSeqNum, startBlocks) + + balance, err := dstToken.BalanceOf(nil, state.Chains[tenv.FeedChainSel].Receiver.Address()) + require.NoError(t, err) + require.Equal(t, twoCoins, balance) +} From 99241ec68098d818b97b046a1f9b4f5c8f0c5366 Mon Sep 17 00:00:00 2001 From: Juan Farber Date: Wed, 6 Nov 2024 09:30:10 -0300 Subject: [PATCH 11/11] [NONEVM-714] [core] - Add configs to support Solana MultipleBlocksEstimator (#15029) * configs nonevm multiple blocks * missing tidy * add changeset * fix config md * bump solana ref * keep blockHistorySize near BlockHistoryPollPeriod * sol bump * put vars close in config * fix docs * call out interval to gauge load on nodes * call out bis * bump sol ref --- .changeset/neat-singers-notice.md | 5 +++++ core/config/docs/chains-solana.toml | 5 +++++ core/scripts/go.mod | 2 +- core/scripts/go.sum | 4 ++-- core/services/chainlink/config_test.go | 2 ++ core/services/chainlink/testdata/config-full.toml | 1 + .../testdata/config-multi-chain-effective.toml | 2 ++ core/web/resolver/testdata/config-full.toml | 1 + .../testdata/config-multi-chain-effective.toml | 2 ++ core/web/solana_chains_controller_test.go | 1 + deployment/go.mod | 2 +- deployment/go.sum | 4 ++-- docs/CONFIG.md | 10 ++++++++++ go.mod | 2 +- go.sum | 4 ++-- integration-tests/go.mod | 2 +- integration-tests/go.sum | 4 ++-- integration-tests/load/go.mod | 2 +- integration-tests/load/go.sum | 4 ++-- 19 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 .changeset/neat-singers-notice.md diff --git a/.changeset/neat-singers-notice.md b/.changeset/neat-singers-notice.md new file mode 100644 index 00000000000..6eff83c0c15 --- /dev/null +++ b/.changeset/neat-singers-notice.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +Added multiple blocks history estimator feature and config for Solana TXM. #added diff --git a/core/config/docs/chains-solana.toml b/core/config/docs/chains-solana.toml index 626c2f0613f..87d71b49cc6 100644 --- a/core/config/docs/chains-solana.toml +++ b/core/config/docs/chains-solana.toml @@ -36,6 +36,11 @@ ComputeUnitPriceDefault = 0 # Default FeeBumpPeriod = '3s' # Default # BlockHistoryPollPeriod is the rate to poll for blocks in the block history fee estimator BlockHistoryPollPeriod = '5s' # Default +# BlockHistorySize is the number of blocks to take into consideration when using FeeEstimatorMode = 'blockhistory' to determine compute unit price. +# If set to 1, the compute unit price will be determined by the median of the last block's compute unit prices. +# If set N > 1, the compute unit price will be determined by the average of the medians of the last N blocks' compute unit prices. +# DISCLAIMER: 1:1 ratio between n and RPC calls. It executes once every 'BlockHistoryPollPeriod' value. +BlockHistorySize = 1 # Default # ComputeUnitLimitDefault is the compute units limit applied to transactions unless overriden during the txm enqueue ComputeUnitLimitDefault = 200_000 # Default # EstimateComputeUnitLimit enables or disables compute unit limit estimations per transaction. If estimations return 0 used compute, the ComputeUnitLimitDefault value is used, if set. diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 0392bfab5c2..651561b7943 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -294,7 +294,7 @@ require ( github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect github.com/smartcontractkit/chainlink-protos/job-distributor v0.4.0 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 // indirect - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241024132041-a3eb2e31b4c4 // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241104202120-39cabce465f6 // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20241009055228-33d0c0bf38de // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 3e5bc90f90f..bc03d5aefec 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1104,8 +1104,8 @@ github.com/smartcontractkit/chainlink-protos/job-distributor v0.4.0 h1:1xTm8UGeD github.com/smartcontractkit/chainlink-protos/job-distributor v0.4.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241024132041-a3eb2e31b4c4 h1:ZUihu/AMiFkZgO5XkVcpFayhIUibdovHzpbHnMPZUr0= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241024132041-a3eb2e31b4c4/go.mod h1:iZugccCLpPWtcGiR/8gurre2j3RtyKnqd1FcVR0NzQw= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241104202120-39cabce465f6 h1:YsE0uS6S10oAWnFbjNDc7tN9JrWYjvyqMnTSbTSgl00= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241104202120-39cabce465f6/go.mod h1:iZugccCLpPWtcGiR/8gurre2j3RtyKnqd1FcVR0NzQw= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 h1:B4DFdk6MGcQnoCjjMBCx7Z+GWQpxRWJ4O8W/dVJyWGA= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8/go.mod h1:WkBqgBo+g34Gm5vWkDDl8Fh3Mzd7bF5hXp7rryg0t5o= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index f5a3aaa7b59..c6dd58369d0 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -752,6 +752,7 @@ func TestConfig_Marshal(t *testing.T) { ComputeUnitPriceDefault: ptr[uint64](100), FeeBumpPeriod: commoncfg.MustNewDuration(time.Minute), BlockHistoryPollPeriod: commoncfg.MustNewDuration(time.Minute), + BlockHistorySize: ptr[uint64](1), ComputeUnitLimitDefault: ptr[uint32](100_000), EstimateComputeUnitLimit: ptr(false), }, @@ -1278,6 +1279,7 @@ ComputeUnitPriceMin = 10 ComputeUnitPriceDefault = 100 FeeBumpPeriod = '1m0s' BlockHistoryPollPeriod = '1m0s' +BlockHistorySize = 1 ComputeUnitLimitDefault = 100000 EstimateComputeUnitLimit = false diff --git a/core/services/chainlink/testdata/config-full.toml b/core/services/chainlink/testdata/config-full.toml index 44362761bd1..f6cb497f7c8 100644 --- a/core/services/chainlink/testdata/config-full.toml +++ b/core/services/chainlink/testdata/config-full.toml @@ -499,6 +499,7 @@ ComputeUnitPriceMin = 10 ComputeUnitPriceDefault = 100 FeeBumpPeriod = '1m0s' BlockHistoryPollPeriod = '1m0s' +BlockHistorySize = 1 ComputeUnitLimitDefault = 100000 EstimateComputeUnitLimit = false diff --git a/core/services/chainlink/testdata/config-multi-chain-effective.toml b/core/services/chainlink/testdata/config-multi-chain-effective.toml index 86612f05798..4d25d23c333 100644 --- a/core/services/chainlink/testdata/config-multi-chain-effective.toml +++ b/core/services/chainlink/testdata/config-multi-chain-effective.toml @@ -660,6 +660,7 @@ ComputeUnitPriceMin = 0 ComputeUnitPriceDefault = 0 FeeBumpPeriod = '3s' BlockHistoryPollPeriod = '5s' +BlockHistorySize = 1 ComputeUnitLimitDefault = 200000 EstimateComputeUnitLimit = false @@ -703,6 +704,7 @@ ComputeUnitPriceMin = 0 ComputeUnitPriceDefault = 0 FeeBumpPeriod = '3s' BlockHistoryPollPeriod = '5s' +BlockHistorySize = 1 ComputeUnitLimitDefault = 200000 EstimateComputeUnitLimit = false diff --git a/core/web/resolver/testdata/config-full.toml b/core/web/resolver/testdata/config-full.toml index ebdafb22c5e..8fbe07f97f9 100644 --- a/core/web/resolver/testdata/config-full.toml +++ b/core/web/resolver/testdata/config-full.toml @@ -498,6 +498,7 @@ ComputeUnitPriceMin = 0 ComputeUnitPriceDefault = 0 FeeBumpPeriod = '3s' BlockHistoryPollPeriod = '5s' +BlockHistorySize = 1 ComputeUnitLimitDefault = 200000 EstimateComputeUnitLimit = false diff --git a/core/web/resolver/testdata/config-multi-chain-effective.toml b/core/web/resolver/testdata/config-multi-chain-effective.toml index 2a0ebcf1039..adc1394e654 100644 --- a/core/web/resolver/testdata/config-multi-chain-effective.toml +++ b/core/web/resolver/testdata/config-multi-chain-effective.toml @@ -660,6 +660,7 @@ ComputeUnitPriceMin = 0 ComputeUnitPriceDefault = 0 FeeBumpPeriod = '3s' BlockHistoryPollPeriod = '5s' +BlockHistorySize = 1 ComputeUnitLimitDefault = 200000 EstimateComputeUnitLimit = false @@ -703,6 +704,7 @@ ComputeUnitPriceMin = 0 ComputeUnitPriceDefault = 0 FeeBumpPeriod = '3s' BlockHistoryPollPeriod = '5s' +BlockHistorySize = 1 ComputeUnitLimitDefault = 200000 EstimateComputeUnitLimit = false diff --git a/core/web/solana_chains_controller_test.go b/core/web/solana_chains_controller_test.go index 148d6302592..56605f734aa 100644 --- a/core/web/solana_chains_controller_test.go +++ b/core/web/solana_chains_controller_test.go @@ -58,6 +58,7 @@ ComputeUnitPriceMin = 0 ComputeUnitPriceDefault = 0 FeeBumpPeriod = '3s' BlockHistoryPollPeriod = '5s' +BlockHistorySize = 1 ComputeUnitLimitDefault = 200000 EstimateComputeUnitLimit = false Nodes = [] diff --git a/deployment/go.mod b/deployment/go.mod index 4f5d3e95624..351f3d61ac6 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -397,7 +397,7 @@ require ( github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 // indirect - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241024132041-a3eb2e31b4c4 // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241104202120-39cabce465f6 // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 // indirect github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 // indirect github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.5 // indirect diff --git a/deployment/go.sum b/deployment/go.sum index 0607d6161fd..a3efb492a52 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -1396,8 +1396,8 @@ github.com/smartcontractkit/chainlink-protos/job-distributor v0.4.0 h1:1xTm8UGeD github.com/smartcontractkit/chainlink-protos/job-distributor v0.4.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241024132041-a3eb2e31b4c4 h1:ZUihu/AMiFkZgO5XkVcpFayhIUibdovHzpbHnMPZUr0= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241024132041-a3eb2e31b4c4/go.mod h1:iZugccCLpPWtcGiR/8gurre2j3RtyKnqd1FcVR0NzQw= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241104202120-39cabce465f6 h1:YsE0uS6S10oAWnFbjNDc7tN9JrWYjvyqMnTSbTSgl00= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241104202120-39cabce465f6/go.mod h1:iZugccCLpPWtcGiR/8gurre2j3RtyKnqd1FcVR0NzQw= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 h1:B4DFdk6MGcQnoCjjMBCx7Z+GWQpxRWJ4O8W/dVJyWGA= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8/go.mod h1:WkBqgBo+g34Gm5vWkDDl8Fh3Mzd7bF5hXp7rryg0t5o= github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.13 h1:T0kbw07Vb6xUyA9MIJZfErMgWseWi1zf7cYvRpoq7ug= diff --git a/docs/CONFIG.md b/docs/CONFIG.md index b28a83d1dfa..8e872a458e6 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -10064,6 +10064,7 @@ ComputeUnitPriceMin = 0 # Default ComputeUnitPriceDefault = 0 # Default FeeBumpPeriod = '3s' # Default BlockHistoryPollPeriod = '5s' # Default +BlockHistorySize = 1 # Default ComputeUnitLimitDefault = 200_000 # Default EstimateComputeUnitLimit = false # Default ``` @@ -10178,6 +10179,15 @@ BlockHistoryPollPeriod = '5s' # Default ``` BlockHistoryPollPeriod is the rate to poll for blocks in the block history fee estimator +### BlockHistorySize +```toml +BlockHistorySize = 1 # Default +``` +BlockHistorySize is the number of blocks to take into consideration when using FeeEstimatorMode = 'blockhistory' to determine compute unit price. +If set to 1, the compute unit price will be determined by the median of the last block's compute unit prices. +If set N > 1, the compute unit price will be determined by the average of the medians of the last N blocks' compute unit prices. +DISCLAIMER: 1:1 ratio between n and RPC calls. It executes once every 'BlockHistoryPollPeriod' value. + ### ComputeUnitLimitDefault ```toml ComputeUnitLimitDefault = 200_000 # Default diff --git a/go.mod b/go.mod index cd089a62e6d..590b0ea19f9 100644 --- a/go.mod +++ b/go.mod @@ -81,7 +81,7 @@ require ( github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e github.com/smartcontractkit/chainlink-feeds v0.1.1 github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241024132041-a3eb2e31b4c4 + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241104202120-39cabce465f6 github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20241009055228-33d0c0bf38de diff --git a/go.sum b/go.sum index fceb95c5efb..8dd8b8f6066 100644 --- a/go.sum +++ b/go.sum @@ -1087,8 +1087,8 @@ github.com/smartcontractkit/chainlink-feeds v0.1.1 h1:JzvUOM/OgGQA1sOqTXXl52R6An github.com/smartcontractkit/chainlink-feeds v0.1.1/go.mod h1:55EZ94HlKCfAsUiKUTNI7QlE/3d3IwTlsU3YNa/nBb4= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241024132041-a3eb2e31b4c4 h1:ZUihu/AMiFkZgO5XkVcpFayhIUibdovHzpbHnMPZUr0= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241024132041-a3eb2e31b4c4/go.mod h1:iZugccCLpPWtcGiR/8gurre2j3RtyKnqd1FcVR0NzQw= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241104202120-39cabce465f6 h1:YsE0uS6S10oAWnFbjNDc7tN9JrWYjvyqMnTSbTSgl00= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241104202120-39cabce465f6/go.mod h1:iZugccCLpPWtcGiR/8gurre2j3RtyKnqd1FcVR0NzQw= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 h1:B4DFdk6MGcQnoCjjMBCx7Z+GWQpxRWJ4O8W/dVJyWGA= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8/go.mod h1:WkBqgBo+g34Gm5vWkDDl8Fh3Mzd7bF5hXp7rryg0t5o= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index b223f729e8b..b7f66862f39 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -412,7 +412,7 @@ require ( github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 // indirect - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241024132041-a3eb2e31b4c4 // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241104202120-39cabce465f6 // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20241009055228-33d0c0bf38de // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index df4ad771076..51b058ce717 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1417,8 +1417,8 @@ github.com/smartcontractkit/chainlink-protos/job-distributor v0.4.0 h1:1xTm8UGeD github.com/smartcontractkit/chainlink-protos/job-distributor v0.4.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241024132041-a3eb2e31b4c4 h1:ZUihu/AMiFkZgO5XkVcpFayhIUibdovHzpbHnMPZUr0= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241024132041-a3eb2e31b4c4/go.mod h1:iZugccCLpPWtcGiR/8gurre2j3RtyKnqd1FcVR0NzQw= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241104202120-39cabce465f6 h1:YsE0uS6S10oAWnFbjNDc7tN9JrWYjvyqMnTSbTSgl00= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241104202120-39cabce465f6/go.mod h1:iZugccCLpPWtcGiR/8gurre2j3RtyKnqd1FcVR0NzQw= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 h1:B4DFdk6MGcQnoCjjMBCx7Z+GWQpxRWJ4O8W/dVJyWGA= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8/go.mod h1:WkBqgBo+g34Gm5vWkDDl8Fh3Mzd7bF5hXp7rryg0t5o= github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 h1:GDGrC5OGiV0RyM1znYWehSQXyZQWTOzrEeJRYmysPCE= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 2cbbda250fd..c8aa31650eb 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -418,7 +418,7 @@ require ( github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 // indirect - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241024132041-a3eb2e31b4c4 // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241104202120-39cabce465f6 // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 // indirect github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 // indirect github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index f6c81e4c500..b3be3423acc 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1404,8 +1404,8 @@ github.com/smartcontractkit/chainlink-feeds v0.1.1 h1:JzvUOM/OgGQA1sOqTXXl52R6An github.com/smartcontractkit/chainlink-feeds v0.1.1/go.mod h1:55EZ94HlKCfAsUiKUTNI7QlE/3d3IwTlsU3YNa/nBb4= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241024132041-a3eb2e31b4c4 h1:ZUihu/AMiFkZgO5XkVcpFayhIUibdovHzpbHnMPZUr0= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241024132041-a3eb2e31b4c4/go.mod h1:iZugccCLpPWtcGiR/8gurre2j3RtyKnqd1FcVR0NzQw= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241104202120-39cabce465f6 h1:YsE0uS6S10oAWnFbjNDc7tN9JrWYjvyqMnTSbTSgl00= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241104202120-39cabce465f6/go.mod h1:iZugccCLpPWtcGiR/8gurre2j3RtyKnqd1FcVR0NzQw= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 h1:B4DFdk6MGcQnoCjjMBCx7Z+GWQpxRWJ4O8W/dVJyWGA= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8/go.mod h1:WkBqgBo+g34Gm5vWkDDl8Fh3Mzd7bF5hXp7rryg0t5o= github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 h1:GDGrC5OGiV0RyM1znYWehSQXyZQWTOzrEeJRYmysPCE=