From 89b90f72db3ff6e3c00f47a4769c7d1ce9e03fde Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Sun, 29 Sep 2024 12:06:26 -0500 Subject: [PATCH 01/14] feat(x/evmstaking): max unbond withdrawal --- client/x/evmstaking/keeper/abci.go | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/client/x/evmstaking/keeper/abci.go b/client/x/evmstaking/keeper/abci.go index da647b45..d7b98e88 100644 --- a/client/x/evmstaking/keeper/abci.go +++ b/client/x/evmstaking/keeper/abci.go @@ -90,17 +90,18 @@ func (k *Keeper) EndBlock(ctx context.Context) (abci.ValidatorUpdates, error) { } for _, entry := range unbondedEntries { - log.Debug(ctx, "Adding undelegation to withdrawal queue", - "delegator", entry.delegatorAddress, - "validator", entry.validatorAddress, - "amount", entry.amount.String()) - delegatorAddr, err := k.authKeeper.AddressCodec().StringToBytes(entry.delegatorAddress) if err != nil { return nil, errors.Wrap(err, "delegator address from bech32") } + + maxAmount := k.bankKeeper.GetBalance(ctx, delegatorAddr, sdk.DefaultBondDenom).Amount + if entry.amount.LT(maxAmount) { + maxAmount = entry.amount + } + // Burn tokens from the delegator - _, coins := IPTokenToBondCoin(entry.amount.BigInt()) + _, coins := IPTokenToBondCoin(maxAmount.BigInt()) err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, delegatorAddr, types.ModuleName, coins) if err != nil { return nil, errors.Wrap(err, "send coins from account to module") @@ -110,6 +111,16 @@ func (k *Keeper) EndBlock(ctx context.Context) (abci.ValidatorUpdates, error) { return nil, errors.Wrap(err, "burn coins") } + log.Debug(ctx, "Adding undelegation to withdrawal queue", + "delegator", entry.delegatorAddress, + "validator", entry.validatorAddress, + "max_amount", maxAmount.String()) + if entry.amount.LT(maxAmount) { + log.Debug(ctx, "Undelegation amount is less than max amount", + "original_amount", entry.amount.String(), + "max_amount", maxAmount.String()) + } + // This should not produce error, as all delegations are done via the evmstaking module via EL. // However, we should gracefully handle in case Get fails. delEvmAddr, err := k.DelegatorMap.Get(ctx, entry.delegatorAddress) @@ -123,7 +134,7 @@ func (k *Keeper) EndBlock(ctx context.Context) (abci.ValidatorUpdates, error) { entry.delegatorAddress, entry.validatorAddress, delEvmAddr, - entry.amount.Uint64(), + maxAmount.Uint64(), )) if err != nil { return nil, err From 58fc5ed47e96c40a35f146354df8977235dd59fa Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Sun, 29 Sep 2024 12:17:22 -0500 Subject: [PATCH 02/14] chore(x/evmstaking): bump log level --- client/x/evmstaking/keeper/abci.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/x/evmstaking/keeper/abci.go b/client/x/evmstaking/keeper/abci.go index d7b98e88..3d0d623b 100644 --- a/client/x/evmstaking/keeper/abci.go +++ b/client/x/evmstaking/keeper/abci.go @@ -116,7 +116,10 @@ func (k *Keeper) EndBlock(ctx context.Context) (abci.ValidatorUpdates, error) { "validator", entry.validatorAddress, "max_amount", maxAmount.String()) if entry.amount.LT(maxAmount) { - log.Debug(ctx, "Undelegation amount is less than max amount", + log.Warn(ctx, "Undelegation amount is less than max amount", + errors.New("undelegation amount is less than max amount"), + "delegator", entry.delegatorAddress, + "validator", entry.validatorAddress, "original_amount", entry.amount.String(), "max_amount", maxAmount.String()) } From dfdd687bd5274843b63b9e6ad053add4d69034dd Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Sun, 29 Sep 2024 12:27:53 -0500 Subject: [PATCH 03/14] feat(x/evmstaking): spendable coin max amount with tests --- client/x/evmstaking/keeper/abci.go | 7 +-- client/x/evmstaking/keeper/abci_test.go | 6 +++ .../testutil/expected_keepers_mocks.go | 52 +++++++------------ client/x/evmstaking/types/expected_keepers.go | 1 + 4 files changed, 29 insertions(+), 37 deletions(-) diff --git a/client/x/evmstaking/keeper/abci.go b/client/x/evmstaking/keeper/abci.go index 3d0d623b..6fbe818a 100644 --- a/client/x/evmstaking/keeper/abci.go +++ b/client/x/evmstaking/keeper/abci.go @@ -95,8 +95,9 @@ func (k *Keeper) EndBlock(ctx context.Context) (abci.ValidatorUpdates, error) { return nil, errors.Wrap(err, "delegator address from bech32") } - maxAmount := k.bankKeeper.GetBalance(ctx, delegatorAddr, sdk.DefaultBondDenom).Amount - if entry.amount.LT(maxAmount) { + maxAmount := k.bankKeeper.SpendableCoin(ctx, delegatorAddr, sdk.DefaultBondDenom).Amount + maxBounded := entry.amount.LT(maxAmount) + if maxBounded { maxAmount = entry.amount } @@ -115,7 +116,7 @@ func (k *Keeper) EndBlock(ctx context.Context) (abci.ValidatorUpdates, error) { "delegator", entry.delegatorAddress, "validator", entry.validatorAddress, "max_amount", maxAmount.String()) - if entry.amount.LT(maxAmount) { + if maxBounded { log.Warn(ctx, "Undelegation amount is less than max amount", errors.New("undelegation amount is less than max amount"), "delegator", entry.delegatorAddress, diff --git a/client/x/evmstaking/keeper/abci_test.go b/client/x/evmstaking/keeper/abci_test.go index ccf26b94..d300159f 100644 --- a/client/x/evmstaking/keeper/abci_test.go +++ b/client/x/evmstaking/keeper/abci_test.go @@ -5,6 +5,8 @@ import ( "testing" "time" + sdkmath "cosmossdk.io/math" + abcitypes "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" dtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" @@ -363,6 +365,7 @@ func compareValUpdates(t *testing.T, expected, actual abcitypes.ValidatorUpdates // setupMaturedUnbonding creates matured unbondings for testing. func (s *TestSuite) setupMatureUnbondingDelegation(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt string, duration time.Duration) { + require := s.Require() pastHeader := ctx.BlockHeader() pastHeader.Time = pastHeader.Time.Add(-duration).Add(-time.Minute) pastCtx := ctx.WithBlockHeader(pastHeader) @@ -372,6 +375,9 @@ func (s *TestSuite) setupMatureUnbondingDelegation(ctx sdk.Context, delAddr sdk. // Mock staking.EndBlocker s.BankKeeper.EXPECT().UndelegateCoinsFromModuleToAccount(gomock.Any(), stypes.NotBondedPoolName, delAddr, gomock.Any()).Return(nil) // Mock evmstaking.EndBlocker + amtInt, ok := sdkmath.NewIntFromString(amt) + require.True(ok) + s.BankKeeper.EXPECT().SpendableCoin(gomock.Any(), delAddr, sdk.DefaultBondDenom).Return(sdk.NewCoin(sdk.DefaultBondDenom, amtInt)) s.BankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), delAddr, types.ModuleName, gomock.Any()).Return(nil) s.BankKeeper.EXPECT().BurnCoins(gomock.Any(), types.ModuleName, gomock.Any()).Return(nil) } diff --git a/client/x/evmstaking/testutil/expected_keepers_mocks.go b/client/x/evmstaking/testutil/expected_keepers_mocks.go index ab85d653..c2360617 100644 --- a/client/x/evmstaking/testutil/expected_keepers_mocks.go +++ b/client/x/evmstaking/testutil/expected_keepers_mocks.go @@ -155,15 +155,15 @@ func (mr *MockAccountKeeperMockRecorder) SetAccount(ctx, acc any) *gomock.Call { } // SetModuleAccount mocks base method. -func (m *MockAccountKeeper) SetModuleAccount(arg0 context.Context, arg1 types0.ModuleAccountI) { +func (m *MockAccountKeeper) SetModuleAccount(ctx context.Context, modAcc types0.ModuleAccountI) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SetModuleAccount", arg0, arg1) + m.ctrl.Call(m, "SetModuleAccount", ctx, modAcc) } // SetModuleAccount indicates an expected call of SetModuleAccount. -func (mr *MockAccountKeeperMockRecorder) SetModuleAccount(arg0, arg1 any) *gomock.Call { +func (mr *MockAccountKeeperMockRecorder) SetModuleAccount(ctx, modAcc any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetModuleAccount", reflect.TypeOf((*MockAccountKeeper)(nil).SetModuleAccount), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetModuleAccount", reflect.TypeOf((*MockAccountKeeper)(nil).SetModuleAccount), ctx, modAcc) } // MockBankKeeper is a mock of BankKeeper interface. @@ -329,6 +329,20 @@ func (mr *MockBankKeeperMockRecorder) SendCoinsFromModuleToModule(ctx, senderPoo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoinsFromModuleToModule", reflect.TypeOf((*MockBankKeeper)(nil).SendCoinsFromModuleToModule), ctx, senderPool, recipientPool, amt) } +// SpendableCoin mocks base method. +func (m *MockBankKeeper) SpendableCoin(ctx context.Context, addr types0.AccAddress, denom string) types0.Coin { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SpendableCoin", ctx, addr, denom) + ret0, _ := ret[0].(types0.Coin) + return ret0 +} + +// SpendableCoin indicates an expected call of SpendableCoin. +func (mr *MockBankKeeperMockRecorder) SpendableCoin(ctx, addr, denom any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpendableCoin", reflect.TypeOf((*MockBankKeeper)(nil).SpendableCoin), ctx, addr, denom) +} + // SpendableCoins mocks base method. func (m *MockBankKeeper) SpendableCoins(ctx context.Context, addr types0.AccAddress) types0.Coins { m.ctrl.T.Helper() @@ -395,21 +409,6 @@ func (mr *MockStakingKeeperMockRecorder) BondDenom(ctx any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BondDenom", reflect.TypeOf((*MockStakingKeeper)(nil).BondDenom), ctx) } -// CompleteRedelegation mocks base method. -func (m *MockStakingKeeper) CompleteRedelegation(ctx context.Context, delAddr types0.AccAddress, valSrcAddr, valDstAddr types0.ValAddress) (types0.Coins, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CompleteRedelegation", ctx, delAddr, valSrcAddr, valDstAddr) - ret0, _ := ret[0].(types0.Coins) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CompleteRedelegation indicates an expected call of CompleteRedelegation. -func (mr *MockStakingKeeperMockRecorder) CompleteRedelegation(ctx, delAddr, valSrcAddr, valDstAddr any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CompleteRedelegation", reflect.TypeOf((*MockStakingKeeper)(nil).CompleteRedelegation), ctx, delAddr, valSrcAddr, valDstAddr) -} - // DeleteUnbondingIndex mocks base method. func (m *MockStakingKeeper) DeleteUnbondingIndex(ctx context.Context, id uint64) error { m.ctrl.T.Helper() @@ -424,21 +423,6 @@ func (mr *MockStakingKeeperMockRecorder) DeleteUnbondingIndex(ctx, id any) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUnbondingIndex", reflect.TypeOf((*MockStakingKeeper)(nil).DeleteUnbondingIndex), ctx, id) } -// DequeueAllMatureRedelegationQueue mocks base method. -func (m *MockStakingKeeper) DequeueAllMatureRedelegationQueue(ctx context.Context, currTime time.Time) ([]types2.DVVTriplet, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DequeueAllMatureRedelegationQueue", ctx, currTime) - ret0, _ := ret[0].([]types2.DVVTriplet) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// DequeueAllMatureRedelegationQueue indicates an expected call of DequeueAllMatureRedelegationQueue. -func (mr *MockStakingKeeperMockRecorder) DequeueAllMatureRedelegationQueue(ctx, currTime any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DequeueAllMatureRedelegationQueue", reflect.TypeOf((*MockStakingKeeper)(nil).DequeueAllMatureRedelegationQueue), ctx, currTime) -} - // EndBlocker mocks base method. func (m *MockStakingKeeper) EndBlocker(ctx context.Context) ([]types.ValidatorUpdate, error) { m.ctrl.T.Helper() diff --git a/client/x/evmstaking/types/expected_keepers.go b/client/x/evmstaking/types/expected_keepers.go index e4f5e20d..d3317890 100644 --- a/client/x/evmstaking/types/expected_keepers.go +++ b/client/x/evmstaking/types/expected_keepers.go @@ -40,6 +40,7 @@ type BankKeeper interface { GetAllBalances(ctx context.Context, addr sdk.AccAddress) sdk.Coins LockedCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins SpendableCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins + SpendableCoin(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin GetSupply(ctx context.Context, denom string) sdk.Coin SendCoinsFromModuleToModule(ctx context.Context, senderPool, recipientPool string, amt sdk.Coins) error } From d6e96b27dc40afbfc62665016676d15161685190 Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Sun, 29 Sep 2024 12:31:00 -0500 Subject: [PATCH 04/14] feat(x/evmstaking): pass zero amount --- client/x/evmstaking/keeper/abci.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/client/x/evmstaking/keeper/abci.go b/client/x/evmstaking/keeper/abci.go index 6fbe818a..142ad7cb 100644 --- a/client/x/evmstaking/keeper/abci.go +++ b/client/x/evmstaking/keeper/abci.go @@ -96,6 +96,16 @@ func (k *Keeper) EndBlock(ctx context.Context) (abci.ValidatorUpdates, error) { } maxAmount := k.bankKeeper.SpendableCoin(ctx, delegatorAddr, sdk.DefaultBondDenom).Amount + if maxAmount.IsZero() { + log.Warn(ctx, "No spendable coins for undelegation", + errors.New("no spendable coins for undelegation"), + "delegator", entry.delegatorAddress, + "validator", entry.validatorAddress, + "original_amount", entry.amount.String()) + + continue + } + maxBounded := entry.amount.LT(maxAmount) if maxBounded { maxAmount = entry.amount From 60a879f1087600a15cb46abadfcbcc5f6d487942 Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Sun, 29 Sep 2024 12:41:57 -0500 Subject: [PATCH 05/14] chore(x/evmstaking): move logs --- client/x/evmstaking/keeper/abci.go | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/client/x/evmstaking/keeper/abci.go b/client/x/evmstaking/keeper/abci.go index 142ad7cb..bbe4a64c 100644 --- a/client/x/evmstaking/keeper/abci.go +++ b/client/x/evmstaking/keeper/abci.go @@ -106,11 +106,21 @@ func (k *Keeper) EndBlock(ctx context.Context) (abci.ValidatorUpdates, error) { continue } - maxBounded := entry.amount.LT(maxAmount) - if maxBounded { + if entry.amount.LT(maxAmount) { maxAmount = entry.amount + log.Warn(ctx, "Undelegation amount is less than max amount", + errors.New("undelegation amount is less than max amount"), + "delegator", entry.delegatorAddress, + "validator", entry.validatorAddress, + "original_amount", entry.amount.String(), + "max_amount", maxAmount.String()) } + log.Debug(ctx, "Adding undelegation to withdrawal queue", + "delegator", entry.delegatorAddress, + "validator", entry.validatorAddress, + "max_amount", maxAmount.String()) + // Burn tokens from the delegator _, coins := IPTokenToBondCoin(maxAmount.BigInt()) err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, delegatorAddr, types.ModuleName, coins) @@ -122,19 +132,6 @@ func (k *Keeper) EndBlock(ctx context.Context) (abci.ValidatorUpdates, error) { return nil, errors.Wrap(err, "burn coins") } - log.Debug(ctx, "Adding undelegation to withdrawal queue", - "delegator", entry.delegatorAddress, - "validator", entry.validatorAddress, - "max_amount", maxAmount.String()) - if maxBounded { - log.Warn(ctx, "Undelegation amount is less than max amount", - errors.New("undelegation amount is less than max amount"), - "delegator", entry.delegatorAddress, - "validator", entry.validatorAddress, - "original_amount", entry.amount.String(), - "max_amount", maxAmount.String()) - } - // This should not produce error, as all delegations are done via the evmstaking module via EL. // However, we should gracefully handle in case Get fails. delEvmAddr, err := k.DelegatorMap.Get(ctx, entry.delegatorAddress) From 5328fa62b9be8b4970d51bde3c7a5501e23d3f8b Mon Sep 17 00:00:00 2001 From: Leeren Chang Date: Sun, 29 Sep 2024 14:02:06 -0700 Subject: [PATCH 06/14] chore(release): finalize client 0.10.1 stable release --- lib/buildinfo/buildinfo.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/buildinfo/buildinfo.go b/lib/buildinfo/buildinfo.go index 011b1ce0..4bf5301e 100644 --- a/lib/buildinfo/buildinfo.go +++ b/lib/buildinfo/buildinfo.go @@ -13,10 +13,10 @@ import ( ) const ( - VersionMajor = 0 // Major version component of the current release - VersionMinor = 10 // Minor version component of the current release - VersionPatch = 1 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 0 // Major version component of the current release + VersionMinor = 10 // Minor version component of the current release + VersionPatch = 1 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version returns the version of the whole story-monorepo and all binaries built from this git commit. From d8174aec31e207611d0864328ddd8bffafe930b5 Mon Sep 17 00:00:00 2001 From: Leeren Chang Date: Sun, 29 Sep 2024 14:05:41 -0700 Subject: [PATCH 07/14] chore(release): begin story v0.10.2 unstable release --- lib/buildinfo/buildinfo.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/buildinfo/buildinfo.go b/lib/buildinfo/buildinfo.go index 4bf5301e..c8bcf637 100644 --- a/lib/buildinfo/buildinfo.go +++ b/lib/buildinfo/buildinfo.go @@ -13,10 +13,10 @@ import ( ) const ( - VersionMajor = 0 // Major version component of the current release - VersionMinor = 10 // Minor version component of the current release - VersionPatch = 1 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 0 // Major version component of the current release + VersionMinor = 10 // Minor version component of the current release + VersionPatch = 2 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version returns the version of the whole story-monorepo and all binaries built from this git commit. From 736db2d102bb32e7e48595a9af231838847bd8d6 Mon Sep 17 00:00:00 2001 From: zsystm <124245155+zsystm@users.noreply.github.com> Date: Wed, 2 Oct 2024 08:17:33 +0900 Subject: [PATCH 08/14] feat(cmd): one block rollback (#157) add cometBFT's one block rollback cmd --- client/app/start.go | 44 ++++++++++++++++++++++++++++++ client/cmd/cmd.go | 60 +++++++++++++++++++++++++++++++++++++++++ client/cmd/flags.go | 4 +++ client/config/config.go | 1 + 4 files changed, 109 insertions(+) diff --git a/client/app/start.go b/client/app/start.go index ed9c8f36..5cd677ef 100644 --- a/client/app/start.go +++ b/client/app/start.go @@ -184,6 +184,50 @@ func Start(ctx context.Context, cfg Config) (func(context.Context) error, error) }, nil } +// TODO: Refactor CreateApp() to be used within the Start function, as most of the code originates from there. +func CreateApp(ctx context.Context, cfg Config) *App { + privVal, err := loadPrivVal(cfg) + if err != nil { + panic(errors.Wrap(err, "load validator key")) + } + + db, err := dbm.NewDB("application", cfg.BackendType(), cfg.DataDir()) + if err != nil { + panic(errors.Wrap(err, "create db")) + } + + baseAppOpts, err := makeBaseAppOpts(cfg) + if err != nil { + panic(errors.Wrap(err, "make base app opts")) + } + + engineCl, err := newEngineClient(ctx, cfg) + if err != nil { + panic(err) + } + + //nolint:contextcheck // False positive + app, err := newApp( + newSDKLogger(ctx), + db, + engineCl, + baseAppOpts..., + ) + if err != nil { + panic(errors.Wrap(err, "create app")) + } + app.Keepers.EVMEngKeeper.SetBuildDelay(cfg.EVMBuildDelay) + app.Keepers.EVMEngKeeper.SetBuildOptimistic(cfg.EVMBuildOptimistic) + + addr, err := k1util.PubKeyToAddress(privVal.Key.PrivKey.PubKey()) + if err != nil { + panic(errors.Wrap(err, "convert validator pubkey to address")) + } + app.Keepers.EVMEngKeeper.SetValidatorAddress(addr) + + return app +} + func newCometNode(ctx context.Context, cfg *cmtcfg.Config, app *App, privVal cmttypes.PrivValidator, ) (*node.Node, error) { nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile()) diff --git a/client/cmd/cmd.go b/client/cmd/cmd.go index 9d742e7e..295ac820 100644 --- a/client/cmd/cmd.go +++ b/client/cmd/cmd.go @@ -3,13 +3,16 @@ package cmd import ( "context" + "fmt" + cmtcmd "github.com/cometbft/cometbft/cmd/cometbft/commands" "github.com/spf13/cobra" "github.com/piplabs/story/client/app" storycfg "github.com/piplabs/story/client/config" "github.com/piplabs/story/lib/buildinfo" libcmd "github.com/piplabs/story/lib/cmd" + "github.com/piplabs/story/lib/errors" "github.com/piplabs/story/lib/log" ) @@ -23,6 +26,7 @@ func New() *cobra.Command { buildinfo.NewVersionCmd(), newValidatorCmds(), newStatusCmd(), + newRollbackCmd(app.CreateApp), ) } @@ -60,3 +64,59 @@ func newRunCmd(name string, runFunc func(context.Context, app.Config) error) *co return cmd } + +// newRollbackCmd returns a new cobra command that rolls back one block of the story consensus client. +func newRollbackCmd(appCreateFunc func(context.Context, app.Config) *app.App) *cobra.Command { + storyCfg := storycfg.DefaultConfig() + logCfg := log.DefaultConfig() + + cmd := &cobra.Command{ + Use: "rollback", + Short: "rollback Cosmos SDK and CometBFT state by one height", + Long: ` +A state rollback is performed to recover from an incorrect application state transition, +when CometBFT has persisted an incorrect app hash and is thus unable to make +progress. Rollback overwrites a state at height n with the state at height n - 1. +The application also rolls back to height n - 1. No blocks are removed, so upon +restarting CometBFT the transactions in block n will be re-executed against the +application. +`, + RunE: func(cmd *cobra.Command, _ []string) error { + ctx, err := log.Init(cmd.Context(), logCfg) + if err != nil { + return err + } + if err := libcmd.LogFlags(ctx, cmd.Flags()); err != nil { + return err + } + + cometCfg, err := parseCometConfig(ctx, storyCfg.HomeDir) + if err != nil { + return err + } + + app := appCreateFunc(ctx, app.Config{ + Config: storyCfg, + Comet: cometCfg, + }) + height, hash, err := cmtcmd.RollbackState(&cometCfg, storyCfg.RemoveBlock) + if err != nil { + return errors.Wrap(err, "failed to rollback CometBFT state") + } + + if err = app.CommitMultiStore().RollbackToVersion(height); err != nil { + return errors.Wrap(err, "failed to rollback to version") + } + + fmt.Printf("Rolled back state to height %d and hash %X", height, hash) + + return nil + }, + } + + bindRunFlags(cmd, &storyCfg) + bindRollbackFlags(cmd, &storyCfg) + log.BindFlags(cmd.Flags(), &logCfg) + + return cmd +} diff --git a/client/cmd/flags.go b/client/cmd/flags.go index b787b9dc..b0de2471 100644 --- a/client/cmd/flags.go +++ b/client/cmd/flags.go @@ -120,6 +120,10 @@ func bindStatusFlags(flags *pflag.FlagSet, cfg *StatusConfig) { libcmd.BindHomeFlag(flags, &cfg.HomeDir) } +func bindRollbackFlags(cmd *cobra.Command, cfg *config.Config) { + cmd.Flags().BoolVar(&cfg.RemoveBlock, "hard", false, "remove last block as well as state") +} + // Flag Validation func validateFlags(flags map[string]string) error { diff --git a/client/config/config.go b/client/config/config.go index c9bba36b..0a6dda76 100644 --- a/client/config/config.go +++ b/client/config/config.go @@ -160,6 +160,7 @@ type Config struct { ExternalAddress string Seeds string SeedMode bool + RemoveBlock bool // See cosmos-sdk/server/rollback.go } // ConfigFile returns the default path to the toml story config file. From 2c6e36cd7645f8fc7875fa42352971555b999af3 Mon Sep 17 00:00:00 2001 From: Leeren Chang Date: Wed, 2 Oct 2024 18:48:05 -0700 Subject: [PATCH 09/14] chore(release): finalize client 0.10.2 stable release --- lib/buildinfo/buildinfo.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/buildinfo/buildinfo.go b/lib/buildinfo/buildinfo.go index c8bcf637..0daa846f 100644 --- a/lib/buildinfo/buildinfo.go +++ b/lib/buildinfo/buildinfo.go @@ -13,10 +13,10 @@ import ( ) const ( - VersionMajor = 0 // Major version component of the current release - VersionMinor = 10 // Minor version component of the current release - VersionPatch = 2 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 0 // Major version component of the current release + VersionMinor = 10 // Minor version component of the current release + VersionPatch = 2 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version returns the version of the whole story-monorepo and all binaries built from this git commit. From 3a5cc2eb227df613d2801766deb3073c6683ff0d Mon Sep 17 00:00:00 2001 From: Leeren Chang Date: Wed, 2 Oct 2024 18:49:52 -0700 Subject: [PATCH 10/14] chore(release): begin client 0.11.0 unstable release --- lib/buildinfo/buildinfo.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/buildinfo/buildinfo.go b/lib/buildinfo/buildinfo.go index 0daa846f..8a8c128f 100644 --- a/lib/buildinfo/buildinfo.go +++ b/lib/buildinfo/buildinfo.go @@ -13,10 +13,10 @@ import ( ) const ( - VersionMajor = 0 // Major version component of the current release - VersionMinor = 10 // Minor version component of the current release - VersionPatch = 2 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 0 // Major version component of the current release + VersionMinor = 11 // Minor version component of the current release + VersionPatch = 0 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version returns the version of the whole story-monorepo and all binaries built from this git commit. From 8504fd0f0fb4e96af27df5460e4b30ab50c3bd66 Mon Sep 17 00:00:00 2001 From: Meng <69021250+limengformal@users.noreply.github.com> Date: Tue, 1 Oct 2024 15:11:00 -0700 Subject: [PATCH 11/14] fix(contracts): add v0.11.0 upgrade (#158) * fix(contracts): add v0.11.0 upgrade * fix var declaration --- client/app/upgrades.go | 2 ++ client/app/upgrades/v0_11_0/constants.go | 16 +++++++++ client/app/upgrades/v0_11_0/upgrades.go | 39 +++++++++++++++++++++ client/genutil/evm/predeploys/predeploys.go | 5 +++ client/x/evmengine/keeper/evmmsgs.go | 22 ++++++++++-- 5 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 client/app/upgrades/v0_11_0/constants.go create mode 100644 client/app/upgrades/v0_11_0/upgrades.go diff --git a/client/app/upgrades.go b/client/app/upgrades.go index 9a4c089d..32e2a3e1 100644 --- a/client/app/upgrades.go +++ b/client/app/upgrades.go @@ -9,6 +9,7 @@ import ( "github.com/piplabs/story/client/app/upgrades" "github.com/piplabs/story/client/app/upgrades/v0_10_0" + "github.com/piplabs/story/client/app/upgrades/v0_11_0" ) var ( @@ -16,6 +17,7 @@ var ( // New upgrades should be added to this slice after they are implemented. Upgrades = []upgrades.Upgrade{ v0_10_0.Upgrade, + v0_11_0.Upgrade, } // Forks are for hard forks that breaks backward compatibility. Forks = []upgrades.Fork{} diff --git a/client/app/upgrades/v0_11_0/constants.go b/client/app/upgrades/v0_11_0/constants.go new file mode 100644 index 00000000..3b2a72b7 --- /dev/null +++ b/client/app/upgrades/v0_11_0/constants.go @@ -0,0 +1,16 @@ +//nolint:revive,stylecheck // version underscores +package v0_11_0 + +import ( + storetypes "cosmossdk.io/store/types" + + "github.com/piplabs/story/client/app/upgrades" +) + +const UpgradeName = "v0.11.0" + +var Upgrade = upgrades.Upgrade{ + UpgradeName: UpgradeName, + CreateUpgradeHandler: CreateUpgradeHandler, + StoreUpgrades: storetypes.StoreUpgrades{}, +} diff --git a/client/app/upgrades/v0_11_0/upgrades.go b/client/app/upgrades/v0_11_0/upgrades.go new file mode 100644 index 00000000..272341d8 --- /dev/null +++ b/client/app/upgrades/v0_11_0/upgrades.go @@ -0,0 +1,39 @@ +//nolint:revive,stylecheck // version underscores +package v0_11_0 + +import ( + "context" + + upgradetypes "cosmossdk.io/x/upgrade/types" + + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/piplabs/story/client/app/keepers" + "github.com/piplabs/story/client/genutil/evm/predeploys" + "github.com/piplabs/story/lib/errors" + clog "github.com/piplabs/story/lib/log" +) + +func CreateUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + keepers *keepers.Keepers, +) upgradetypes.UpgradeHandler { + return func(ctx context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + clog.Info(ctx, "Starting module migrations...") + + vm, err := mm.RunMigrations(ctx, configurator, vm) + if err != nil { + return vm, errors.Wrap(err, "run migrations") + } + + clog.Info(ctx, "Setting updated IPTokenSlashing address...") + + // Upgrade to use the fixed slashing contract + predeploys.UpdatedIPTokenSlashing = "0xEEf1c4fD443965404f13BE2705766988317b3B32" + + clog.Info(ctx, "Upgrade v0.11.0 complete") + + return vm, nil + } +} diff --git a/client/genutil/evm/predeploys/predeploys.go b/client/genutil/evm/predeploys/predeploys.go index 838aaac5..83fd4750 100644 --- a/client/genutil/evm/predeploys/predeploys.go +++ b/client/genutil/evm/predeploys/predeploys.go @@ -39,6 +39,11 @@ const ( ) var ( + // IP Token Updated Addresses. Not used if empty. + UpdatedIPTokenStaking = "" + UpdatedIPTokenSlashing = "" + UpdatedUpgradeEntrypoint = "" + // Namespace big.Ints. storyNamespace = common.HexToAddress(StoryNamespace).Big() ipTokenNamespace = common.HexToAddress(IPTokenNamespace).Big() diff --git a/client/x/evmengine/keeper/evmmsgs.go b/client/x/evmengine/keeper/evmmsgs.go index 5b2d28f8..655070b9 100644 --- a/client/x/evmengine/keeper/evmmsgs.go +++ b/client/x/evmengine/keeper/evmmsgs.go @@ -18,13 +18,29 @@ import ( func (k *Keeper) evmEvents(ctx context.Context, blockHash common.Hash) ([]*types.EVMEvent, error) { var events []*types.EVMEvent + // identify updated addresses + var iPTokenStakingAddress = predeploys.IPTokenStaking + if predeploys.UpdatedIPTokenStaking != "" { + iPTokenStakingAddress = predeploys.UpdatedIPTokenStaking + } + + var iPTokenSlashingAddress = predeploys.IPTokenSlashing + if predeploys.UpdatedIPTokenSlashing != "" { + iPTokenSlashingAddress = predeploys.UpdatedIPTokenSlashing + } + + var upgradeEntrypointAddress = predeploys.UpgradeEntrypoint + if predeploys.UpdatedIPTokenStaking != "" { + upgradeEntrypointAddress = predeploys.UpdatedUpgradeEntrypoint + } + logs, err := k.engineCl.FilterLogs(ctx, ethereum.FilterQuery{ BlockHash: &blockHash, // only IPTokenStaking contract Addresses: []common.Address{ - common.HexToAddress(predeploys.IPTokenStaking), - common.HexToAddress(predeploys.IPTokenSlashing), - common.HexToAddress(predeploys.UpgradeEntrypoint), + common.HexToAddress(iPTokenStakingAddress), + common.HexToAddress(iPTokenSlashingAddress), + common.HexToAddress(upgradeEntrypointAddress), }, }) if err != nil { From cddfe8b03400546ab9ba7108b0900f8234d5749a Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Tue, 1 Oct 2024 20:23:54 -0500 Subject: [PATCH 12/14] fix(x/evmstaking): endblock unbond branch check (#163) * fix(x/evmstaking): branch condition on max spendable * test(x/evmstaking): add expected call in tests --- client/x/evmstaking/keeper/abci.go | 24 +++++++++++++----------- client/x/evmstaking/keeper/abci_test.go | 2 ++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/client/x/evmstaking/keeper/abci.go b/client/x/evmstaking/keeper/abci.go index bbe4a64c..802fa771 100644 --- a/client/x/evmstaking/keeper/abci.go +++ b/client/x/evmstaking/keeper/abci.go @@ -95,8 +95,8 @@ func (k *Keeper) EndBlock(ctx context.Context) (abci.ValidatorUpdates, error) { return nil, errors.Wrap(err, "delegator address from bech32") } - maxAmount := k.bankKeeper.SpendableCoin(ctx, delegatorAddr, sdk.DefaultBondDenom).Amount - if maxAmount.IsZero() { + spendableAmount := k.bankKeeper.SpendableCoin(ctx, delegatorAddr, sdk.DefaultBondDenom).Amount + if spendableAmount.IsZero() { log.Warn(ctx, "No spendable coins for undelegation", errors.New("no spendable coins for undelegation"), "delegator", entry.delegatorAddress, @@ -106,23 +106,25 @@ func (k *Keeper) EndBlock(ctx context.Context) (abci.ValidatorUpdates, error) { continue } - if entry.amount.LT(maxAmount) { - maxAmount = entry.amount - log.Warn(ctx, "Undelegation amount is less than max amount", - errors.New("undelegation amount is less than max amount"), + // If the requested undelegation amount is greater than the spendable amount, set the real undelegation amount to + // the total spendable amount. + if entry.amount.GT(spendableAmount) { + entry.amount = spendableAmount + log.Warn(ctx, "Spendable amount is less than the requested undelegation amount", + errors.New("spendable amount is less than the requested undelegation amount"), "delegator", entry.delegatorAddress, "validator", entry.validatorAddress, - "original_amount", entry.amount.String(), - "max_amount", maxAmount.String()) + "requested_amount", entry.amount.String(), + "spendable_amount", spendableAmount.String()) } log.Debug(ctx, "Adding undelegation to withdrawal queue", "delegator", entry.delegatorAddress, "validator", entry.validatorAddress, - "max_amount", maxAmount.String()) + "amount", entry.amount.String()) // Burn tokens from the delegator - _, coins := IPTokenToBondCoin(maxAmount.BigInt()) + _, coins := IPTokenToBondCoin(entry.amount.BigInt()) err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, delegatorAddr, types.ModuleName, coins) if err != nil { return nil, errors.Wrap(err, "send coins from account to module") @@ -145,7 +147,7 @@ func (k *Keeper) EndBlock(ctx context.Context) (abci.ValidatorUpdates, error) { entry.delegatorAddress, entry.validatorAddress, delEvmAddr, - maxAmount.Uint64(), + entry.amount.Uint64(), )) if err != nil { return nil, err diff --git a/client/x/evmstaking/keeper/abci_test.go b/client/x/evmstaking/keeper/abci_test.go index d300159f..dcf1dba4 100644 --- a/client/x/evmstaking/keeper/abci_test.go +++ b/client/x/evmstaking/keeper/abci_test.go @@ -247,6 +247,7 @@ func (s *TestSuite) TestEndBlock() { // Mock staking.EndBlocker s.BankKeeper.EXPECT().UndelegateCoinsFromModuleToAccount(gomock.Any(), stypes.NotBondedPoolName, delAddr, gomock.Any()).Return(nil) // Mock evmstaking.EndBlocker + s.BankKeeper.EXPECT().SpendableCoin(gomock.Any(), delAddr, sdk.DefaultBondDenom).Return(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(10))) s.BankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), delAddr, types.ModuleName, gomock.Any()).Return(errors.New("failed to send coins to module")) return nil, []abcitypes.ValidatorUpdate{ @@ -273,6 +274,7 @@ func (s *TestSuite) TestEndBlock() { // Mock staking.EndBlocker s.BankKeeper.EXPECT().UndelegateCoinsFromModuleToAccount(gomock.Any(), stypes.NotBondedPoolName, delAddr, gomock.Any()).Return(nil) // Mock evmstaking.EndBlocker + s.BankKeeper.EXPECT().SpendableCoin(gomock.Any(), delAddr, sdk.DefaultBondDenom).Return(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(10))) s.BankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), delAddr, types.ModuleName, gomock.Any()).Return(nil) s.BankKeeper.EXPECT().BurnCoins(gomock.Any(), types.ModuleName, gomock.Any()).Return(errors.New("failed to burn coins")) From 691425dfc3a8b13279250043977b637ae4a54757 Mon Sep 17 00:00:00 2001 From: Meng <69021250+limengformal@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:58:35 -0700 Subject: [PATCH 13/14] fix(contracts): update v0.11.0 upgrade (#166) * fix(contracts): update v0.11.0 upgrade * upgrade slashing contract address --- client/app/upgrades/v0_11_0/upgrades.go | 6 ------ client/genutil/evm/predeploys/predeploys.go | 7 +------ client/x/evmengine/keeper/evmmsgs.go | 22 +++------------------ 3 files changed, 4 insertions(+), 31 deletions(-) diff --git a/client/app/upgrades/v0_11_0/upgrades.go b/client/app/upgrades/v0_11_0/upgrades.go index 272341d8..89f7a8bd 100644 --- a/client/app/upgrades/v0_11_0/upgrades.go +++ b/client/app/upgrades/v0_11_0/upgrades.go @@ -9,7 +9,6 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" "github.com/piplabs/story/client/app/keepers" - "github.com/piplabs/story/client/genutil/evm/predeploys" "github.com/piplabs/story/lib/errors" clog "github.com/piplabs/story/lib/log" ) @@ -27,11 +26,6 @@ func CreateUpgradeHandler( return vm, errors.Wrap(err, "run migrations") } - clog.Info(ctx, "Setting updated IPTokenSlashing address...") - - // Upgrade to use the fixed slashing contract - predeploys.UpdatedIPTokenSlashing = "0xEEf1c4fD443965404f13BE2705766988317b3B32" - clog.Info(ctx, "Upgrade v0.11.0 complete") return vm, nil diff --git a/client/genutil/evm/predeploys/predeploys.go b/client/genutil/evm/predeploys/predeploys.go index 83fd4750..3df4a032 100644 --- a/client/genutil/evm/predeploys/predeploys.go +++ b/client/genutil/evm/predeploys/predeploys.go @@ -28,7 +28,7 @@ const ( // IP Token Predeploys. IPTokenStaking = "0xcccccc0000000000000000000000000000000001" - IPTokenSlashing = "0xcccccc0000000000000000000000000000000002" + IPTokenSlashing = "0xa39241Eb9Ff830178339D1E6aD38EfB160Ee9ab1" UpgradeEntrypoint = "0xcccccc0000000000000000000000000000000003" Secp256k1 = "0x00000000000000000000000000000000000256f1" @@ -39,11 +39,6 @@ const ( ) var ( - // IP Token Updated Addresses. Not used if empty. - UpdatedIPTokenStaking = "" - UpdatedIPTokenSlashing = "" - UpdatedUpgradeEntrypoint = "" - // Namespace big.Ints. storyNamespace = common.HexToAddress(StoryNamespace).Big() ipTokenNamespace = common.HexToAddress(IPTokenNamespace).Big() diff --git a/client/x/evmengine/keeper/evmmsgs.go b/client/x/evmengine/keeper/evmmsgs.go index 655070b9..5b2d28f8 100644 --- a/client/x/evmengine/keeper/evmmsgs.go +++ b/client/x/evmengine/keeper/evmmsgs.go @@ -18,29 +18,13 @@ import ( func (k *Keeper) evmEvents(ctx context.Context, blockHash common.Hash) ([]*types.EVMEvent, error) { var events []*types.EVMEvent - // identify updated addresses - var iPTokenStakingAddress = predeploys.IPTokenStaking - if predeploys.UpdatedIPTokenStaking != "" { - iPTokenStakingAddress = predeploys.UpdatedIPTokenStaking - } - - var iPTokenSlashingAddress = predeploys.IPTokenSlashing - if predeploys.UpdatedIPTokenSlashing != "" { - iPTokenSlashingAddress = predeploys.UpdatedIPTokenSlashing - } - - var upgradeEntrypointAddress = predeploys.UpgradeEntrypoint - if predeploys.UpdatedIPTokenStaking != "" { - upgradeEntrypointAddress = predeploys.UpdatedUpgradeEntrypoint - } - logs, err := k.engineCl.FilterLogs(ctx, ethereum.FilterQuery{ BlockHash: &blockHash, // only IPTokenStaking contract Addresses: []common.Address{ - common.HexToAddress(iPTokenStakingAddress), - common.HexToAddress(iPTokenSlashingAddress), - common.HexToAddress(upgradeEntrypointAddress), + common.HexToAddress(predeploys.IPTokenStaking), + common.HexToAddress(predeploys.IPTokenSlashing), + common.HexToAddress(predeploys.UpgradeEntrypoint), }, }) if err != nil { From 9555cc32a91c99a067c3465a942c041e8daab0ed Mon Sep 17 00:00:00 2001 From: Ramarti Date: Fri, 4 Oct 2024 16:59:25 -0300 Subject: [PATCH 14/14] feat(contracts): deploy script for iptokenslashing (#159) * deploy script for iptokenslashing * use solady CREATE3 to deploy IPTokenSlashing deterministically * fix create3 deployment * use create2 to deploy create3 deployer * use predeploy create3, make sure admin is not deployer --- contracts/package.json | 1 + contracts/pnpm-lock.yaml | 8 +++ contracts/script/DeployCore.s.sol | 4 +- contracts/script/DeployIPTokenSlashing.s.sol | 72 ++++++++++++++++++++ contracts/src/deploy/Create3.sol | 40 ----------- contracts/src/deploy/ICreate3Deployer.sol | 15 ++++ contracts/test/deploy/Create3.t.sol | 13 +--- contracts/test/utils/Test.sol | 27 ++++++-- 8 files changed, 121 insertions(+), 59 deletions(-) create mode 100644 contracts/script/DeployIPTokenSlashing.s.sol delete mode 100644 contracts/src/deploy/Create3.sol create mode 100644 contracts/src/deploy/ICreate3Deployer.sol diff --git a/contracts/package.json b/contracts/package.json index 9411ca17..d726184c 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -34,6 +34,7 @@ "dependencies": { "@openzeppelin/contracts": "5.0.2", "@openzeppelin/contracts-upgradeable": "5.0.2", + "solady": "^0.0.246", "solmate": "^6.2.0" } } diff --git a/contracts/pnpm-lock.yaml b/contracts/pnpm-lock.yaml index 08901052..8c95d287 100644 --- a/contracts/pnpm-lock.yaml +++ b/contracts/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@openzeppelin/contracts-upgradeable': specifier: 5.0.2 version: 5.0.2(@openzeppelin/contracts@5.0.2) + solady: + specifier: ^0.0.246 + version: 0.0.246 solmate: specifier: ^6.2.0 version: 6.2.0 @@ -655,6 +658,9 @@ packages: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} + solady@0.0.246: + resolution: {integrity: sha512-SotcVbKUcz/d3aa4U58mIjxeOJwjSW5pLtPJJbpzm+N2F4iKfRrolMTW0M5nKwhrZcwqDnUGpNdlykZSvhd27g==} + solhint-community@4.0.0: resolution: {integrity: sha512-BERw3qYzkJE64EwvYrp2+iiTN8yAZOJ74FCiL4bTBp7v0JFUvRYCEGZKAqfHcfi/koKkzM6qThsJUceKm9vvfg==} hasBin: true @@ -1368,6 +1374,8 @@ snapshots: astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 + solady@0.0.246: {} + solhint-community@4.0.0(typescript@5.5.3): dependencies: '@solidity-parser/parser': 0.16.2 diff --git a/contracts/script/DeployCore.s.sol b/contracts/script/DeployCore.s.sol index 59aa012a..249cb4b4 100644 --- a/contracts/script/DeployCore.s.sol +++ b/contracts/script/DeployCore.s.sol @@ -53,10 +53,10 @@ contract DeployCore is Script { UpgradeEntrypoint upgradeEntrypoint = UpgradeEntrypoint(address(new ERC1967Proxy(impl, ""))); upgradeEntrypoint.initialize(protocolAccessManagerAddr); - vm.stopBroadcast(); - console2.log("IPTokenStaking deployed at:", address(ipTokenStaking)); console2.log("IPTokenSlashing deployed at:", address(ipTokenSlashing)); console2.log("UpgradeEntrypoint deployed at:", address(upgradeEntrypoint)); + + vm.stopBroadcast(); } } diff --git a/contracts/script/DeployIPTokenSlashing.s.sol b/contracts/script/DeployIPTokenSlashing.s.sol new file mode 100644 index 00000000..59e7291c --- /dev/null +++ b/contracts/script/DeployIPTokenSlashing.s.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.23; +/* solhint-disable no-console */ +/* solhint-disable max-line-length */ + +import { Script } from "forge-std/Script.sol"; +import { console2 } from "forge-std/console2.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +import { IPTokenSlashing } from "../src/protocol/IPTokenSlashing.sol"; +import { ICreate3Deployer } from "../src/deploy/ICreate3Deployer.sol"; + +/** + * @title DeployIPTokenSlashing + * @dev A script to deploy IPTokenSlashing for Illiad + */ +contract DeployIPTokenSlashing is Script { + // To run the script: + // - Dry run + // forge script script/DeployIPTokenSlashing.s.sol --fork-url + // + // - Deploy (OK for devnet) + // forge script script/DeployIPTokenSlashing.s.sol --fork-url --broadcast + // + // - Deploy and Verify (for testnet) + // forge script script/DeployIPTokenSlashing.s.sol --fork-url https://testnet.storyrpc.io --broadcast --verify --verifier blockscout --verifier-url https://testnet.storyscan.xyz/api\? + function run() public { + // Read env for admin address + address protocolAccessManagerAddr = vm.envAddress("ADMIN_ADDRESS"); + require(protocolAccessManagerAddr != address(0), "address not set"); + // Read env for deployer private key + uint256 deployerKey = vm.envUint("IPTOKENSTAKING_DEPLOYER_KEY"); + address deployer = vm.addr(deployerKey); + require(deployer != protocolAccessManagerAddr, "Deployer wallet can't be admin address"); + console2.log("deployer", deployer); + vm.startBroadcast(deployerKey); + + ICreate3Deployer c3Deployer = ICreate3Deployer(0x384a891dFDE8180b054f04D66379f16B7a678Ad6); + console2.log("Create3 deployer:", address(c3Deployer)); + + address ipTokenStaking = 0xCCcCcC0000000000000000000000000000000001; + + address impl = address(new IPTokenSlashing(ipTokenStaking)); + bytes memory initializationData = abi.encodeCall( + IPTokenSlashing.initialize, + ( + protocolAccessManagerAddr, + 1 ether // unjailFee + ) + ); + bytes memory creationCode = + abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(impl, initializationData)); + + bytes32 salt = keccak256(abi.encode("STORY", type(IPTokenSlashing).name)); + address predicted = c3Deployer.getDeployed(salt); + console2.log("IPTokenSlashing will be deployed at:", predicted); + IPTokenSlashing ipTokenSlashing = IPTokenSlashing(c3Deployer.deploy(salt, creationCode)); + + console2.log("IP_TOKEN_STAKING", address(ipTokenSlashing.IP_TOKEN_STAKING())); + console2.log("owner:", ipTokenSlashing.owner()); + console2.log("unjailFee:", ipTokenSlashing.unjailFee()); + + if (address(ipTokenSlashing) != predicted) { + revert("IPTokenSlashing mismatch"); + } + console2.log("IPTokenSlashing deployed at:", address(ipTokenSlashing)); + + vm.stopBroadcast(); + } + + +} diff --git a/contracts/src/deploy/Create3.sol b/contracts/src/deploy/Create3.sol deleted file mode 100644 index c7129f2a..00000000 --- a/contracts/src/deploy/Create3.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity ^0.8.23; - -import { CREATE3 } from "solmate/src/utils/CREATE3.sol"; - -/** - * @title Create3 - * @notice Factory for deploying contracts to deterministic addresses via CREATE3 Enables deploying - * contracts using CREATE3. Each deployer (msg.sender) has its own namespace for deployed - * addresses. - * @author zefram.eth - * @custom:attribution zefram.eth (https://github.com/ZeframLou/create3-factory/blob/main/src/CREATE3Factory.sol) - */ -contract Create3 { - /** - * @notice Deploys a contract using CREATE3 - * @dev The provided salt is hashed together with msg.sender to generate the final salt - * @param salt The deployer-specific salt for determining the deployed contract's address - * @param creationCode The creation code of the contract to deploy - * @return deployed The address of the deployed contract - */ - function deploy(bytes32 salt, bytes memory creationCode) external payable returns (address deployed) { - // hash salt with the deployer address to give each deployer its own namespace - salt = keccak256(abi.encodePacked(msg.sender, salt)); - return CREATE3.deploy(salt, creationCode, msg.value); - } - - /** - * @notice Predicts the address of a deployed contract - * @dev The provided salt is hashed together with the deployer address to generate the final salt - * @param deployer The deployer account that will call deploy() - * @param salt The deployer-specific salt for determining the deployed contract's address - * @return deployed The address of the contract that will be deployed - */ - function getDeployed(address deployer, bytes32 salt) external view returns (address deployed) { - // hash salt with the deployer address to give each deployer its own namespace - salt = keccak256(abi.encodePacked(deployer, salt)); - return CREATE3.getDeployed(salt); - } -} diff --git a/contracts/src/deploy/ICreate3Deployer.sol b/contracts/src/deploy/ICreate3Deployer.sol new file mode 100644 index 00000000..f983e702 --- /dev/null +++ b/contracts/src/deploy/ICreate3Deployer.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +interface ICreate3Deployer { + /// @notice Deploys a contract using CREATE3 + /// @param salt The salt to use for deployment + /// @param creationCode The contract creation code + /// @return deployed The address of the deployed contract + function deploy(bytes32 salt, bytes calldata creationCode) external payable returns (address); + + /// @notice Predicts the address of a deployed contract + /// @param salt The salt to use for deployment + /// @return deployed The address of the contract that will be deployed + function getDeployed(bytes32 salt) external view returns (address); +} diff --git a/contracts/test/deploy/Create3.t.sol b/contracts/test/deploy/Create3.t.sol index 0cff576f..d1ca9b4b 100644 --- a/contracts/test/deploy/Create3.t.sol +++ b/contracts/test/deploy/Create3.t.sol @@ -21,24 +21,17 @@ contract Create3Test is Test { bytes32 salt = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; bytes memory creationCode = type(Create3).creationCode; address deployed = create3.deploy(salt, creationCode); - address expected = create3.getDeployed(address(this), salt); + address expected = create3.getDeployed(salt); assertEq(deployed, expected); // Network shall generate the same address for the same deployer and salt. - vm.expectRevert("DEPLOYMENT_FAILED"); + vm.expectRevert(); deployed = create3.deploy(salt, creationCode); - // Network shall generate different addresses for different deployers. - address otherAddr = address(0xf398C12A45Bc409b6C652E25bb0a3e702492A4ab); - vm.prank(otherAddr); - deployed = create3.deploy(salt, creationCode); - expected = create3.getDeployed(otherAddr, salt); - assertEq(deployed, expected); - // Network shall generate different addresses for different salts. bytes32 otherSalt = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890fedcba; deployed = create3.deploy(otherSalt, creationCode); - expected = create3.getDeployed(address(this), otherSalt); + expected = create3.getDeployed(otherSalt); assertEq(deployed, expected); } } diff --git a/contracts/test/utils/Test.sol b/contracts/test/utils/Test.sol index b5509d88..59d6bf65 100644 --- a/contracts/test/utils/Test.sol +++ b/contracts/test/utils/Test.sol @@ -10,6 +10,7 @@ import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy import { IPTokenStaking } from "../../src/protocol/IPTokenStaking.sol"; import { IPTokenSlashing } from "../../src/protocol/IPTokenSlashing.sol"; import { UpgradeEntrypoint } from "../../src/protocol/UpgradeEntrypoint.sol"; +import { Create3 } from "../../src/deploy/Create3.sol"; contract Test is ForgeTest { address internal admin = address(0x123); @@ -33,22 +34,33 @@ contract Test is ForgeTest { 500 // defaultMaxCommissionChangeRate, 5% ) ); - bytes memory initializer = abi.encodeCall( - IPTokenStaking.initialize, - (admin, 1 ether, 1 ether, 1 ether, 7 days) - ); + bytes memory initializer = abi.encodeCall(IPTokenStaking.initialize, (admin, 1 ether, 1 ether, 1 ether, 7 days)); ipTokenStaking = IPTokenStaking(address(new ERC1967Proxy(impl, initializer))); } function setSlashing() internal { require(address(ipTokenStaking) != address(0), "ipTokenStaking not set"); + Create3 c3Deployer = new Create3(); + address impl = address(new IPTokenSlashing(address(ipTokenStaking))); + bytes memory initializationData = abi.encodeCall( + IPTokenSlashing.initialize, + ( + admin, + 1 ether // unjailFee + ) + ); + bytes memory creationCode = + abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(impl, initializationData)); + bytes32 salt = keccak256(abi.encode("STORY", type(IPTokenSlashing).name)); + address predicted = c3Deployer.getDeployed(salt); + ipTokenSlashing = IPTokenSlashing(c3Deployer.deploy(salt, creationCode)); - bytes memory initializer = abi.encodeCall(IPTokenSlashing.initialize, (admin, 1 ether)); - ipTokenSlashing = IPTokenSlashing(address(new ERC1967Proxy(impl, initializer))); + if (address(ipTokenSlashing) != predicted) { + revert("IPTokenSlashing mismatch"); + } - console2.log("unjailFee:", ipTokenSlashing.unjailFee()); } function setUpgrade() internal { @@ -57,4 +69,5 @@ contract Test is ForgeTest { bytes memory initializer = abi.encodeWithSignature("initialize(address)", admin); upgradeEntrypoint = UpgradeEntrypoint(address(new ERC1967Proxy(impl, initializer))); } + }