Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
423 changes: 186 additions & 237 deletions deployment/common/changeset/deploy_mcms_with_timelock_test.go

Large diffs are not rendered by default.

243 changes: 116 additions & 127 deletions deployment/common/changeset/example/solana_transfer_mcm_test.go
Original file line number Diff line number Diff line change
@@ -1,115 +1,78 @@
package example_test

import (
"crypto/ecdsa"
"fmt"
"testing"
"time"

"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
chainselectors "github.com/smartcontractkit/chain-selectors"
mcmsSolana "github.com/smartcontractkit/mcms/sdk/solana"
"github.com/smartcontractkit/quarantine"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"

cldf_solana "github.com/smartcontractkit/chainlink-deployments-framework/chain/solana"
"github.com/smartcontractkit/quarantine"
"github.com/smartcontractkit/chainlink-common/pkg/logger"

cldf_chain "github.com/smartcontractkit/chainlink-deployments-framework/chain"

chainselectors "github.com/smartcontractkit/chain-selectors"
mcmsSolana "github.com/smartcontractkit/mcms/sdk/solana"

cldf_solana "github.com/smartcontractkit/chainlink-deployments-framework/chain/solana"
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
"github.com/smartcontractkit/chainlink-deployments-framework/engine/test/environment"
"github.com/smartcontractkit/chainlink-deployments-framework/engine/test/runtime"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"

"github.com/smartcontractkit/chainlink/deployment/common/changeset"
"github.com/smartcontractkit/chainlink/deployment/common/changeset/example"
"github.com/smartcontractkit/chainlink/deployment/common/changeset/state"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
"github.com/smartcontractkit/chainlink/deployment/common/types"
"github.com/smartcontractkit/chainlink/deployment/environment/memory"
"github.com/smartcontractkit/chainlink/v2/core/logger"
"github.com/smartcontractkit/chainlink/deployment/internal/soltestutils"
"github.com/smartcontractkit/chainlink/deployment/internal/solutils"
)

// setupFundingTestEnv deploys all required contracts for the funding test
func setupFundingTestEnv(t *testing.T) cldf.Environment {
lggr := logger.TestLogger(t)
cfg := memory.MemoryEnvironmentConfig{
SolChains: 1,
}
env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg)
chainSelector := env.BlockChains.ListChainSelectors(cldf_chain.WithFamily(chainselectors.FamilySolana))[0]

config := proposalutils.SingleGroupTimelockConfigV2(t)
err := testhelpers.SavePreloadedSolAddresses(env, chainSelector)
require.NoError(t, err)
// Initialize the address book with a dummy address to avoid deploy precondition errors.
err = env.ExistingAddresses.Save(chainSelector, "dummyAddress", cldf.TypeAndVersion{Type: "dummy", Version: deployment.Version1_0_0})
require.NoError(t, err)

// Deploy MCMS and Timelock
env, err = changeset.Apply(t, env,
changeset.Configure(
cldf.CreateLegacyChangeSet(changeset.DeployMCMSWithTimelockV2),
map[uint64]types.MCMSWithTimelockConfigV2{
chainSelector: config,
},
),
)
require.NoError(t, err)

return env
}

func TestTransferFromTimelockConfig_VerifyPreconditions(t *testing.T) {
t.Parallel()
lggr := logger.TestLogger(t)
validEnv := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{SolChains: 1})
validSolChainSelector := validEnv.BlockChains.ListChainSelectors(cldf_chain.WithFamily(chainselectors.FamilySolana))[0]

receiverKey := solana.NewWallet().PublicKey()
cs := example.TransferFromTimelock{}
selector := chainselectors.TEST_22222222222222222222222222222222222222222222.Selector

// Save the timelock contract address to the address book
ab := cldf.NewMemoryAddressBook()
timelockID := mcmsSolana.ContractAddress(
solana.NewWallet().PublicKey(),
[32]byte{'t', 'e', 's', 't'},
)
err := validEnv.ExistingAddresses.Save(validSolChainSelector, timelockID, cldf.TypeAndVersion{
require.NoError(t, ab.Save(selector, timelockID, cldf.TypeAndVersion{
Type: types.RBACTimelock,
Version: deployment.Version1_0_0,
})
require.NoError(t, err)
}))

// Create an environment that simulates a chain where the MCMS contracts have not been deployed,
// e.g. missing the required addresses so that the state loader returns empty seeds.
noTimelockEnv := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{})
noTimelockEnv.BlockChains = cldf_chain.NewBlockChains(map[uint64]cldf_chain.BlockChain{
chainselectors.SOLANA_DEVNET.Selector: cldf_solana.Chain{},
})
err = noTimelockEnv.ExistingAddresses.Save(chainselectors.SOLANA_DEVNET.Selector, "dummy", cldf.TypeAndVersion{
Type: "Sometype",
Version: deployment.Version1_0_0,
})
env, err := environment.New(t.Context(),
environment.WithSolanaContainer(t, []uint64{selector}, t.TempDir(), map[string]string{}),
environment.WithAddressBook(ab),
environment.WithLogger(logger.Test(t)),
)
require.NoError(t, err)

// Create an environment with a Solana chain that has an invalid (zero) underlying chain.
invalidSolChainEnv := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{
SolChains: 0,
})
invalidSolChainEnv.BlockChains = cldf_chain.NewBlockChains(map[uint64]cldf_chain.BlockChain{
validSolChainSelector: cldf_solana.Chain{},
// Create an environment with a Solana chain that has an invalid (zero value) underlying chain.
invalidEnv, err := environment.New(t.Context())
require.NoError(t, err)
invalidEnv.BlockChains = cldf_chain.NewBlockChains(map[uint64]cldf_chain.BlockChain{
selector: cldf_solana.Chain{},
})

tests := []struct {
name string
env cldf.Environment
env func(t *testing.T) cldf.Environment
config example.TransferFromTimelockConfig
expectedError string
}{
{
name: "All preconditions satisfied",
env: validEnv,
env: func(t *testing.T) cldf.Environment { t.Helper(); return *env },
config: example.TransferFromTimelockConfig{
AmountsPerChain: map[uint64]example.TransferData{validSolChainSelector: {
AmountsPerChain: map[uint64]example.TransferData{selector: {
Amount: 100,
To: receiverKey,
}},
Expand All @@ -118,34 +81,56 @@ func TestTransferFromTimelockConfig_VerifyPreconditions(t *testing.T) {
},
{
name: "No Solana chains found in environment",
env: memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{
Bootstraps: 1,
Chains: 1,
SolChains: 0,
Nodes: 1,
}),
env: func(t *testing.T) cldf.Environment {
t.Helper()

emptyEnv, gerr := environment.New(t.Context())
require.NoError(t, gerr)

return *emptyEnv
},
config: example.TransferFromTimelockConfig{
AmountsPerChain: map[uint64]example.TransferData{validSolChainSelector: {
AmountsPerChain: map[uint64]example.TransferData{selector: {
Amount: 100,
To: receiverKey,
}},
},
expectedError: fmt.Sprintf("solana chain not found for selector %d", validSolChainSelector),
expectedError: fmt.Sprintf("solana chain not found for selector %d", selector),
},
{
name: "Chain selector not found in environment",
env: validEnv,
config: example.TransferFromTimelockConfig{AmountsPerChain: map[uint64]example.TransferData{99999: {
Amount: 100,
To: receiverKey,
}}},
env: func(t *testing.T) cldf.Environment { t.Helper(); return *env },
config: example.TransferFromTimelockConfig{
AmountsPerChain: map[uint64]example.TransferData{99999: {
Amount: 100,
To: receiverKey,
}}},
expectedError: "solana chain not found for selector 99999",
},
{
name: "timelock contracts not deployed (empty seeds)",
env: noTimelockEnv,
env: func(t *testing.T) cldf.Environment {
t.Helper()

// Create an environment that simulates a chain where the MCMS contracts have not been deployed,
// e.g. missing the required addresses so that the state loader returns empty seeds.
emptyEnv, gerr := environment.New(t.Context())

require.NoError(t, gerr)

emptyEnv.BlockChains = cldf_chain.NewBlockChainsFromSlice([]cldf_chain.BlockChain{
cldf_solana.Chain{Selector: selector},
})
gerr = emptyEnv.ExistingAddresses.Save(selector, "dummy", cldf.TypeAndVersion{
Type: "Sometype",
Version: deployment.Version1_0_0,
})
require.NoError(t, gerr)

return *emptyEnv
},
config: example.TransferFromTimelockConfig{
AmountsPerChain: map[uint64]example.TransferData{chainselectors.SOLANA_DEVNET.Selector: {
AmountsPerChain: map[uint64]example.TransferData{selector: {
Amount: 100,
To: receiverKey,
}},
Expand All @@ -154,23 +139,10 @@ func TestTransferFromTimelockConfig_VerifyPreconditions(t *testing.T) {
},
{
name: "Insufficient deployer balance",
env: validEnv,
config: example.TransferFromTimelockConfig{
AmountsPerChain: map[uint64]example.TransferData{
validSolChainSelector: {
Amount: 999999999999999999,
To: receiverKey,
},
},
},
expectedError: "deployer balance is insufficient",
},
{
name: "Insufficient deployer balance",
env: validEnv,
env: func(t *testing.T) cldf.Environment { t.Helper(); return *env },
config: example.TransferFromTimelockConfig{
AmountsPerChain: map[uint64]example.TransferData{
validSolChainSelector: {
selector: {
Amount: 999999999999999999,
To: receiverKey,
},
Expand All @@ -180,9 +152,9 @@ func TestTransferFromTimelockConfig_VerifyPreconditions(t *testing.T) {
},
{
name: "Invalid Solana chain in environment",
env: invalidSolChainEnv,
env: func(t *testing.T) cldf.Environment { t.Helper(); return *invalidEnv },
config: example.TransferFromTimelockConfig{
AmountsPerChain: map[uint64]example.TransferData{validSolChainSelector: {
AmountsPerChain: map[uint64]example.TransferData{selector: {
Amount: 100,
To: receiverKey,
}},
Expand All @@ -191,9 +163,9 @@ func TestTransferFromTimelockConfig_VerifyPreconditions(t *testing.T) {
},
{
name: "empty from field",
env: invalidSolChainEnv,
env: func(t *testing.T) cldf.Environment { t.Helper(); return *invalidEnv },
config: example.TransferFromTimelockConfig{
AmountsPerChain: map[uint64]example.TransferData{validSolChainSelector: {
AmountsPerChain: map[uint64]example.TransferData{selector: {
Amount: 100,
To: solana.PublicKey{},
}},
Expand All @@ -204,7 +176,7 @@ func TestTransferFromTimelockConfig_VerifyPreconditions(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := cs.VerifyPreconditions(tt.env, tt.config)
err := example.TransferFromTimelock{}.VerifyPreconditions(tt.env(t), tt.config)
if tt.expectedError == "" {
require.NoError(t, err)
} else {
Expand All @@ -218,40 +190,57 @@ func TestTransferFromTimelockConfig_VerifyPreconditions(t *testing.T) {
func TestTransferFromTimelockConfig_Apply(t *testing.T) {
quarantine.Flaky(t, "DX-1754")
t.Parallel()
env := setupFundingTestEnv(t)
cfgAmounts := example.TransferData{
Amount: 100 * solana.LAMPORTS_PER_SOL,
To: solana.NewWallet().PublicKey(),
}
amountsPerChain := make(map[uint64]example.TransferData)
solChains := env.BlockChains.SolanaChains()
for chainSelector := range solChains {
amountsPerChain[chainSelector] = cfgAmounts
}
config := example.TransferFromTimelockConfig{
TimelockCfg: proposalutils.TimelockConfig{MinDelay: 1 * time.Second},
AmountsPerChain: amountsPerChain,
}
solChainSelector := env.BlockChains.ListChainSelectors(cldf_chain.WithFamily(chainselectors.FamilySolana))[0]
addresses, err := env.ExistingAddresses.AddressesForChain(solChainSelector)

selector := chainselectors.TEST_22222222222222222222222222222222222222222222.Selector
programsPath, programIDs, ab := soltestutils.PreloadMCMS(t, selector)

// Initialize the address book with a dummy address to avoid deploy precondition errors.
err := ab.Save(selector, "dummyAddress", cldf.TypeAndVersion{Type: "dummy", Version: deployment.Version1_0_0})
require.NoError(t, err)
mcmState, err := state.MaybeLoadMCMSWithTimelockChainStateSolana(solChains[solChainSelector], addresses)

rt, err := runtime.New(t.Context(), runtime.WithEnvOpts(
environment.WithSolanaContainer(t, []uint64{selector}, programsPath, programIDs),
environment.WithAddressBook(ab),
environment.WithLogger(logger.Test(t)),
))
require.NoError(t, err)

chain := rt.Environment().BlockChains.SolanaChains()[selector]

// Deploy MCMS and Timelock
err = rt.Exec(
runtime.ChangesetTask(cldf.CreateLegacyChangeSet(changeset.DeployMCMSWithTimelockV2), map[uint64]types.MCMSWithTimelockConfigV2{
selector: proposalutils.SingleGroupTimelockConfigV2(t),
}),
)
require.NoError(t, err)

// Fund the signer PDAs for the MCMS contracts
mcmState := soltestutils.GetMCMSStateFromAddressBook(t, rt.State().AddressBook, chain)
timelockSigner := state.GetTimelockSignerPDA(mcmState.TimelockProgram, mcmState.TimelockSeed)
mcmSigner := state.GetMCMSignerPDA(mcmState.McmProgram, mcmState.ProposerMcmSeed)
chainSelector := env.BlockChains.ListChainSelectors(cldf_chain.WithFamily(chainselectors.FamilySolana))[0]
solChain := solChains[chainSelector]
err = memory.FundSolanaAccounts(env.GetContext(), []solana.PublicKey{timelockSigner, mcmSigner, solChain.DeployerKey.PublicKey()}, 150, solChain.Client)

err = solutils.FundAccounts(t.Context(), chain.Client, []solana.PublicKey{timelockSigner, mcmSigner, chain.DeployerKey.PublicKey()}, 150)
require.NoError(t, err)

changesetInstance := example.TransferFromTimelock{}
// Execute the transfer from timelock changeset
cfgAmounts := example.TransferData{
Amount: 100 * solana.LAMPORTS_PER_SOL,
To: solana.NewWallet().PublicKey(),
}

env, _, err = changeset.ApplyChangesets(t, env, []changeset.ConfiguredChangeSet{
changeset.Configure(changesetInstance, config),
})
err = rt.Exec(
runtime.ChangesetTask(example.TransferFromTimelock{}, example.TransferFromTimelockConfig{
TimelockCfg: proposalutils.TimelockConfig{MinDelay: 1 * time.Second},
AmountsPerChain: map[uint64]example.TransferData{
selector: cfgAmounts,
},
}),
runtime.SignAndExecuteProposalsTask([]*ecdsa.PrivateKey{proposalutils.TestXXXMCMSSigner}),
)
require.NoError(t, err)

balance, err := solChain.Client.GetBalance(env.GetContext(), cfgAmounts.To, rpc.CommitmentConfirmed)
balance, err := chain.Client.GetBalance(t.Context(), cfgAmounts.To, rpc.CommitmentConfirmed)
require.NoError(t, err)
t.Logf("Account: %s, Balance: %d", cfgAmounts.To, balance.Value)

Expand Down
Loading
Loading