-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(evmengine/keeper): add test cases for upgrades (#86)
test(evmengine/keeper): add tcs for upgrades
- Loading branch information
Showing
1 changed file
with
279 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
package keeper | ||
|
||
import ( | ||
"testing" | ||
|
||
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" | ||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/stretchr/testify/require" | ||
|
||
moduletestutil "github.com/piplabs/story/client/x/evmengine/testutil" | ||
"github.com/piplabs/story/client/x/evmengine/types" | ||
"github.com/piplabs/story/contracts/bindings" | ||
"github.com/piplabs/story/lib/errors" | ||
"github.com/piplabs/story/lib/ethclient/mock" | ||
"github.com/piplabs/story/lib/k1util" | ||
"github.com/piplabs/story/lib/tutil" | ||
|
||
"go.uber.org/mock/gomock" | ||
) | ||
|
||
const ( | ||
dummyAddressHex = "0x1398C32A45Bc409b6C652E25bb0a3e702492A4ab" | ||
) | ||
|
||
var ( | ||
dummyContractAddress = common.HexToAddress(dummyAddressHex) | ||
dummyHash = common.HexToHash("0x1398C32A45Bc409b6C652E25bb0a3e702492A4ab") | ||
) | ||
|
||
func TestKeeper_ProcessSoftwareUpgrade(t *testing.T) { | ||
t.Parallel() | ||
keeper, ctx, ctrl, uk := setupTestEnvironment(t) | ||
t.Cleanup(ctrl.Finish) | ||
|
||
tcs := []struct { | ||
name string | ||
ev func() *bindings.UpgradeEntrypointSoftwareUpgrade | ||
setupMock func() | ||
expectedErr string | ||
}{ | ||
{ | ||
name: "pass: valid software upgrade event", | ||
ev: func() *bindings.UpgradeEntrypointSoftwareUpgrade { | ||
return &bindings.UpgradeEntrypointSoftwareUpgrade{ | ||
Name: "test-upgrade", | ||
Height: 1, | ||
Info: "test-info", | ||
} | ||
}, | ||
setupMock: func() { | ||
uk.EXPECT().ScheduleUpgrade(gomock.Any(), gomock.Any()).Return(nil) | ||
}, | ||
}, | ||
// Fail cases: The following test cases simulate basic error scenarios. | ||
// Since a mocked upgrade keeper is used, not all error cases can be tested here. | ||
// Comprehensive error testing would require the real upgrade keeper, which is beyond the scope of this unit test. | ||
{ | ||
name: "fail: invalid upgrade event - height is 0", | ||
ev: func() *bindings.UpgradeEntrypointSoftwareUpgrade { | ||
return &bindings.UpgradeEntrypointSoftwareUpgrade{ | ||
Name: "test upgrade", | ||
Height: 0, | ||
Info: "test-info", | ||
} | ||
}, | ||
setupMock: func() { | ||
uk.EXPECT().ScheduleUpgrade(gomock.Any(), gomock.Any()).Return(errors.New("height must be greater than 0")) | ||
}, | ||
expectedErr: "height must be greater than 0", | ||
}, | ||
{ | ||
name: "fail: invalid upgrade event - name is empty", | ||
ev: func() *bindings.UpgradeEntrypointSoftwareUpgrade { | ||
return &bindings.UpgradeEntrypointSoftwareUpgrade{ | ||
Name: "", | ||
Height: 1, | ||
Info: "test-info", | ||
} | ||
}, | ||
setupMock: func() { | ||
uk.EXPECT().ScheduleUpgrade(gomock.Any(), gomock.Any()).Return(errors.New("name cannot be empty")) | ||
}, | ||
expectedErr: "name cannot be empty", | ||
}, | ||
} | ||
|
||
for _, tc := range tcs { | ||
t.Run(tc.name, func(t *testing.T) { | ||
t.Parallel() | ||
tc.setupMock() | ||
err := keeper.ProcessSoftwareUpgrade(ctx, tc.ev()) | ||
if tc.expectedErr != "" { | ||
require.Error(t, err) | ||
require.Contains(t, err.Error(), tc.expectedErr) | ||
} else { | ||
require.NoError(t, err) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestKeeper_ProcessUpgradeEvents(t *testing.T) { | ||
t.Parallel() | ||
keeper, ctx, ctrl, uk := setupTestEnvironment(t) | ||
t.Cleanup(ctrl.Finish) | ||
|
||
upgradeAbi, err := bindings.UpgradeEntrypointMetaData.GetAbi() | ||
require.NoError(t, err, "failed to load ABI") | ||
|
||
tcs := []struct { | ||
name string | ||
evmEvents func() []*types.EVMEvent | ||
setupMock func() | ||
expectedErr string | ||
}{ | ||
{ | ||
name: "pass: nil events - nothing to process", | ||
evmEvents: func() []*types.EVMEvent { return nil }, | ||
}, | ||
{ | ||
name: "pass: empty events - nothing to process", | ||
evmEvents: func() []*types.EVMEvent { return []*types.EVMEvent{} }, | ||
}, | ||
{ | ||
name: "pass: one valid upgrade event", | ||
evmEvents: func() []*types.EVMEvent { | ||
data, err := upgradeAbi.Events["SoftwareUpgrade"].Inputs.NonIndexed().Pack("test-upgrade", int64(1), "test-info") | ||
require.NoError(t, err) | ||
|
||
return []*types.EVMEvent{ | ||
{ | ||
Address: dummyContractAddress.Bytes(), | ||
Topics: [][]byte{types.SoftwareUpgradeEvent.ID.Bytes()}, | ||
Data: data, | ||
}, | ||
} | ||
}, | ||
setupMock: func() { | ||
uk.EXPECT().ScheduleUpgrade(gomock.Any(), gomock.Any()).Return(nil) | ||
}, | ||
}, | ||
{ | ||
name: "pass: multiple valid upgrade events", | ||
evmEvents: func() []*types.EVMEvent { | ||
data1, err := upgradeAbi.Events["SoftwareUpgrade"].Inputs.NonIndexed().Pack("test-upgrade1", int64(2), "test-info") | ||
require.NoError(t, err) | ||
data2, err := upgradeAbi.Events["SoftwareUpgrade"].Inputs.NonIndexed().Pack("test-upgrade2", int64(3), "test-info") | ||
require.NoError(t, err) | ||
|
||
return []*types.EVMEvent{ | ||
{ | ||
Address: dummyContractAddress.Bytes(), | ||
Topics: [][]byte{types.SoftwareUpgradeEvent.ID.Bytes()}, | ||
Data: data1, | ||
}, | ||
{ | ||
Address: dummyContractAddress.Bytes(), | ||
Topics: [][]byte{types.SoftwareUpgradeEvent.ID.Bytes()}, | ||
Data: data2, | ||
}, | ||
} | ||
}, | ||
setupMock: func() { | ||
uk.EXPECT().ScheduleUpgrade(gomock.Any(), gomock.Any()).Return(nil).Times(2) | ||
}, | ||
}, | ||
// Failed but pass cases: The following test cases simulate basic error scenarios. | ||
// Since a mocked upgrade keeper is used, not all error cases can be tested here. | ||
// Comprehensive error testing would require the real upgrade keeper, which is beyond the scope of this unit test. | ||
{ | ||
name: "pass(failed but continue): invalid upgrade event - height is 0", | ||
evmEvents: func() []*types.EVMEvent { | ||
data, err := upgradeAbi.Events["SoftwareUpgrade"].Inputs.NonIndexed().Pack("test-upgrade", int64(0), "test-info") | ||
require.NoError(t, err) | ||
|
||
return []*types.EVMEvent{ | ||
{ | ||
Address: dummyContractAddress.Bytes(), | ||
Topics: [][]byte{types.SoftwareUpgradeEvent.ID.Bytes()}, | ||
Data: data, | ||
}, | ||
} | ||
}, | ||
setupMock: func() { | ||
uk.EXPECT().ScheduleUpgrade(gomock.Any(), gomock.Any()).Return(errors.New("height must be greater than 0")) | ||
}, | ||
}, | ||
{ | ||
name: "pass(failed but continue): invalid upgrade event - name is empty", | ||
evmEvents: func() []*types.EVMEvent { | ||
data, err := upgradeAbi.Events["SoftwareUpgrade"].Inputs.NonIndexed().Pack("", int64(5), "test-info") | ||
require.NoError(t, err) | ||
|
||
return []*types.EVMEvent{ | ||
{ | ||
Address: dummyContractAddress.Bytes(), | ||
Topics: [][]byte{types.SoftwareUpgradeEvent.ID.Bytes()}, | ||
Data: data, | ||
}, | ||
} | ||
}, | ||
setupMock: func() { | ||
uk.EXPECT().ScheduleUpgrade(gomock.Any(), gomock.Any()).Return(errors.New("name cannot be empty")) | ||
}, | ||
}, | ||
{ | ||
name: "pass(failed but continue): invalid upgrade event - not an upgrade event, it don't reach ProcessSoftwareUpgrade", | ||
evmEvents: func() []*types.EVMEvent { | ||
return []*types.EVMEvent{ | ||
{ | ||
Address: dummyContractAddress.Bytes(), | ||
Topics: [][]byte{types.SoftwareUpgradeEvent.ID.Bytes(), dummyHash.Bytes()}, | ||
}, | ||
} | ||
}, | ||
}, | ||
|
||
// Fail case: When given EVMEvent is not valid | ||
{ | ||
name: "fail: invalid EVMEvent", | ||
evmEvents: func() []*types.EVMEvent { | ||
return []*types.EVMEvent{ | ||
{ | ||
Address: []byte("invalid address"), | ||
Topics: [][]byte{types.SoftwareUpgradeEvent.ID.Bytes()}, | ||
}, | ||
} | ||
}, | ||
expectedErr: "invalid address length", | ||
}, | ||
} | ||
|
||
for _, tc := range tcs { | ||
t.Run(tc.name, func(t *testing.T) { | ||
t.Parallel() | ||
if tc.setupMock != nil { | ||
tc.setupMock() | ||
} | ||
cachedCtx, _ := ctx.CacheContext() | ||
err := keeper.ProcessUpgradeEvents(cachedCtx, 1, tc.evmEvents()) | ||
if tc.expectedErr != "" { | ||
require.Error(t, err) | ||
require.Contains(t, err.Error(), tc.expectedErr) | ||
} else { | ||
require.NoError(t, err) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func setupTestEnvironment(t *testing.T) (*Keeper, sdk.Context, *gomock.Controller, *moduletestutil.MockUpgradeKeeper) { | ||
t.Helper() | ||
cdc := getCodec(t) | ||
txConfig := authtx.NewTxConfig(cdc, nil) | ||
mockEngine, err := newMockEngineAPI(0) | ||
require.NoError(t, err) | ||
|
||
cmtAPI := newMockCometAPI(t, nil) | ||
header := cmtproto.Header{Height: 1, AppHash: tutil.RandomHash().Bytes(), ProposerAddress: cmtAPI.validatorSet.Validators[0].Address} | ||
ctrl := gomock.NewController(t) | ||
mockClient := mock.NewMockClient(ctrl) | ||
ak := moduletestutil.NewMockAccountKeeper(ctrl) | ||
esk := moduletestutil.NewMockEvmStakingKeeper(ctrl) | ||
uk := moduletestutil.NewMockUpgradeKeeper(ctrl) | ||
|
||
ctx, storeService := setupCtxStore(t, &header) | ||
|
||
keeper, err := NewKeeper(cdc, storeService, &mockEngine, mockClient, txConfig, ak, esk, uk) | ||
require.NoError(t, err) | ||
keeper.SetCometAPI(cmtAPI) | ||
nxtAddr, err := k1util.PubKeyToAddress(cmtAPI.validatorSet.Validators[1].PubKey) | ||
require.NoError(t, err) | ||
keeper.SetValidatorAddress(nxtAddr) | ||
populateGenesisHead(ctx, t, keeper) | ||
|
||
return keeper, ctx, ctrl, uk | ||
} |