From bd0a767f599e6665e8a059e5db0ca6492eec07ec Mon Sep 17 00:00:00 2001 From: Eric Bolten Date: Tue, 19 Dec 2023 09:30:09 -0600 Subject: [PATCH] Axelar integration tests (#248) * axelarcork integration test stub * add axelarcork genesis state to integration tests * WIP on axelar integration test * computer cork/axelarcork IDHash the same way * Fix up axelarcork msgs.go * WIP on axelarcork integration test * go mod tidy * appease linter * move creation of axelar keeper to after gravity * fix creation of msg for scheduling an axelar cork * WIP for axelar integration test * check all the scheduled corks * v7 upgrade uses zero-size slices with capacities * integration tests normal and scheduled corks * Set the ChainID in upgrade data * integration test for upgrade proxy proposal * appease linter * full axelar integration test --- .github/workflows/integration-tests.yml | 1 + Makefile | 5 +- app/app.go | 27 +- app/upgrades/v7/upgrades.go | 8 +- go.mod | 4 +- go.sum | 4 - integration_tests/axelarcork_test.go | 483 ++++++++++++++++++++++++ integration_tests/setup_test.go | 18 +- integration_tests/validator.go | 4 + x/axelarcork/keeper/proposal_handler.go | 1 + x/axelarcork/types/axelarcork.go | 4 +- x/axelarcork/types/msgs.go | 104 ++++- x/cork/types/cork.go | 4 +- 13 files changed, 628 insertions(+), 39 deletions(-) create mode 100644 integration_tests/axelarcork_test.go diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 3cd9abaf..2a2f44d6 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -121,6 +121,7 @@ jobs: matrix: test_type: [ "ScheduledCork", + "AxelarCork", "Auction", "CellarFees", "Incentives", diff --git a/Makefile b/Makefile index 5720b6df..ac37a1d0 100644 --- a/Makefile +++ b/Makefile @@ -368,7 +368,10 @@ e2e_basic: e2e_clean_slate e2e_scheduled_cork_test: e2e_clean_slate @E2E_SKIP_CLEANUP=true integration_tests/integration_tests.test -test.failfast -test.v -test.run IntegrationTestSuite -testify.m TestScheduledCork || make -s fail -e2e_auction_module_test: e2e_clean_slate +e2e_axelarcork_test: e2e_clean_slate + @E2E_SKIP_CLEANUP=true integration_tests/integration_tests.test -test.failfast -test.v -test.run IntegrationTestSuite -testify.m TestAxelarCork || make -s fail + +e2e_auction_test: e2e_clean_slate @E2E_SKIP_CLEANUP=true integration_tests/integration_tests.test -test.failfast -test.v -test.run IntegrationTestSuite -testify.m TestAuction || make -s fail e2e_cellarfees_test: e2e_clean_slate diff --git a/app/app.go b/app/app.go index 343a0c93..0066a05d 100644 --- a/app/app.go +++ b/app/app.go @@ -418,19 +418,6 @@ func NewSommelierApp( app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, ) - // create axelar cork keeper - app.AxelarCorkKeeper = axelarcorkkeeper.NewKeeper( - appCodec, - keys[axelarcorktypes.StoreKey], - app.GetSubspace(axelarcorktypes.ModuleName), - app.AccountKeeper, - app.BankKeeper, - app.StakingKeeper, - app.TransferKeeper, - app.DistrKeeper, - app.IBCKeeper.ChannelKeeper, - app.GravityKeeper, - ) transferModule := ibctransfer.NewAppModule(app.TransferKeeper) transferIBCModule := ibctransfer.NewIBCModule(app.TransferKeeper) var transferStack ibcporttypes.IBCModule = transferIBCModule @@ -459,6 +446,20 @@ func NewSommelierApp( app.ModuleAccountAddressesToNames([]string{distrtypes.ModuleName}), ) + // create axelar cork keeper + app.AxelarCorkKeeper = axelarcorkkeeper.NewKeeper( + appCodec, + keys[axelarcorktypes.StoreKey], + app.GetSubspace(axelarcorktypes.ModuleName), + app.AccountKeeper, + app.BankKeeper, + app.StakingKeeper, + app.TransferKeeper, + app.DistrKeeper, + app.IBCKeeper.ChannelKeeper, + app.GravityKeeper, + ) + app.CorkKeeper = corkkeeper.NewKeeper( appCodec, keys[corktypes.StoreKey], app.GetSubspace(corktypes.ModuleName), app.StakingKeeper, app.GravityKeeper, diff --git a/app/upgrades/v7/upgrades.go b/app/upgrades/v7/upgrades.go index e87c352f..237c33c6 100644 --- a/app/upgrades/v7/upgrades.go +++ b/app/upgrades/v7/upgrades.go @@ -265,7 +265,7 @@ func pubsubInitGenesis(ctx sdk.Context, pubsubKeeper pubsubkeeper.Keeper) { } // Set 7seas publisher intents for existing cellars - publisherIntents := make([]*pubsubtypes.PublisherIntent, 25) + publisherIntents := make([]*pubsubtypes.PublisherIntent, 0, 25) for _, cellar := range cellars { publisherIntents = append(publisherIntents, &pubsubtypes.PublisherIntent{ SubscriptionId: cellar, @@ -276,7 +276,7 @@ func pubsubInitGenesis(ctx sdk.Context, pubsubKeeper pubsubkeeper.Keeper) { } // Set default subscriptions for 7seas as the publisher for existing cellars - defaultSubscriptions := make([]*pubsubtypes.DefaultSubscription, 25) + defaultSubscriptions := make([]*pubsubtypes.DefaultSubscription, 0, 25) for _, cellar := range cellars { defaultSubscriptions = append(defaultSubscriptions, &pubsubtypes.DefaultSubscription{ SubscriptionId: cellar, @@ -286,7 +286,7 @@ func pubsubInitGenesis(ctx sdk.Context, pubsubKeeper pubsubkeeper.Keeper) { // Create subscribers and intents for existing validators subscribers := createSubscribers() - subscriberIntents := make([]*pubsubtypes.SubscriberIntent, 875) + subscriberIntents := make([]*pubsubtypes.SubscriberIntent, 0, 875) for _, subscriber := range subscribers { for _, cellar := range cellars { subscriberIntents = append(subscriberIntents, &pubsubtypes.SubscriberIntent{ @@ -318,7 +318,7 @@ func pubsubInitGenesis(ctx sdk.Context, pubsubKeeper pubsubkeeper.Keeper) { // leaving out made_in_block because I can't find their validator on-chain // blockhunters hadn't been merged, but verified and added here func createSubscribers() []*pubsubtypes.Subscriber { - subscribers := make([]*pubsubtypes.Subscriber, 35) + subscribers := make([]*pubsubtypes.Subscriber, 0, 35) subscribers = append(subscribers, &pubsubtypes.Subscriber{ Address: "somm1s2q8avjykkztudpl8k60f0ns4v5mvnjp5t366c", diff --git a/go.mod b/go.mod index b5ad5ff1..c440fe3a 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/peggyjv/sommelier/v7 go 1.19 require ( + cosmossdk.io/errors v1.0.0-beta.7 + cosmossdk.io/math v1.0.0-rc.0 github.com/cosmos/cosmos-sdk v0.46.14 github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/ibc-go/v6 v6.2.0 @@ -34,8 +36,6 @@ require ( cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v0.12.0 // indirect cloud.google.com/go/storage v1.28.1 // indirect - cosmossdk.io/errors v1.0.0-beta.7 // indirect - cosmossdk.io/math v1.0.0-rc.0 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/keyring v1.2.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect diff --git a/go.sum b/go.sum index 7eb30ae3..df96781c 100644 --- a/go.sum +++ b/go.sum @@ -842,10 +842,6 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/peggyjv/gravity-bridge/module/v4 v4.0.0-20231130211750-e1fa9902c9d9 h1:3aqB5WI8S1FtvsoU7HAM+35czUtDvEZJa1feK8/9xRg= -github.com/peggyjv/gravity-bridge/module/v4 v4.0.0-20231130211750-e1fa9902c9d9/go.mod h1:biwoVDKWggMYtzuUZOuqaJD8B40XHrXg3zOJjeAhT78= -github.com/peggyjv/gravity-bridge/module/v4 v4.0.0-20231211214131-bf2a31eb986f h1:xaVjypGpS3+VUXzEb2YFaTK632Id6jVWY1ViS4nljJs= -github.com/peggyjv/gravity-bridge/module/v4 v4.0.0-20231211214131-bf2a31eb986f/go.mod h1:n8Jj3X+w6q0Xz19w8feRRl2sfIGV+3FGU4anzXBQGbA= github.com/peggyjv/gravity-bridge/module/v4 v4.0.0 h1:jH10FodlMxzGdbxAxeNpsql9qhYDlmwqciwOuwuU0LQ= github.com/peggyjv/gravity-bridge/module/v4 v4.0.0/go.mod h1:n8Jj3X+w6q0Xz19w8feRRl2sfIGV+3FGU4anzXBQGbA= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= diff --git a/integration_tests/axelarcork_test.go b/integration_tests/axelarcork_test.go new file mode 100644 index 00000000..d8100641 --- /dev/null +++ b/integration_tests/axelarcork_test.go @@ -0,0 +1,483 @@ +package integration_tests + +import ( + "context" + "encoding/hex" + "sort" + "time" + + "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/client" + sdk "github.com/cosmos/cosmos-sdk/types" + govtypesv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + "github.com/ethereum/go-ethereum/common" + "github.com/golang/protobuf/proto" //nolint:staticcheck + "github.com/peggyjv/sommelier/v7/x/axelarcork/types" +) + +func (s *IntegrationTestSuite) TestAxelarCork() { + s.Run("Test the axelarcork module", func() { + /////////// + // Setup // + /////////// + + val0 := s.chain.validators[0] + val0kb, err := val0.keyring() + s.Require().NoError(err) + val0ClientCtx, err := s.chain.clientContext("tcp://localhost:26657", &val0kb, "val", val0.address()) + s.Require().NoError(err) + + orch0 := s.chain.orchestrators[0] + orch0ClientCtx, err := s.chain.clientContext("tcp://localhost:26657", orch0.keyring, "orch", orch0.address()) + s.Require().NoError(err) + + proposer := s.chain.proposer + proposerCtx, err := s.chain.clientContext("tcp://localhost:26657", proposer.keyring, "proposer", proposer.address()) + s.Require().NoError(err) + propID := uint64(1) + + sortedValidators := make([]string, 0, 4) + for _, validator := range s.chain.validators { + sortedValidators = append(sortedValidators, validator.validatorAddress().String()) + } + sort.Strings(sortedValidators) + + axelarcorkQueryClient := types.NewQueryClient(val0ClientCtx) + govQueryClient := govtypesv1beta1.NewQueryClient(orch0ClientCtx) + + ///////////////////////////// + // Add chain configuration // + ///////////////////////////// + + arbitrumChainName := "arbitrum" + arbitrumChainID := uint64(42161) + proxyAddress := "0xEe75bA2C81C04DcA4b0ED6d1B7077c188FEde4d2" + + s.T().Log("Creating AddChainConfigurationProposal") + addChainConfigurationProp := types.AddChainConfigurationProposal{ + Title: "add a chain configuration", + Description: "adding an arbitrum chain config", + ChainConfiguration: &types.ChainConfiguration{ + Name: arbitrumChainName, + Id: arbitrumChainID, + ProxyAddress: proxyAddress, + }, + } + + addChainConfigurationPropMsg, err := govtypesv1beta1.NewMsgSubmitProposal( + &addChainConfigurationProp, + sdk.Coins{ + { + Denom: testDenom, + Amount: math.NewInt(2), + }, + }, + proposer.address(), + ) + s.Require().NoError(err, "Unable to create AddChainConfigurationProposal") + + s.submitAndVoteForAxelarProposal(proposerCtx, orch0ClientCtx, propID, addChainConfigurationPropMsg) + propID++ + + s.T().Log("Verifying ChainConfiguration correctly added") + chainConfigurationsResponse, err := axelarcorkQueryClient.QueryChainConfigurations(context.Background(), &types.QueryChainConfigurationsRequest{}) + s.Require().NoError(err) + s.Require().Len(chainConfigurationsResponse.Configurations, 1) + chainConfig := chainConfigurationsResponse.Configurations[0] + s.Require().Equal(chainConfig.Name, arbitrumChainName) + s.Require().Equal(chainConfig.Id, arbitrumChainID) + s.Require().Equal(chainConfig.ProxyAddress, proxyAddress) + + ////////////////// + // Add a cellar // + ////////////////// + + s.T().Log("Creating AddAxelarManagedCellarIDsProposal for counter contract") + addAxelarManagedCellarIDsProp := types.AddAxelarManagedCellarIDsProposal{ + Title: "add the counter contract as axelar cellar", + Description: "arbitrum counter contract", + ChainId: arbitrumChainID, + CellarIds: &types.CellarIDSet{ + Ids: []string{ + counterContract.Hex(), + }, + }, + } + + addAxelarManagedCellarIDsPropMsg, err := govtypesv1beta1.NewMsgSubmitProposal( + &addAxelarManagedCellarIDsProp, + sdk.Coins{ + { + Denom: testDenom, + Amount: math.NewInt(2), + }, + }, + proposer.address(), + ) + s.Require().NoError(err, "Unable to create AddAxelarManagedCellarIDsProposal") + + s.submitAndVoteForAxelarProposal(proposerCtx, orch0ClientCtx, propID, addAxelarManagedCellarIDsPropMsg) + propID++ + + s.T().Log("Verifying CellarID correctly added") + cellarIDsResponse, err := axelarcorkQueryClient.QueryCellarIDsByChainID(context.Background(), &types.QueryCellarIDsByChainIDRequest{ChainId: arbitrumChainID}) + s.Require().NoError(err) + s.Require().Len(cellarIDsResponse.CellarIds, 1) + s.Require().Equal(cellarIDsResponse.CellarIds[0], counterContract.Hex()) + + ///////////////////////////// + // Schedule an Axelar cork // + ///////////////////////////// + + s.T().Log("Schedule an axelar cork for the future") + node, err := proposerCtx.GetNode() + s.Require().NoError(err) + status, err := node.Status(context.Background()) + s.Require().NoError(err) + currentBlockHeight := status.SyncInfo.LatestBlockHeight + targetBlockHeight := uint64(currentBlockHeight + 15) + deadline := uint64(time.Now().Unix() + int64(time.Hour.Seconds())) + + s.T().Logf("Scheduling axelar cork calls for height %d", targetBlockHeight) + axelarCork := types.AxelarCork{ + EncodedContractCall: ABIEncodedInc(), + ChainId: arbitrumChainID, + TargetContractAddress: counterContract.Hex(), + Deadline: deadline, + } + axelarCorkID := axelarCork.IDHash(targetBlockHeight) + axelarCorkIDHex := hex.EncodeToString(axelarCorkID) + s.T().Logf("Axelar cork ID is %s", axelarCorkIDHex) + for i, orch := range s.chain.orchestrators { + i := i + orch := orch + clientCtx, err := s.chain.clientContext("tcp://localhost:26657", orch.keyring, "orch", orch.address()) + s.Require().NoError(err) + axelarCorkMsg, err := types.NewMsgScheduleAxelarCorkRequest( + arbitrumChainID, + ABIEncodedInc(), + counterContract, + deadline, + targetBlockHeight, + orch.address()) + s.Require().NoError(err, "Failed to construct axelar cork") + response, err := s.chain.sendMsgs(*clientCtx, axelarCorkMsg) + s.Require().NoError(err, "Failed to send axelar cork to node %d", i) + if response.Code != 0 { + if response.Code != 32 { + s.T().Log(response) + } + } + + s.T().Logf("Axelar cork msg for orch %d sent successfully", i) + } + + s.T().Log("Verifying scheduled axelar corks were created") + scheduledCorksResponse, err := axelarcorkQueryClient.QueryScheduledCorks(context.Background(), &types.QueryScheduledCorksRequest{ChainId: arbitrumChainID}) + s.Require().NoError(err) + s.Require().Len(scheduledCorksResponse.Corks, 4) + cork0 := scheduledCorksResponse.Corks[0] + cork1 := scheduledCorksResponse.Corks[1] + cork2 := scheduledCorksResponse.Corks[2] + cork3 := scheduledCorksResponse.Corks[3] + s.Require().Equal(cork0.Cork.EncodedContractCall, ABIEncodedInc()) + s.Require().Equal(cork0.Cork.ChainId, arbitrumChainID) + s.Require().Equal(cork0.Cork.TargetContractAddress, counterContract.Hex()) + s.Require().Equal(cork0.Cork.Deadline, deadline) + s.Require().Equal(cork0.BlockHeight, targetBlockHeight) + s.Require().Equal(cork0.Id, axelarCorkIDHex) + s.Require().Equal(cork1.Cork.EncodedContractCall, ABIEncodedInc()) + s.Require().Equal(cork1.Cork.ChainId, arbitrumChainID) + s.Require().Equal(cork1.Cork.TargetContractAddress, counterContract.Hex()) + s.Require().Equal(cork1.Cork.Deadline, deadline) + s.Require().Equal(cork1.BlockHeight, targetBlockHeight) + s.Require().Equal(cork1.Id, axelarCorkIDHex) + s.Require().Equal(cork2.Cork.EncodedContractCall, ABIEncodedInc()) + s.Require().Equal(cork2.Cork.ChainId, arbitrumChainID) + s.Require().Equal(cork2.Cork.TargetContractAddress, counterContract.Hex()) + s.Require().Equal(cork2.Cork.Deadline, deadline) + s.Require().Equal(cork2.BlockHeight, targetBlockHeight) + s.Require().Equal(cork2.Id, axelarCorkIDHex) + s.Require().Equal(cork3.Cork.EncodedContractCall, ABIEncodedInc()) + s.Require().Equal(cork3.Cork.ChainId, arbitrumChainID) + s.Require().Equal(cork3.Cork.TargetContractAddress, counterContract.Hex()) + s.Require().Equal(cork3.Cork.Deadline, deadline) + s.Require().Equal(cork3.BlockHeight, targetBlockHeight) + s.Require().Equal(cork3.Id, axelarCorkIDHex) + + corkValidators := []string{cork0.Validator, cork1.Validator, cork2.Validator, cork3.Validator} + sort.Strings(corkValidators) + s.Require().Equal(corkValidators, sortedValidators) + + s.T().Log("Waiting for scheduled height") + s.Require().Eventuallyf(func() bool { + node, err := val0ClientCtx.GetNode() + s.Require().NoError(err) + status, err := node.Status(context.Background()) + s.Require().NoError(err) + + currentHeight := status.SyncInfo.LatestBlockHeight + if currentHeight > int64(targetBlockHeight+1) { + return true + } else if currentHeight < int64(targetBlockHeight) { + scheduledCorksResponse, err := axelarcorkQueryClient.QueryScheduledCorks(context.Background(), &types.QueryScheduledCorksRequest{ChainId: arbitrumChainID}) + if err != nil { + s.T().Logf("error: %s", err) + return false + } + + // verify that the scheduled corks have not yet been consumed + s.Require().Len(scheduledCorksResponse.Corks, len(s.chain.validators)) + } + + return false + }, 3*time.Minute, 1*time.Second, "never reached scheduled height") + + s.T().Log("Verifying axelar cork was approved") + corkResultResponse, err := axelarcorkQueryClient.QueryCorkResult(context.Background(), &types.QueryCorkResultRequest{Id: axelarCorkIDHex, ChainId: arbitrumChainID}) + s.Require().NoError(err) + s.Require().True(corkResultResponse.CorkResult.Approved) + s.Require().True(sdk.MustNewDecFromStr(corkResultResponse.CorkResult.ApprovalPercentage).GT(corkVoteThreshold)) + s.Require().Equal(counterContract, common.HexToAddress(corkResultResponse.CorkResult.Cork.TargetContractAddress)) + + // the corks are deleted when it's converted into a WinningAxelarCork and is relayable + s.T().Log("Verifying scheduled axelar corks were deleted") + scheduledCorksByHeightResponse, err := axelarcorkQueryClient.QueryScheduledCorksByBlockHeight(context.Background(), &types.QueryScheduledCorksByBlockHeightRequest{BlockHeight: targetBlockHeight, ChainId: arbitrumChainID}) + s.Require().NoError(err) + s.Require().Empty(scheduledCorksByHeightResponse.Corks) + + /////////////////////////////////////////////// + // Create a governance scheduled Axelar cork // + /////////////////////////////////////////////// + + protoJSON := "{\"cellar_id\":\"0x123801a7D398351b8bE11C439e05C5B3259aeC9B\",\"cellar_v1\":{\"some_fuction\":{\"function_args\":{}},\"block_height\":12345}}" + s.T().Log("Creating AxelarScheduledCorkProposal for counter contract") + addAxelarScheduledCorkProp := types.AxelarScheduledCorkProposal{ + Title: "schedule a cork to the counter contract", + Description: "arbitrum counter contract scheduled cork", + BlockHeight: targetBlockHeight, + ChainId: arbitrumChainID, + TargetContractAddress: counterContract.Hex(), + ContractCallProtoJson: protoJSON, + Deadline: deadline, + } + + addAxelarScheduledCorkPropMsg, err := govtypesv1beta1.NewMsgSubmitProposal( + &addAxelarScheduledCorkProp, + sdk.Coins{ + { + Denom: testDenom, + Amount: math.NewInt(2), + }, + }, + proposer.address(), + ) + s.Require().NoError(err, "Unable to create AxelarScheduledCorkProposal") + + s.submitAndVoteForAxelarProposal(proposerCtx, orch0ClientCtx, propID, addAxelarScheduledCorkPropMsg) + + s.T().Log("Verifying the details of the AxelarScheduledCorkProposal proposal") + proposalResponse, err := govQueryClient.Proposal(context.Background(), &govtypesv1beta1.QueryProposalRequest{ProposalId: propID}) + s.Require().NoError(err) + propContent := &types.AxelarScheduledCorkProposal{} + err = proto.Unmarshal(proposalResponse.Proposal.Content.Value, propContent) + s.Require().NoError(err, "couldn't unmarshal into proposal") + s.Require().Equal(propContent.Title, addAxelarScheduledCorkProp.Title) + s.Require().Equal(propContent.Description, addAxelarScheduledCorkProp.Description) + s.Require().Equal(propContent.BlockHeight, addAxelarScheduledCorkProp.BlockHeight) + s.Require().Equal(propContent.ChainId, addAxelarScheduledCorkProp.ChainId) + s.Require().Equal(propContent.TargetContractAddress, addAxelarScheduledCorkProp.TargetContractAddress) + s.Require().Equal(propContent.ContractCallProtoJson, addAxelarScheduledCorkProp.ContractCallProtoJson) + s.Require().Equal(propContent.Deadline, addAxelarScheduledCorkProp.Deadline) + propID++ + + ////////////////////////////////////// + // Upgrade an Axelar proxy contract // + ////////////////////////////////////// + + s.T().Log("Creating UpgradeAxelarProxyContractProposal") + newProxyAddress := "0x438087f7c226A89762a791F187d7c3D4a0e95ae6" + upgradeAxelarProxyContractProp := types.UpgradeAxelarProxyContractProposal{ + Title: "upgrade an axelar proxy contract", + Description: "arbitrum is getting a new proxy", + ChainId: arbitrumChainID, + NewProxyAddress: newProxyAddress, + } + + upgradeAxelarProxyContractPropMsg, err := govtypesv1beta1.NewMsgSubmitProposal( + &upgradeAxelarProxyContractProp, + sdk.Coins{ + { + Denom: testDenom, + Amount: math.NewInt(2), + }, + }, + proposer.address(), + ) + s.Require().NoError(err, "Unable to create UpgradeAxelarProxyContractProposal") + + s.submitAndVoteForAxelarProposal(proposerCtx, orch0ClientCtx, propID, upgradeAxelarProxyContractPropMsg) + propID++ + + s.T().Log("Verifying upgrade data added correctly") + node, err = val0ClientCtx.GetNode() + s.Require().NoError(err) + status, err = node.Status(context.Background()) + s.Require().NoError(err) + + threshold := int64(types.DefaultExecutableHeightThreshold) + currentHeight := status.SyncInfo.LatestBlockHeight + upgradeResponse, err := axelarcorkQueryClient.QueryAxelarProxyUpgradeData(context.Background(), &types.QueryAxelarProxyUpgradeDataRequest{}) + s.Require().NoError(err) + s.Require().Len(upgradeResponse.ProxyUpgradeData, 1) + upgradeData := upgradeResponse.ProxyUpgradeData[0] + s.Require().Equal(upgradeData.ChainId, arbitrumChainID) + // an approximation but timing is difficult + s.Require().Less(upgradeData.ExecutableHeightThreshold, currentHeight+threshold+5) + s.Require().Greater(upgradeData.ExecutableHeightThreshold, currentHeight+threshold-5) + encodedProxy, _, err := types.DecodeUpgradeArgs(upgradeData.Payload) + s.Require().NoError(err) + s.Require().Equal(encodedProxy, newProxyAddress) + + ///////////////////////////////////////////// + // Cancel an Axelar proxy contract upgrade // + ///////////////////////////////////////////// + + s.T().Log("Creating CancelAxelarProxyContractUpgradeProposal") + cancelAxelarProxyContractUpgradeProp := types.CancelAxelarProxyContractUpgradeProposal{ + Title: "cancel upgrade ofan axelar proxy contract", + Description: "arbitrum is not getting a new proxy", + ChainId: arbitrumChainID, + } + + cancelAxelarProxyContractUpgradePropMsg, err := govtypesv1beta1.NewMsgSubmitProposal( + &cancelAxelarProxyContractUpgradeProp, + sdk.Coins{ + { + Denom: testDenom, + Amount: math.NewInt(2), + }, + }, + proposer.address(), + ) + s.Require().NoError(err, "Unable to create CancelAxelarProxyContractUpgradeProposal") + + s.submitAndVoteForAxelarProposal(proposerCtx, orch0ClientCtx, propID, cancelAxelarProxyContractUpgradePropMsg) + propID++ + + s.T().Log("Verifying upgrade data removed correctly") + upgradeResponse, err = axelarcorkQueryClient.QueryAxelarProxyUpgradeData(context.Background(), &types.QueryAxelarProxyUpgradeDataRequest{}) + s.Require().NoError(err) + s.Require().Empty(upgradeResponse.ProxyUpgradeData) + + ///////////////////// + // Remove a cellar // + ///////////////////// + + s.T().Log("Creating RemoveAxelarManagedCellarIDsProposal for counter contract") + removeAxelarManagedCellarIDsProp := types.RemoveAxelarManagedCellarIDsProposal{ + Title: "add the counter contract as axelar cellar", + Description: "arbitrum counter contract", + ChainId: arbitrumChainID, + CellarIds: &types.CellarIDSet{ + Ids: []string{ + counterContract.Hex(), + }, + }, + } + + removeAxelarManagedCellarIDsPropMsg, err := govtypesv1beta1.NewMsgSubmitProposal( + &removeAxelarManagedCellarIDsProp, + sdk.Coins{ + { + Denom: testDenom, + Amount: math.NewInt(2), + }, + }, + proposer.address(), + ) + s.Require().NoError(err, "Unable to create RemoveAxelarManagedCellarIDsProposal") + + s.submitAndVoteForAxelarProposal(proposerCtx, orch0ClientCtx, propID, removeAxelarManagedCellarIDsPropMsg) + propID++ + + s.T().Log("Verifying CellarID correctly added") + cellarIDsResponse, err = axelarcorkQueryClient.QueryCellarIDsByChainID(context.Background(), &types.QueryCellarIDsByChainIDRequest{ChainId: arbitrumChainID}) + s.Require().NoError(err) + s.Require().Empty(cellarIDsResponse.CellarIds) + + ////////////////////////////////// + // Remove a chain configuration // + ////////////////////////////////// + + s.T().Log("Creating RemoveChainConfigurationProposal") + removeChainConfigurationProp := types.RemoveChainConfigurationProposal{ + Title: "add a chain configuration", + Description: "adding an arbitrum chain config", + ChainId: arbitrumChainID, + } + + removeChainConfigurationPropMsg, err := govtypesv1beta1.NewMsgSubmitProposal( + &removeChainConfigurationProp, + sdk.Coins{ + { + Denom: testDenom, + Amount: math.NewInt(2), + }, + }, + proposer.address(), + ) + s.Require().NoError(err, "Unable to create RemoveChainConfigurationProposal") + + s.submitAndVoteForAxelarProposal(proposerCtx, orch0ClientCtx, propID, removeChainConfigurationPropMsg) + + s.T().Log("Verifying ChainConfiguration correctly added") + chainConfigurationsResponse, err = axelarcorkQueryClient.QueryChainConfigurations(context.Background(), &types.QueryChainConfigurationsRequest{}) + s.Require().NoError(err) + s.Require().Empty(chainConfigurationsResponse.Configurations) + }) +} + +func (s *IntegrationTestSuite) submitAndVoteForAxelarProposal(proposerCtx *client.Context, orchClientCtx *client.Context, propID uint64, proposalMsg *govtypesv1beta1.MsgSubmitProposal) { + s.T().Log("Submit proposal") + submitProposalResponse, err := s.chain.sendMsgs(*proposerCtx, proposalMsg) + s.Require().NoError(err) + s.Require().Zero(submitProposalResponse.Code, "raw log: %s", submitProposalResponse.RawLog) + + s.T().Log("Check proposal was submitted correctly") + govQueryClient := govtypesv1beta1.NewQueryClient(orchClientCtx) + + s.Require().Eventually(func() bool { + proposalsQueryResponse, err := govQueryClient.Proposals(context.Background(), &govtypesv1beta1.QueryProposalsRequest{}) + if err != nil { + s.T().Logf("error querying proposals: %e", err) + return false + } + + s.Require().NotEmpty(proposalsQueryResponse.Proposals) + s.Require().Equal(propID, proposalsQueryResponse.Proposals[propID-1].ProposalId, "not proposal id %d", propID) + s.Require().Equal(govtypesv1beta1.StatusVotingPeriod, proposalsQueryResponse.Proposals[propID-1].Status, "proposal not in voting period") + + return true + }, time.Second*30, time.Second*5, "proposal submission was never found") + + s.T().Log("Vote for proposal") + for _, val := range s.chain.validators { + kr, err := val.keyring() + s.Require().NoError(err) + localClientCtx, err := s.chain.clientContext("tcp://localhost:26657", &kr, "val", val.address()) + s.Require().NoError(err) + + voteMsg := govtypesv1beta1.NewMsgVote(val.address(), propID, govtypesv1beta1.OptionYes) + voteResponse, err := s.chain.sendMsgs(*localClientCtx, voteMsg) + s.Require().NoError(err) + s.Require().Zero(voteResponse.Code, "Vote error: %s", voteResponse.RawLog) + } + + s.T().Log("Waiting for proposal to be approved") + s.Require().Eventually(func() bool { + proposalQueryResponse, _ := govQueryClient.Proposal(context.Background(), &govtypesv1beta1.QueryProposalRequest{ProposalId: propID}) + return govtypesv1beta1.StatusPassed == proposalQueryResponse.Proposal.Status + }, time.Second*30, time.Second*5, "proposal was never accepted") + s.T().Log("Proposal approved!") +} diff --git a/integration_tests/setup_test.go b/integration_tests/setup_test.go index 0c31bb02..455b2a40 100644 --- a/integration_tests/setup_test.go +++ b/integration_tests/setup_test.go @@ -19,6 +19,7 @@ import ( govtypesv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" "github.com/peggyjv/sommelier/v7/app/params" auctiontypes "github.com/peggyjv/sommelier/v7/x/auction/types" + axelarcorktypes "github.com/peggyjv/sommelier/v7/x/axelarcork/types" cellarfeestypes "github.com/peggyjv/sommelier/v7/x/cellarfees/types" corktypes "github.com/peggyjv/sommelier/v7/x/cork/types" @@ -36,6 +37,7 @@ import ( genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + ibctransfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" "github.com/spf13/viper" @@ -407,11 +409,25 @@ func (s *IntegrationTestSuite) initGenesis() { FundingModuleAccount: cellarfeestypes.ModuleName, ProceedsModuleAccount: cellarfeestypes.ModuleName, }) - bz, err = cdc.MarshalJSON(&auctionGenState) s.Require().NoError(err) appGenState[auctiontypes.ModuleName] = bz + axelarcorkGenState := axelarcorktypes.DefaultGenesisState() + s.Require().NoError(cdc.UnmarshalJSON(appGenState[axelarcorktypes.ModuleName], &axelarcorkGenState)) + axelarcorkGenState.Params = &axelarcorktypes.Params{ + Enabled: true, + IbcChannel: "channel-1", + IbcPort: ibctransfertypes.PortID, + GmpAccount: "axelar1dv4u5k73pzqrxlzujxg3qp8kvc3pje7jtdvu72npnt5zhq05ejcsn5qme5", + ExecutorAccount: "axelar1zl3rxpp70lmte2xr6c4lgske2fyuj3hupcsvcd", + TimeoutDuration: uint64(6 * time.Hour), + CorkTimeoutBlocks: 5000, + } + bz, err = cdc.MarshalJSON(&axelarcorkGenState) + s.Require().NoError(err) + appGenState[axelarcorktypes.ModuleName] = bz + // set cellarfees gen state cellarfeesGenState := cellarfeestypes.DefaultGenesisState() s.Require().NoError(cdc.UnmarshalJSON(appGenState[cellarfeestypes.ModuleName], &cellarfeesGenState)) diff --git a/integration_tests/validator.go b/integration_tests/validator.go index 66f7a5c1..c84afc7a 100644 --- a/integration_tests/validator.go +++ b/integration_tests/validator.go @@ -357,6 +357,10 @@ func (v *validator) address() sdk.AccAddress { return addr } +func (v *validator) validatorAddress() sdk.ValAddress { + return sdk.ValAddress(v.address()) +} + func (v *validator) pubKey() cryptotypes.PubKey { pubKey, err := v.keyRecord.GetPubKey() if err != nil { diff --git a/x/axelarcork/keeper/proposal_handler.go b/x/axelarcork/keeper/proposal_handler.go index 43bec67e..7dca52e6 100644 --- a/x/axelarcork/keeper/proposal_handler.go +++ b/x/axelarcork/keeper/proposal_handler.go @@ -181,6 +181,7 @@ func HandleUpgradeAxelarProxyContractProposal(ctx sdk.Context, k Keeper, p types } upgradeData := types.AxelarUpgradeData{ + ChainId: p.ChainId, Payload: payload, ExecutableHeightThreshold: ctx.BlockHeight() + int64(types.DefaultExecutableHeightThreshold), } diff --git a/x/axelarcork/types/axelarcork.go b/x/axelarcork/types/axelarcork.go index da99cc90..c435d086 100644 --- a/x/axelarcork/types/axelarcork.go +++ b/x/axelarcork/types/axelarcork.go @@ -10,12 +10,12 @@ import ( func (c *AxelarCork) IDHash(blockHeight uint64) []byte { blockHeightBytes := sdk.Uint64ToBigEndian(blockHeight) - + chainIDBytes := sdk.Uint64ToBigEndian(c.ChainId) address := common.HexToAddress(c.TargetContractAddress) return crypto.Keccak256Hash( bytes.Join( - [][]byte{blockHeightBytes, address.Bytes(), c.EncodedContractCall, sdk.Uint64ToBigEndian(c.Deadline)}, + [][]byte{blockHeightBytes, chainIDBytes, address.Bytes(), c.EncodedContractCall}, []byte{}, )).Bytes() } diff --git a/x/axelarcork/types/msgs.go b/x/axelarcork/types/msgs.go index 58f42913..03fc2702 100644 --- a/x/axelarcork/types/msgs.go +++ b/x/axelarcork/types/msgs.go @@ -11,12 +11,16 @@ import ( var ( _ sdk.Msg = &MsgScheduleAxelarCorkRequest{} + _ sdk.Msg = &MsgRelayAxelarCorkRequest{} + _ sdk.Msg = &MsgBumpAxelarCorkGasRequest{} + _ sdk.Msg = &MsgCancelAxelarCorkRequest{} + _ sdk.Msg = &MsgRelayAxelarCorkRequest{} ) const ( - TypeMsgScheduleCorkRequest = "axelar_cork_schedule" - TypeMsgRelayCorkRequest = "axelar_cork_relay" - TypeMsgBumpCorkGasRequest = "axelar_cork_bump_gas" + TypeMsgScheduleAxelarCorkRequest = "axelar_cork_schedule" + TypeMsgRelayAxelarCorkRequest = "axelar_cork_relay" + TypeMsgBumpAxelarCorkGasRequest = "axelar_cork_bump_gas" TypeMsgCancelAxelarCorkRequest = "axelar_cancel_cork" TypeMsgRelayAxelarProxyUpgradeRequest = "axelar_proxy_upgrade_relay" ) @@ -25,8 +29,8 @@ const ( // MsgScheduleAxelarCorkRequest // ////////////////////////////////// -// NewMsgScheduleCorkRequest return a new MsgScheduleAxelarCorkRequest -func NewMsgScheduleCorkRequest(chainID uint64, body []byte, address common.Address, deadline uint64, blockHeight uint64, signer sdk.AccAddress) (*MsgScheduleAxelarCorkRequest, error) { +// NewMsgScheduleAxelarCorkRequest return a new MsgScheduleAxelarCorkRequest +func NewMsgScheduleAxelarCorkRequest(chainID uint64, body []byte, address common.Address, deadline uint64, blockHeight uint64, signer sdk.AccAddress) (*MsgScheduleAxelarCorkRequest, error) { return &MsgScheduleAxelarCorkRequest{ Cork: &AxelarCork{ ChainId: chainID, @@ -34,6 +38,7 @@ func NewMsgScheduleCorkRequest(chainID uint64, body []byte, address common.Addre TargetContractAddress: address.String(), Deadline: deadline, }, + ChainId: chainID, BlockHeight: blockHeight, Signer: signer.String(), }, nil @@ -43,7 +48,7 @@ func NewMsgScheduleCorkRequest(chainID uint64, body []byte, address common.Addre func (m *MsgScheduleAxelarCorkRequest) Route() string { return ModuleName } // Type implements sdk.Msg -func (m *MsgScheduleAxelarCorkRequest) Type() string { return TypeMsgScheduleCorkRequest } +func (m *MsgScheduleAxelarCorkRequest) Type() string { return TypeMsgScheduleAxelarCorkRequest } // ValidateBasic implements sdk.Msg func (m *MsgScheduleAxelarCorkRequest) ValidateBasic() error { @@ -51,6 +56,10 @@ func (m *MsgScheduleAxelarCorkRequest) ValidateBasic() error { return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) } + if m.BlockHeight == 0 { + return fmt.Errorf("block height must be greater than zero") + } + return m.Cork.ValidateBasic() } @@ -77,11 +86,22 @@ func (m *MsgScheduleAxelarCorkRequest) MustGetSigner() sdk.AccAddress { // MsgRelayAxelarCorkRequest // /////////////////////////////// +// NewMsgRelayAxelarCorkRequest returns a new MsgRelayAxelarCorkRequest +func NewMsgRelayAxelarCorkRequest(signer sdk.Address, token sdk.Coin, fee uint64, chainID uint64, address common.Address) (*MsgRelayAxelarCorkRequest, error) { + return &MsgRelayAxelarCorkRequest{ + Signer: signer.String(), + Token: token, + Fee: fee, + ChainId: chainID, + TargetContractAddress: address.String(), + }, nil +} + // Route implements sdk.Msg func (m *MsgRelayAxelarCorkRequest) Route() string { return ModuleName } // Type implements sdk.Msg -func (m *MsgRelayAxelarCorkRequest) Type() string { return TypeMsgRelayCorkRequest } +func (m *MsgRelayAxelarCorkRequest) Type() string { return TypeMsgRelayAxelarCorkRequest } // ValidateBasic implements sdk.Msg func (m *MsgRelayAxelarCorkRequest) ValidateBasic() error { @@ -89,6 +109,18 @@ func (m *MsgRelayAxelarCorkRequest) ValidateBasic() error { return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) } + if !m.Token.IsPositive() { + return fmt.Errorf("token amount must be positive") + } + + if m.Fee == 0 { + return fmt.Errorf("fee must be greather than zero") + } + + if m.ChainId == 0 { + return fmt.Errorf("chain ID must be greater than zero") + } + if m.TargetContractAddress == "" { return fmt.Errorf("cannot relay a cork to an empty address") } @@ -123,6 +155,16 @@ func (m *MsgRelayAxelarCorkRequest) MustGetSigner() sdk.AccAddress { // MsgRelayAxelarProxyUpgradeRequest // /////////////////////////////////////// +// NewMsgRelayAxelarProxyUpgradeRequest returns a new MsgRelayAxelarProxyUpgradeRequest +func NewMsgRelayAxelarProxyUpgradeRequest(signer sdk.AccAddress, token sdk.Coin, fee uint64, chainID uint64) (*MsgRelayAxelarProxyUpgradeRequest, error) { + return &MsgRelayAxelarProxyUpgradeRequest{ + Signer: signer.String(), + Token: token, + Fee: fee, + ChainId: chainID, + }, nil +} + // Route implements sdk.Msg func (m *MsgRelayAxelarProxyUpgradeRequest) Route() string { return ModuleName } @@ -137,6 +179,18 @@ func (m *MsgRelayAxelarProxyUpgradeRequest) ValidateBasic() error { return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) } + if !m.Token.IsPositive() { + return fmt.Errorf("token amount must be positive") + } + + if m.Fee == 0 { + return fmt.Errorf("fee must be greather than zero") + } + + if m.ChainId == 0 { + return fmt.Errorf("chain ID must be greater than zero") + } + return nil } @@ -163,11 +217,20 @@ func (m *MsgRelayAxelarProxyUpgradeRequest) MustGetSigner() sdk.AccAddress { // MsgBumpAxelarCorkGasRequest // ///////////////////////////////// +// NewMsgBumpAxelarCorkGasRequest returns a new MsgBumpAxelarCorkGasRequest +func NewMsgBumpAxelarCorkGasRequest(signer sdk.AccAddress, token sdk.Coin, messageID string) (*MsgBumpAxelarCorkGasRequest, error) { + return &MsgBumpAxelarCorkGasRequest{ + Signer: signer.String(), + Token: token, + MessageId: messageID, + }, nil +} + // Route implements sdk.Msg func (m *MsgBumpAxelarCorkGasRequest) Route() string { return ModuleName } // Type implements sdk.Msg -func (m *MsgBumpAxelarCorkGasRequest) Type() string { return TypeMsgBumpCorkGasRequest } +func (m *MsgBumpAxelarCorkGasRequest) Type() string { return TypeMsgBumpAxelarCorkGasRequest } // ValidateBasic implements sdk.Msg func (m *MsgBumpAxelarCorkGasRequest) ValidateBasic() error { @@ -175,6 +238,14 @@ func (m *MsgBumpAxelarCorkGasRequest) ValidateBasic() error { return errorsmod.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) } + if !m.Token.IsPositive() { + return fmt.Errorf("token amount must be positive") + } + + if m.MessageId == "" { + return fmt.Errorf("message ID must be present") + } + return nil } @@ -201,11 +272,20 @@ func (m *MsgBumpAxelarCorkGasRequest) MustGetSigner() sdk.AccAddress { // MsgCancelAxelarCorkRequest // //////////////////////////////// +// NewMsgCancelAxelarCorkRequest returns a new MsgCancelAxelarCorkRequest +func NewMsgCancelAxelarCorkRequest(signer sdk.AccAddress, chainID uint64, address common.Address) (*MsgCancelAxelarCorkRequest, error) { + return &MsgCancelAxelarCorkRequest{ + Signer: signer.String(), + ChainId: chainID, + TargetContractAddress: address.String(), + }, nil +} + // Route implements sdk.Msg func (m *MsgCancelAxelarCorkRequest) Route() string { return ModuleName } // Type implements sdk.Msg -func (m *MsgCancelAxelarCorkRequest) Type() string { return TypeMsgBumpCorkGasRequest } +func (m *MsgCancelAxelarCorkRequest) Type() string { return TypeMsgCancelAxelarCorkRequest } // ValidateBasic implements sdk.Msg func (m *MsgCancelAxelarCorkRequest) ValidateBasic() error { @@ -214,13 +294,17 @@ func (m *MsgCancelAxelarCorkRequest) ValidateBasic() error { } if m.TargetContractAddress == "" { - return fmt.Errorf("cannot relay a cork to an empty address") + return fmt.Errorf("cannot cancel a cork to an empty address") } if !common.IsHexAddress(m.TargetContractAddress) { return errorsmod.Wrapf(ErrInvalidEVMAddress, "%s", m.TargetContractAddress) } + if m.ChainId == 0 { + return fmt.Errorf("chain ID must be greater than zero") + } + return nil } diff --git a/x/cork/types/cork.go b/x/cork/types/cork.go index 739b9985..24c9e318 100644 --- a/x/cork/types/cork.go +++ b/x/cork/types/cork.go @@ -20,12 +20,12 @@ func (c *Cork) InvalidationScope() tmbytes.HexBytes { func (c *Cork) IDHash(blockHeight uint64) []byte { blockHeightBytes := sdk.Uint64ToBigEndian(blockHeight) - + chainIDBytes := sdk.Uint64ToBigEndian(1) // corks are on eth mainnet address := common.HexToAddress(c.TargetContractAddress) return crypto.Keccak256Hash( bytes.Join( - [][]byte{blockHeightBytes, address.Bytes(), c.EncodedContractCall}, + [][]byte{blockHeightBytes, chainIDBytes, address.Bytes(), c.EncodedContractCall}, []byte{}, )).Bytes() }