From 657135d8826008571a014a7a57e33c970172bdf1 Mon Sep 17 00:00:00 2001 From: Jade Park Date: Wed, 8 Jan 2025 16:20:33 +0900 Subject: [PATCH] chore: timelock multiple instance test setup --- ...test.go => mcm_multiple_instances_test.go} | 0 .../mcms/timelock_bypasser_execute_test.go | 2 +- .../mcms/timelock_multiple_instances_test.go | 213 ++++++++++++++++++ .../tests/mcms/timelock_rbac_test.go | 2 +- .../mcms/timelock_schedule_execute_test.go | 2 +- 5 files changed, 216 insertions(+), 3 deletions(-) rename chains/solana/contracts/tests/mcms/{mcm_multiple_multisigs_test.go => mcm_multiple_instances_test.go} (100%) create mode 100644 chains/solana/contracts/tests/mcms/timelock_multiple_instances_test.go diff --git a/chains/solana/contracts/tests/mcms/mcm_multiple_multisigs_test.go b/chains/solana/contracts/tests/mcms/mcm_multiple_instances_test.go similarity index 100% rename from chains/solana/contracts/tests/mcms/mcm_multiple_multisigs_test.go rename to chains/solana/contracts/tests/mcms/mcm_multiple_instances_test.go diff --git a/chains/solana/contracts/tests/mcms/timelock_bypasser_execute_test.go b/chains/solana/contracts/tests/mcms/timelock_bypasser_execute_test.go index ecaa0e42..99a12bc7 100644 --- a/chains/solana/contracts/tests/mcms/timelock_bypasser_execute_test.go +++ b/chains/solana/contracts/tests/mcms/timelock_bypasser_execute_test.go @@ -82,7 +82,7 @@ func TestTimelockBypasserExecute(t *testing.T) { } }) - t.Run("setup:init timelock program", func(t *testing.T) { + t.Run("setup:initialize timelock instance", func(t *testing.T) { // get program data account data, accErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.TimelockProgram, &rpc.GetAccountInfoOpts{ Commitment: config.DefaultCommitment, diff --git a/chains/solana/contracts/tests/mcms/timelock_multiple_instances_test.go b/chains/solana/contracts/tests/mcms/timelock_multiple_instances_test.go new file mode 100644 index 00000000..e66353a0 --- /dev/null +++ b/chains/solana/contracts/tests/mcms/timelock_multiple_instances_test.go @@ -0,0 +1,213 @@ +package contracts + +import ( + "testing" + + bin "github.com/gagliardetto/binary" + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/config" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/testutils" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/access_controller" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/timelock" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/accesscontroller" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/common" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/mcms" + timelockutil "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/timelock" +) + +func TestTimelockMultipleInstances(t *testing.T) { + t.Parallel() + ctx := tests.Context(t) + + timelock.SetProgramID(config.TimelockProgram) + access_controller.SetProgramID(config.AccessControllerProgram) + + deployer, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + + solanaGoClient := testutils.DeployAllPrograms(t, testutils.PathToAnchorConfig, deployer) + + type TimelockInstance struct { + ID [32]byte + Admin solana.PrivateKey + ConfigPDA solana.PublicKey + SignerPDA solana.PublicKey + Roles []timelockutil.RoleAccounts + RoleMap timelockutil.RoleMap + } + + admin1, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + + admin2, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + + testTimelockID1, err := mcms.PadString32("test_timelock_instance_1") + require.NoError(t, err) + + testTimelockID2, err := mcms.PadString32("test_timelock_instance_2") + require.NoError(t, err) + + roles1, roleMap1 := timelockutil.TestRoleAccounts(1) + roles2, roleMap2 := timelockutil.TestRoleAccounts(1) + + timelockInstances := []TimelockInstance{ + { + ID: testTimelockID1, + Admin: admin1, + ConfigPDA: timelockutil.GetConfigPDA(testTimelockID1), + SignerPDA: timelockutil.GetSignerPDA(testTimelockID1), + Roles: roles1, + RoleMap: roleMap1, + }, + { + ID: testTimelockID2, + Admin: admin2, + ConfigPDA: timelockutil.GetConfigPDA(testTimelockID2), + SignerPDA: timelockutil.GetSignerPDA(testTimelockID2), + Roles: roles2, + RoleMap: roleMap2, + }, + } + + t.Run("setup:funding", func(t *testing.T) { + all := []solana.PrivateKey{} + all = append(all, deployer) + for _, instance := range timelockInstances { + all = append(all, instance.Admin) + for _, role := range instance.Roles { + all = append(all, role.Accounts...) + } + } + testutils.FundAccounts(ctx, all, solanaGoClient, t) + }) + + t.Run("setup:init access controllers", func(t *testing.T) { + for _, instance := range timelockInstances { + for _, data := range instance.RoleMap { + initAccIxs, ierr := timelockutil.GetInitAccessControllersIxs(ctx, data.AccessController.PublicKey(), instance.Admin, solanaGoClient) + require.NoError(t, ierr) + + testutils.SendAndConfirm(ctx, t, solanaGoClient, initAccIxs, instance.Admin, config.DefaultCommitment, common.AddSigners(data.AccessController)) + + var ac access_controller.AccessController + err = common.GetAccountDataBorshInto(ctx, solanaGoClient, data.AccessController.PublicKey(), config.DefaultCommitment, &ac) + if err != nil { + require.NoError(t, err, "failed to get account info") + } + } + } + }) + + t.Run("setup:init timelock instances", func(t *testing.T) { + // get program data account + data, accErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.TimelockProgram, &rpc.GetAccountInfoOpts{ + Commitment: config.DefaultCommitment, + }) + require.NoError(t, accErr) + + // decode program data + var programData struct { + DataType uint32 + Address solana.PublicKey + } + require.NoError(t, bin.UnmarshalBorsh(&programData, data.Bytes())) + + for _, instance := range timelockInstances { + initTimelockIx, ierr := timelock.NewInitializeInstruction( + instance.ID, + config.MinDelay, + instance.ConfigPDA, + deployer.PublicKey(), // deployer is the owner of the timelock program initially + solana.SystemProgramID, + config.TimelockProgram, + programData.Address, + config.AccessControllerProgram, + instance.RoleMap[timelock.Proposer_Role].AccessController.PublicKey(), + instance.RoleMap[timelock.Executor_Role].AccessController.PublicKey(), + instance.RoleMap[timelock.Canceller_Role].AccessController.PublicKey(), + instance.RoleMap[timelock.Bypasser_Role].AccessController.PublicKey(), + ).ValidateAndBuild() + require.NoError(t, ierr) + + testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{initTimelockIx}, deployer, config.DefaultCommitment) + + var configAccount timelock.Config + err = common.GetAccountDataBorshInto(ctx, solanaGoClient, instance.ConfigPDA, config.DefaultCommitment, &configAccount) + if err != nil { + require.NoError(t, err, "failed to get account info") + } + + require.Equal(t, deployer.PublicKey(), configAccount.Owner, "The initial owner doesn't match") + require.Equal(t, instance.ID, configAccount.TimelockId, "TimelockID doesn't match") + require.Equal(t, config.MinDelay, configAccount.MinDelay, "MinDelay doesn't match") + require.Equal(t, instance.RoleMap[timelock.Proposer_Role].AccessController.PublicKey(), configAccount.ProposerRoleAccessController, "ProposerRoleAccessController doesn't match") + require.Equal(t, instance.RoleMap[timelock.Executor_Role].AccessController.PublicKey(), configAccount.ExecutorRoleAccessController, "ExecutorRoleAccessController doesn't match") + require.Equal(t, instance.RoleMap[timelock.Canceller_Role].AccessController.PublicKey(), configAccount.CancellerRoleAccessController, "CancellerRoleAccessController doesn't match") + require.Equal(t, instance.RoleMap[timelock.Bypasser_Role].AccessController.PublicKey(), configAccount.BypasserRoleAccessController, "BypasserRoleAccessController doesn't match") + } + }) + + t.Run("transfer config ownership(admin role) to each admin", func(t *testing.T) { + for _, instance := range timelockInstances { + t.Run("transfer ownership", func(t *testing.T) { + ix, ierr := timelock.NewTransferOwnershipInstruction( + instance.ID, + instance.Admin.PublicKey(), + instance.ConfigPDA, + deployer.PublicKey(), + ).ValidateAndBuild() + require.NoError(t, ierr) + result := testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, deployer, config.DefaultCommitment) + require.NotNil(t, result) + }) + + t.Run("instance.Admin becomes owner", func(t *testing.T) { + ix, ierr := timelock.NewAcceptOwnershipInstruction( + instance.ID, + instance.ConfigPDA, + instance.Admin.PublicKey(), + ).ValidateAndBuild() + require.NoError(t, ierr) + result := testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, instance.Admin, config.DefaultCommitment) + require.NotNil(t, result) + + // Validate proposed set to 0-address after accepting ownership + var configAccount timelock.Config + err = common.GetAccountDataBorshInto(ctx, solanaGoClient, timelockutil.GetConfigPDA(instance.ID), config.DefaultCommitment, &configAccount) + if err != nil { + require.NoError(t, err, "failed to get account info") + } + require.Equal(t, instance.Admin.PublicKey(), configAccount.Owner) + require.Equal(t, solana.PublicKey{}, configAccount.ProposedOwner) + }) + } + }) + + t.Run("setup:register access list & verify", func(t *testing.T) { + for _, instance := range timelockInstances { + for role, data := range instance.RoleMap { + addresses := []solana.PublicKey{} + for _, account := range data.Accounts { + addresses = append(addresses, account.PublicKey()) + } + batchAddAccessIxs, baerr := timelockutil.GetBatchAddAccessIxs(ctx, instance.ID, data.AccessController.PublicKey(), role, addresses, instance.Admin, config.BatchAddAccessChunkSize, solanaGoClient) + require.NoError(t, baerr) + + for _, ix := range batchAddAccessIxs { + testutils.SendAndConfirm(ctx, t, solanaGoClient, []solana.Instruction{ix}, instance.Admin, config.DefaultCommitment) + } + + for _, account := range data.Accounts { + found, ferr := accesscontroller.HasAccess(ctx, solanaGoClient, data.AccessController.PublicKey(), account.PublicKey(), config.DefaultCommitment) + require.NoError(t, ferr) + require.True(t, found, "Account %s not found in %s AccessList", account.PublicKey(), data.Role) + } + } + } + }) +} diff --git a/chains/solana/contracts/tests/mcms/timelock_rbac_test.go b/chains/solana/contracts/tests/mcms/timelock_rbac_test.go index 0680c39e..53aa1b6c 100644 --- a/chains/solana/contracts/tests/mcms/timelock_rbac_test.go +++ b/chains/solana/contracts/tests/mcms/timelock_rbac_test.go @@ -99,7 +99,7 @@ func TestTimelockRBAC(t *testing.T) { require.NotNil(t, result) }) - t.Run("setup:init timelock program", func(t *testing.T) { + t.Run("setup:initialize timelock instance", func(t *testing.T) { // get program data account data, accErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.TimelockProgram, &rpc.GetAccountInfoOpts{ Commitment: config.DefaultCommitment, diff --git a/chains/solana/contracts/tests/mcms/timelock_schedule_execute_test.go b/chains/solana/contracts/tests/mcms/timelock_schedule_execute_test.go index fb94f80a..ec6a1b7f 100644 --- a/chains/solana/contracts/tests/mcms/timelock_schedule_execute_test.go +++ b/chains/solana/contracts/tests/mcms/timelock_schedule_execute_test.go @@ -82,7 +82,7 @@ func TestTimelockScheduleAndExecute(t *testing.T) { } } }) - t.Run("setup:init timelock program", func(t *testing.T) { + t.Run("setup:initialize timelock instance", func(t *testing.T) { // get program data account data, accErr := solanaGoClient.GetAccountInfoWithOpts(ctx, config.TimelockProgram, &rpc.GetAccountInfoOpts{ Commitment: config.DefaultCommitment,