From 1bf31fa8fe213187e6b479ea17c66225d703aa11 Mon Sep 17 00:00:00 2001 From: seolaoh Date: Mon, 29 Jul 2024 16:10:58 +0900 Subject: [PATCH 1/2] feat(validator): assert output submission condition before submit tx --- kroma-validator/l2_output_submitter.go | 42 +++++++++++++++----------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/kroma-validator/l2_output_submitter.go b/kroma-validator/l2_output_submitter.go index 515435def..7ed95af3f 100644 --- a/kroma-validator/l2_output_submitter.go +++ b/kroma-validator/l2_output_submitter.go @@ -159,15 +159,16 @@ func (l *L2OutputSubmitter) InitConfig(ctx context.Context) error { if err != nil { if errors.Is(err, errors.New("method 'BOND_AMOUNT' not found")) { requiredBondAmountV2 = big.NewInt(0) + } else { + return fmt.Errorf("failed to get required bond amount: %w", err) } - return fmt.Errorf("failed to get required bond amount: %w", err) } l.requiredBondAmountV2 = requiredBondAmountV2 return nil }) if err != nil { - return fmt.Errorf("failed to initiate assetMgr config: %w, err") + return fmt.Errorf("failed to initiate assetMgr config: %w", err) } return nil @@ -230,14 +231,16 @@ func (l *L2OutputSubmitter) repeatSubmitL2Output(ctx context.Context) { // If it needs to wait, it will calculate how long the validator should wait and // try again after the delay. func (l *L2OutputSubmitter) trySubmitL2Output(ctx context.Context) (time.Duration, error) { + defaultWaitTime := l.cfg.OutputSubmitterRetryInterval + nextBlockNumber, err := l.FetchNextBlockNumber(ctx) if err != nil { - return l.cfg.OutputSubmitterRetryInterval, err + return defaultWaitTime, err } outputIndex, err := l.FetchNextOutputIndex(ctx) if err != nil { - return l.cfg.OutputSubmitterRetryInterval, err + return defaultWaitTime, err } calculatedWaitTime := l.CalculateWaitTime(ctx, nextBlockNumber, outputIndex) @@ -245,8 +248,13 @@ func (l *L2OutputSubmitter) trySubmitL2Output(ctx context.Context) (time.Duratio return calculatedWaitTime, nil } + canSubmitOutput, err := l.CanSubmitOutput(ctx, outputIndex) + if err != nil || !canSubmitOutput { + return defaultWaitTime, err + } + if err = l.doSubmitL2Output(ctx, nextBlockNumber, outputIndex); err != nil { - return l.cfg.OutputSubmitterRetryInterval, err + return defaultWaitTime, err } // successfully submitted. start next loop immediately. @@ -286,7 +294,7 @@ func (l *L2OutputSubmitter) CalculateWaitTime(ctx context.Context, nextBlockNumb return defaultWaitTime } - if err = l.assertCanSubmitOutput(ctx, outputIndex); err != nil { + if _, err = l.CanSubmitOutput(ctx, outputIndex); err != nil { l.log.Error("failed to check the validator can submit output", "err", err) return defaultWaitTime } @@ -321,8 +329,8 @@ func (l *L2OutputSubmitter) CalculateWaitTime(ctx context.Context, nextBlockNumb return 0 } -// assertCanSubmitOutput asserts that the validator satisfies the condition to submit L2Output. -func (l *L2OutputSubmitter) assertCanSubmitOutput(ctx context.Context, outputIndex *big.Int) error { +// CanSubmitOutput checks that the validator satisfies the condition to submit L2Output. +func (l *L2OutputSubmitter) CanSubmitOutput(ctx context.Context, outputIndex *big.Int) (bool, error) { cCtx, cCancel := context.WithTimeout(ctx, l.cfg.NetworkTimeout) defer cCancel() from := l.cfg.TxManager.From() @@ -330,37 +338,36 @@ func (l *L2OutputSubmitter) assertCanSubmitOutput(ctx context.Context, outputInd var balance, requiredBondAmount *big.Int if l.IsValPoolTerminated(outputIndex) { if isInJail, err := l.IsInJail(ctx); err != nil { - return err + return false, err } else if isInJail { l.log.Warn("validator is in jail") - return nil + return false, nil } validatorStatus, err := l.GetValidatorStatus(ctx) if err != nil { - return err + return false, err } l.metr.RecordValidatorStatus(validatorStatus) if validatorStatus != StatusActive { l.log.Warn("validator is not in the status to submit output", "currentStatus", validatorStatus) - return nil + return false, nil } balance, err = l.assetMgrContract.TotalValidatorKroNotBonded(optsutils.NewSimpleCallOpts(cCtx), from) if err != nil { - return fmt.Errorf("failed to fetch balance: %w", err) + return false, fmt.Errorf("failed to fetch balance: %w", err) } requiredBondAmount = l.requiredBondAmountV2 } else { var err error balance, err = l.valPoolContract.BalanceOf(optsutils.NewSimpleCallOpts(cCtx), from) if err != nil { - return fmt.Errorf("failed to fetch deposit amount: %w", err) + return false, fmt.Errorf("failed to fetch deposit amount: %w", err) } requiredBondAmount = l.requiredBondAmountV1 } - l.metr.RecordUnbondedDepositAmount(balance) // Check if the unbonded deposit amount is less than the required bond amount @@ -370,12 +377,11 @@ func (l *L2OutputSubmitter) assertCanSubmitOutput(ctx context.Context, outputInd "requiredBondAmount", requiredBondAmount, "unbonded_deposit", balance, ) - return nil + return false, nil } l.log.Info("unbonded deposit amount and bond amount", "unbonded_deposit", balance, "bond", requiredBondAmount) - - return nil + return true, nil } func (l *L2OutputSubmitter) FetchNextOutputIndex(ctx context.Context) (*big.Int, error) { From 32e2f26ca751bfbc59cff61c492c117cba45b0ac Mon Sep 17 00:00:00 2001 From: seolaoh Date: Mon, 29 Jul 2024 16:11:35 +0900 Subject: [PATCH 2/2] test(validator): modify e2e tests related to V2 --- .../testdata/test-deploy-config-full.json | 2 +- kroma-validator/challenger.go | 5 +- op-e2e/actions/l2_challenger.go | 4 +- op-e2e/actions/l2_challenger_test.go | 158 ++++++------------ op-e2e/actions/l2_runtime.go | 37 ++-- op-e2e/actions/l2_validator.go | 12 +- op-e2e/actions/l2_validator_test.go | 6 +- op-e2e/e2eutils/validator/validator.go | 11 ++ op-e2e/setup.go | 7 +- op-e2e/system_test.go | 37 ++-- .../deploy-config/devnetL1-template.json | 2 +- 11 files changed, 124 insertions(+), 157 deletions(-) diff --git a/kroma-chain-ops/genesis/testdata/test-deploy-config-full.json b/kroma-chain-ops/genesis/testdata/test-deploy-config-full.json index dc3551e68..ecb6fd371 100644 --- a/kroma-chain-ops/genesis/testdata/test-deploy-config-full.json +++ b/kroma-chain-ops/genesis/testdata/test-deploy-config-full.json @@ -71,7 +71,7 @@ "validatorManagerMaxFinalizations": 10, "validatorManagerBaseReward": "0x1", "assetManagerKgh": "0xff000000000000000000000000000000000000ff", - "assetManagerVault": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", + "assetManagerVault": "0x14dc79964da2c08b23698b3d3cc7ca32193d9955", "assetManagerMinDelegationPeriod": 2, "assetManagerBondAmount": "0xa", "securityCouncilOwners": [ diff --git a/kroma-validator/challenger.go b/kroma-validator/challenger.go index 0354b3c4e..70642b68b 100644 --- a/kroma-validator/challenger.go +++ b/kroma-validator/challenger.go @@ -191,15 +191,16 @@ func (c *Challenger) InitConfig(ctx context.Context) error { if err != nil { if errors.Is(err, errors.New("method 'BOND_AMOUNT' not found")) { requiredBondAmountV2 = big.NewInt(0) + } else { + return fmt.Errorf("failed to get required bond amount: %w", err) } - return fmt.Errorf("failed to get required bond amount: %w", err) } c.requiredBondAmountV2 = requiredBondAmountV2 return nil }) if err != nil { - return fmt.Errorf("failed to initiate assetMgr config: %w, err") + return fmt.Errorf("failed to initiate assetMgr config: %w", err) } return nil diff --git a/op-e2e/actions/l2_challenger.go b/op-e2e/actions/l2_challenger.go index cfe91f119..cc28139ea 100644 --- a/op-e2e/actions/l2_challenger.go +++ b/op-e2e/actions/l2_challenger.go @@ -31,8 +31,8 @@ func (v *L2Validator) ActCreateChallenge(t Testing, outputIndex *big.Int) common }, "challenge is already in progress") canCreateChallenge, err := v.challenger.CanCreateChallenge(t.Ctx(), outputIndex) - require.NoError(t, err, "unable to check if challenger is in the status that can create challenge") - require.True(t, canCreateChallenge, "challenger is not in the status that can create challenge") + require.NoError(t, err, "unable to check if challenger can create challenge") + require.True(t, canCreateChallenge, "challenger cannot create challenge") tx, err := v.challenger.CreateChallenge(t.Ctx(), outputRange) require.NoError(t, err, "unable to create create challenge tx") diff --git a/op-e2e/actions/l2_challenger_test.go b/op-e2e/actions/l2_challenger_test.go index 6b3ef914e..5306e204b 100644 --- a/op-e2e/actions/l2_challenger_test.go +++ b/op-e2e/actions/l2_challenger_test.go @@ -82,11 +82,9 @@ func ChallengeBasic(t *testing.T, deltaTimeOffset *hexutil.Uint64, version uint8 // create challenge rt.setupChallenge(rt.challenger1, version) - var beforeAsset *big.Int - var slashingAmount *big.Int - var taxAmount *big.Int + var beforeAsset, beforeAssetBonded *big.Int if version == valhelper.ValidatorV2 { - beforeAsset, slashingAmount, taxAmount = rt.fetchChallengeAssets(rt.validator.address) + _, _, beforeAsset, beforeAssetBonded, _ = rt.fetchValidatorStatus(rt.validator) } interaction: @@ -143,22 +141,11 @@ interaction: require.Equal(rt.t, big.NewInt(2*rt.dp.DeployConfig.ValidatorPoolRequiredBondAmount.ToInt().Int64()), bond.Amount) } else if version == valhelper.ValidatorV2 { // check asserter has been slashed - valStatus, err := rt.validator.getValidatorStatus(rt.t) - require.NoError(rt.t, err) - require.Equal(rt.t, val.StatusRegistered, valStatus) - - afterAsset, err := rt.assetMgrContract.TotalKroAssets(nil, rt.validator.address) - require.NoError(rt.t, err) - require.Equal(rt.t, beforeAsset.Uint64()-slashingAmount.Uint64(), afterAsset.Uint64()) - - inJail, err := rt.validator.isInJail(rt.t) - require.NoError(rt.t, err) + valStatus, inJail, afterAsset, afterAssetBonded, slashingAmount := rt.fetchValidatorStatus(rt.validator) + require.Equal(rt.t, val.StatusReady, valStatus) require.True(rt.t, inJail) - - // check security council has received tax - bal, err := rt.assetTokenContract.BalanceOf(nil, rt.sd.DeploymentsL1.SecurityCouncilProxy) - require.NoError(rt.t, err) - require.Equal(t, taxAmount.Uint64(), bal.Uint64()) + require.Equal(rt.t, beforeAsset.Uint64()-slashingAmount.Uint64(), afterAsset.Uint64()) + require.Equal(rt.t, beforeAssetBonded.Uint64()-slashingAmount.Uint64(), afterAssetBonded.Uint64()) } } @@ -183,11 +170,9 @@ func ChallengeAsserterBisectTimeout(t *testing.T, deltaTimeOffset *hexutil.Uint6 // create challenge rt.setupChallenge(rt.challenger1, version) - var beforeAsset *big.Int - var slashingAmount *big.Int - var taxAmount *big.Int + var beforeAsset, beforeAssetBonded *big.Int if version == valhelper.ValidatorV2 { - beforeAsset, slashingAmount, taxAmount = rt.fetchChallengeAssets(rt.validator.address) + _, _, beforeAsset, beforeAssetBonded, _ = rt.fetchValidatorStatus(rt.validator) } interaction: @@ -239,22 +224,11 @@ interaction: require.Equal(rt.t, big.NewInt(2*rt.dp.DeployConfig.ValidatorPoolRequiredBondAmount.ToInt().Int64()), bond.Amount) } else if version == valhelper.ValidatorV2 { // check asserter has been slashed - valStatus, err := rt.validator.getValidatorStatus(rt.t) - require.NoError(rt.t, err) - require.Equal(rt.t, val.StatusRegistered, valStatus) - - afterAsset, err := rt.assetMgrContract.TotalKroAssets(nil, rt.validator.address) - require.NoError(rt.t, err) - require.Equal(rt.t, beforeAsset.Uint64()-slashingAmount.Uint64(), afterAsset.Uint64()) - - inJail, err := rt.validator.isInJail(rt.t) - require.NoError(rt.t, err) + valStatus, inJail, afterAsset, afterAssetBonded, slashingAmount := rt.fetchValidatorStatus(rt.validator) + require.Equal(rt.t, val.StatusReady, valStatus) require.True(rt.t, inJail) - - // check security council has received tax - bal, err := rt.assetTokenContract.BalanceOf(nil, rt.sd.DeploymentsL1.SecurityCouncilProxy) - require.NoError(rt.t, err) - require.Equal(t, taxAmount.Uint64(), bal.Uint64()) + require.Equal(rt.t, beforeAsset.Uint64()-slashingAmount.Uint64(), afterAsset.Uint64()) + require.Equal(rt.t, beforeAssetBonded.Uint64()-slashingAmount.Uint64(), afterAssetBonded.Uint64()) } } @@ -278,11 +252,9 @@ func ChallengeChallengerBisectTimeout(t *testing.T, deltaTimeOffset *hexutil.Uin // create challenge rt.setupChallenge(rt.challenger1, version) - var beforeAsset *big.Int - var slashingAmount *big.Int - var taxAmount *big.Int + var beforeAsset, beforeAssetBonded *big.Int if version == valhelper.ValidatorV2 { - beforeAsset, slashingAmount, taxAmount = rt.fetchChallengeAssets(rt.challenger1.address) + _, _, beforeAsset, beforeAssetBonded, _ = rt.fetchValidatorStatus(rt.challenger1) } interaction: @@ -333,22 +305,11 @@ interaction: require.Equal(rt.t, big.NewInt(2*rt.dp.DeployConfig.ValidatorPoolRequiredBondAmount.ToInt().Int64()), bond.Amount) } else if version == valhelper.ValidatorV2 { // check challenger has been slashed - valStatus, err := rt.challenger1.getValidatorStatus(rt.t) - require.NoError(rt.t, err) - require.Equal(rt.t, val.StatusRegistered, valStatus) - - afterAsset, err := rt.assetMgrContract.TotalKroAssets(nil, rt.challenger1.address) - require.NoError(rt.t, err) - require.Equal(rt.t, beforeAsset.Uint64()-slashingAmount.Uint64(), afterAsset.Uint64()) - - inJail, err := rt.challenger1.isInJail(rt.t) - require.NoError(rt.t, err) + valStatus, inJail, afterAsset, afterAssetBonded, slashingAmount := rt.fetchValidatorStatus(rt.challenger1) + require.Equal(rt.t, val.StatusReady, valStatus) require.True(rt.t, inJail) - - // check security council has received tax - bal, err := rt.assetTokenContract.BalanceOf(nil, rt.sd.DeploymentsL1.SecurityCouncilProxy) - require.NoError(rt.t, err) - require.Equal(t, taxAmount.Uint64(), bal.Uint64()) + require.Equal(rt.t, beforeAsset.Uint64()-slashingAmount.Uint64(), afterAsset.Uint64()) + require.Equal(rt.t, beforeAssetBonded.Uint64()-slashingAmount.Uint64(), afterAssetBonded.Uint64()) } } @@ -372,11 +333,9 @@ func ChallengeChallengerProvingTimeout(t *testing.T, deltaTimeOffset *hexutil.Ui // create challenge rt.setupChallenge(rt.challenger1, version) - var beforeAsset *big.Int - var slashingAmount *big.Int - var taxAmount *big.Int + var beforeAsset, beforeAssetBonded *big.Int if version == valhelper.ValidatorV2 { - beforeAsset, slashingAmount, taxAmount = rt.fetchChallengeAssets(rt.challenger1.address) + _, _, beforeAsset, beforeAssetBonded, _ = rt.fetchValidatorStatus(rt.challenger1) } interaction: @@ -431,22 +390,11 @@ interaction: require.Equal(rt.t, big.NewInt(2*rt.dp.DeployConfig.ValidatorPoolRequiredBondAmount.ToInt().Int64()), bond.Amount) } else if version == valhelper.ValidatorV2 { // check challenger has been slashed - valStatus, err := rt.challenger1.getValidatorStatus(rt.t) - require.NoError(rt.t, err) - require.Equal(rt.t, val.StatusRegistered, valStatus) - - afterAsset, err := rt.assetMgrContract.TotalKroAssets(nil, rt.challenger1.address) - require.NoError(rt.t, err) - require.Equal(rt.t, beforeAsset.Uint64()-slashingAmount.Uint64(), afterAsset.Uint64()) - - inJail, err := rt.challenger1.isInJail(rt.t) - require.NoError(rt.t, err) + valStatus, inJail, afterAsset, afterAssetBonded, slashingAmount := rt.fetchValidatorStatus(rt.challenger1) + require.Equal(rt.t, val.StatusReady, valStatus) require.True(rt.t, inJail) - - // check security council has received tax - bal, err := rt.assetTokenContract.BalanceOf(nil, rt.sd.DeploymentsL1.SecurityCouncilProxy) - require.NoError(rt.t, err) - require.Equal(t, taxAmount.Uint64(), bal.Uint64()) + require.Equal(rt.t, beforeAsset.Uint64()-slashingAmount.Uint64(), afterAsset.Uint64()) + require.Equal(rt.t, beforeAssetBonded.Uint64()-slashingAmount.Uint64(), afterAssetBonded.Uint64()) } } @@ -471,11 +419,10 @@ func ChallengeInvalidProofFail(t *testing.T, deltaTimeOffset *hexutil.Uint64, ve // create challenge rt.setupChallenge(rt.challenger1, version) - var taxAmount *big.Int + var beforeAssetChal, beforeAssetVal, beforeAssetBondedChal, beforeAssetBondedVal *big.Int if version == valhelper.ValidatorV2 { - // if the challenger proves fault with invalid proof, asserter will be slashed - // after security council dismisses the challenge, the slashed asset should be handled manually - _, _, taxAmount = rt.fetchChallengeAssets(rt.validator.address) + _, _, beforeAssetChal, beforeAssetBondedChal, _ = rt.fetchValidatorStatus(rt.challenger1) + _, _, beforeAssetVal, beforeAssetBondedVal, _ = rt.fetchValidatorStatus(rt.validator) } interaction: @@ -551,15 +498,19 @@ interaction: require.NoError(rt.t, err) require.Equal(rt.t, big.NewInt(2*rt.dp.DeployConfig.ValidatorPoolRequiredBondAmount.ToInt().Int64()), bond.Amount) } else if version == valhelper.ValidatorV2 { - // check asserter has been unjailed by guardian - inJail, err := rt.validator.isInJail(rt.t) - require.NoError(rt.t, err) - require.False(rt.t, inJail) + // check challenger has been slashed + valStatus, inJail, afterAsset, afterAssetBonded, slashingAmount := rt.fetchValidatorStatus(rt.challenger1) + require.Equal(rt.t, val.StatusReady, valStatus) + require.True(rt.t, inJail) + require.Equal(rt.t, beforeAssetChal.Uint64()-slashingAmount.Uint64(), afterAsset.Uint64()) + require.Equal(rt.t, beforeAssetBondedChal.Uint64()-slashingAmount.Uint64(), afterAssetBonded.Uint64()) - // check security council has received tax - bal, err := rt.assetTokenContract.BalanceOf(nil, rt.sd.DeploymentsL1.SecurityCouncilProxy) - require.NoError(rt.t, err) - require.Equal(t, taxAmount.Uint64(), bal.Uint64()) + // check asserter has been reverted slash by guardian + valStatus, inJail, afterAsset, afterAssetBonded, slashingAmount = rt.fetchValidatorStatus(rt.validator) + require.Equal(rt.t, val.StatusActive, valStatus) + require.False(rt.t, inJail) + require.Equal(rt.t, beforeAssetVal.Uint64(), afterAsset.Uint64()) + require.Equal(rt.t, beforeAssetBondedVal.Uint64(), afterAssetBonded.Uint64()) } } @@ -584,11 +535,9 @@ func ChallengeForceDeleteOutputBySecurityCouncil(t *testing.T, deltaTimeOffset * // create challenge rt.setupChallenge(rt.challenger1, version) - var beforeAsset *big.Int - var slashingAmount *big.Int - var taxAmount *big.Int + var beforeAsset, beforeAssetBonded *big.Int if version == valhelper.ValidatorV2 { - beforeAsset, slashingAmount, taxAmount = rt.fetchChallengeAssets(rt.validator.address) + _, _, beforeAsset, beforeAssetBonded, _ = rt.fetchValidatorStatus(rt.validator) } interaction: @@ -651,22 +600,11 @@ interaction: require.Equal(rt.t, big.NewInt(2*rt.dp.DeployConfig.ValidatorPoolRequiredBondAmount.ToInt().Int64()), bond.Amount) } else if version == valhelper.ValidatorV2 { // check asserter has been slashed - valStatus, err := rt.validator.getValidatorStatus(rt.t) - require.NoError(rt.t, err) - require.Equal(rt.t, val.StatusRegistered, valStatus) - - inJail, err := rt.validator.isInJail(rt.t) - require.NoError(rt.t, err) + valStatus, inJail, afterAsset, afterAssetBonded, slashingAmount := rt.fetchValidatorStatus(rt.validator) + require.Equal(rt.t, val.StatusReady, valStatus) require.True(rt.t, inJail) - - afterAsset, err := rt.assetMgrContract.TotalKroAssets(nil, rt.validator.address) - require.NoError(rt.t, err) require.Equal(rt.t, beforeAsset.Uint64()-slashingAmount.Uint64(), afterAsset.Uint64()) - - // check security council has received tax (in this case, tax is double: challenger timeout and force delete output) - bal, err := rt.assetTokenContract.BalanceOf(nil, rt.sd.DeploymentsL1.SecurityCouncilProxy) - require.NoError(rt.t, err) - require.Equal(t, taxAmount.Uint64()*2, bal.Uint64()) + require.Equal(rt.t, beforeAssetBonded.Uint64()-slashingAmount.Uint64(), afterAssetBonded.Uint64()) } } @@ -741,6 +679,10 @@ interaction1: balance, err := rt.valPoolContract.BalanceOf(nil, rt.challenger2.address) require.NoError(rt.t, err) require.Equal(rt.t, balance.Int64(), defaultDepositAmount-rt.dp.DeployConfig.ValidatorPoolRequiredBondAmount.ToInt().Int64()) + } else if version == valhelper.ValidatorV2 { + // check bond amount before challenge is canceled + _, _, _, bond, slashingAmount := rt.fetchValidatorStatus(rt.challenger2) + require.Equal(t, slashingAmount.Uint64(), bond.Uint64()) } // progress challenge by challenger 2 @@ -777,5 +719,9 @@ interaction2: balance, err := rt.valPoolContract.BalanceOf(nil, rt.challenger2.address) require.NoError(rt.t, err) require.Equal(rt.t, balance.Int64(), int64(defaultDepositAmount)) + } else if version == valhelper.ValidatorV2 { + // check bond amount released after challenge canceled + _, _, _, bond, _ := rt.fetchValidatorStatus(rt.challenger2) + require.Equal(t, uint64(0), bond.Uint64()) } } diff --git a/op-e2e/actions/l2_runtime.go b/op-e2e/actions/l2_runtime.go index 0f38b7c6f..1c678e0e2 100644 --- a/op-e2e/actions/l2_runtime.go +++ b/op-e2e/actions/l2_runtime.go @@ -11,7 +11,6 @@ import ( "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" @@ -260,6 +259,11 @@ func (rt *Runtime) setupChallenge(challenger *L2Validator, version uint8) { require.Equal(rt.t, rt.dp.DeployConfig.ValidatorPoolRequiredBondAmount.ToInt(), bond.Amount) } else if version == valhelper.ValidatorV2 { rt.registerToValMgr(challenger) + + // check bond amount before create challenge + bond, err := rt.assetMgrContract.TotalValidatorKroBonded(nil, challenger.address) + require.NoError(rt.t, err) + require.Equal(rt.t, uint64(0), bond.Uint64()) } // submit create challenge tx @@ -288,6 +292,11 @@ func (rt *Runtime) setupChallenge(challenger *L2Validator, version uint8) { cBal, err := rt.valPoolContract.BalanceOf(nil, challenger.address) require.NoError(rt.t, err) require.Equal(rt.t, new(big.Int).Sub(new(big.Int).SetInt64(defaultDepositAmount), rt.dp.DeployConfig.ValidatorPoolRequiredBondAmount.ToInt()), cBal) + } else if version == valhelper.ValidatorV2 { + // check bond amount after create challenge + bond, err := rt.assetMgrContract.TotalValidatorKroBonded(nil, challenger.address) + require.NoError(rt.t, err) + require.Equal(rt.t, rt.dp.DeployConfig.AssetManagerBondAmount.ToInt().Uint64(), bond.Uint64()) } } @@ -304,6 +313,7 @@ func (rt *Runtime) depositToValPool(validator *L2Validator) { func (rt *Runtime) registerToValMgr(validator *L2Validator) { minActivateAmount := rt.dp.DeployConfig.ValidatorManagerMinActivateAmount.ToInt() + minActivateAmount = new(big.Int).Mul(minActivateAmount, common.Big256) // approve governance token validator.ActApprove(rt.t, minActivateAmount) @@ -314,8 +324,7 @@ func (rt *Runtime) registerToValMgr(validator *L2Validator) { rt.includeL1BlockBySender(validator.address) // check validator status is active - status, err := validator.getValidatorStatus(rt.t) - require.NoError(rt.t, err) + status := validator.getValidatorStatus(rt.t) require.Equal(rt.t, val.StatusActive, status) } @@ -354,25 +363,17 @@ func (rt *Runtime) submitL2Output() { require.Equal(rt.t, types.ReceiptStatusSuccessful, receipt.Status, "submission failed") } -func (rt *Runtime) fetchChallengeAssets(loser common.Address) (*big.Int, *big.Int, *big.Int) { - slashingRate, err := rt.assetMgrContract.SLASHINGRATE(nil) - require.NoError(rt.t, err) - slashingRateDenom, err := rt.assetMgrContract.SLASHINGRATEDENOM(nil) - require.NoError(rt.t, err) - taxRate, err := rt.assetMgrContract.TAXNUMERATOR(nil) +func (rt *Runtime) fetchValidatorStatus(validator *L2Validator) (uint8, bool, *big.Int, *big.Int, *big.Int) { + valStatus := validator.getValidatorStatus(rt.t) + inJail := validator.isInJail(rt.t) + slashingAmount, err := rt.assetMgrContract.BONDAMOUNT(nil) require.NoError(rt.t, err) - taxDenom, err := rt.assetMgrContract.TAXDENOMINATOR(nil) + validatorAsset, err := rt.assetMgrContract.TotalValidatorKro(nil, validator.address) require.NoError(rt.t, err) - minSlashingAmount, err := rt.assetMgrContract.MINSLASHINGAMOUNT(nil) + validatorAssetBonded, err := rt.assetMgrContract.TotalValidatorKroBonded(nil, validator.address) require.NoError(rt.t, err) - totalAsset, err := rt.assetMgrContract.TotalKroAssets(nil, loser) - require.NoError(rt.t, err) - - slashingAmount := new(big.Int).Div(new(big.Int).Mul(totalAsset, slashingRate), slashingRateDenom) - slashingAmount = math.BigMax(slashingAmount, minSlashingAmount) - taxAmount := new(big.Int).Div(new(big.Int).Mul(slashingAmount, taxRate), taxDenom) - return totalAsset, slashingAmount, taxAmount + return valStatus, inJail, validatorAsset, validatorAssetBonded, slashingAmount } func (rt *Runtime) includeL1BlockBySender(from common.Address) { diff --git a/op-e2e/actions/l2_validator.go b/op-e2e/actions/l2_validator.go index cd73542d8..e47314fff 100644 --- a/op-e2e/actions/l2_validator.go +++ b/op-e2e/actions/l2_validator.go @@ -135,6 +135,10 @@ func (v *L2Validator) CalculateWaitTime(t Testing) time.Duration { outputIndex, err := v.l2os.FetchNextOutputIndex(t.Ctx()) require.NoError(t, err) + canSubmitOutput, err := v.l2os.CanSubmitOutput(t.Ctx(), outputIndex) + require.NoError(t, err) + require.True(t, canSubmitOutput) + calculatedWaitTime := v.l2os.CalculateWaitTime(t.Ctx(), nextBlockNumber, outputIndex) return calculatedWaitTime } @@ -208,18 +212,18 @@ func (v *L2Validator) isValPoolTerminated(t Testing) bool { return v.l2os.IsValPoolTerminated(outputIndex) } -func (v *L2Validator) getValidatorStatus(t Testing) (uint8, error) { +func (v *L2Validator) getValidatorStatus(t Testing) uint8 { validatorStatus, err := v.l2os.GetValidatorStatus(t.Ctx()) require.NoError(t, err) - return validatorStatus, err + return validatorStatus } -func (v *L2Validator) isInJail(t Testing) (bool, error) { +func (v *L2Validator) isInJail(t Testing) bool { inJail, err := v.l2os.IsInJail(t.Ctx()) require.NoError(t, err) - return inJail, err + return inJail } // sendTx reimplements creating & sending transactions because we need to do the final send as async in diff --git a/op-e2e/actions/l2_validator_test.go b/op-e2e/actions/l2_validator_test.go index 57b62a80f..92990d2c1 100644 --- a/op-e2e/actions/l2_validator_test.go +++ b/op-e2e/actions/l2_validator_test.go @@ -6,7 +6,6 @@ import ( "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/stretchr/testify/require" @@ -56,8 +55,7 @@ func RunValidatorManagerTest(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { rt := defaultRuntime(gt, setupSequencerTest, deltaTimeOffset) // Redeploy and upgrade ValidatorPool to set the termination index to a smaller value for ValidatorManager test - newTerminationIndex := common.Big2 - rt.assertRedeployValPoolToTerminate(newTerminationIndex) + rt.assertRedeployValPoolToTerminate(defaultValPoolTerminationIndex) rt.setupHonestValidator(false) @@ -69,7 +67,7 @@ func RunValidatorManagerTest(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { rt.depositToValPool(rt.validator) // Submit outputs to ValidatorPool until newTerminationIndex - for i := uint64(0); new(big.Int).SetUint64(i).Cmp(newTerminationIndex) <= 0; i++ { + for i := uint64(0); new(big.Int).SetUint64(i).Cmp(defaultValPoolTerminationIndex) <= 0; i++ { rt.submitL2Output() } diff --git a/op-e2e/e2eutils/validator/validator.go b/op-e2e/e2eutils/validator/validator.go index dbdf9792f..cc7a94a64 100644 --- a/op-e2e/e2eutils/validator/validator.go +++ b/op-e2e/e2eutils/validator/validator.go @@ -85,6 +85,17 @@ func (h *Helper) UnbondValPool(priv *ecdsa.PrivateKey) bool { return receipt.Status == types.ReceiptStatusSuccessful } +func (h *Helper) ApproveAssetToken(priv *ecdsa.PrivateKey, spender common.Address, amount *big.Int) { + transactOpts, err := bind.NewKeyedTransactorWithChainID(priv, h.l1ChainID) + require.NoError(h.t, err) + + tx, err := h.AssetTokenContract.Approve(transactOpts, spender, amount) + require.NoError(h.t, err) + + _, err = wait.ForReceiptOK(context.Background(), h.l1Client, tx.Hash()) + require.NoError(h.t, err) +} + func (h *Helper) RegisterToValMgr(priv *ecdsa.PrivateKey, amount *big.Int, withdrawAddr common.Address) { transactOpts, err := bind.NewKeyedTransactorWithChainID(priv, h.l1ChainID) require.NoError(h.t, err) diff --git a/op-e2e/setup.go b/op-e2e/setup.go index 047dcaa1a..7c6e19883 100644 --- a/op-e2e/setup.go +++ b/op-e2e/setup.go @@ -6,6 +6,7 @@ import ( "crypto/rand" "errors" "fmt" + "math" "math/big" "net" "os" @@ -843,7 +844,11 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste if cfg.ValidatorVersion == valhelper.ValidatorV2 { // register to ValidatorManager to be a validator - validatorHelper.RegisterToValMgr(cfg.Secrets.TrustedValidator, cfg.DeployConfig.ValidatorManagerMinActivateAmount.ToInt(), cfg.Secrets.Addresses().TrustedValidator) + depositAmount := new(big.Int).Mul(cfg.DeployConfig.ValidatorManagerMinActivateAmount.ToInt(), common.Big256) + validatorHelper.RegisterToValMgr(cfg.Secrets.TrustedValidator, depositAmount, cfg.Secrets.Addresses().TrustedValidator) + + // set up ValidatorRewardVault(Mallory) to be able to provide asset tokens to AssetManager + validatorHelper.ApproveAssetToken(cfg.Secrets.Mallory, cfg.L1Deployments.AssetManagerProxy, new(big.Int).SetUint64(math.MaxUint64)) func() { // Redeploy and upgrade ValidatorPool to set the termination index to a smaller value for ValidatorManager test diff --git a/op-e2e/system_test.go b/op-e2e/system_test.go index 67364db96..ba2a8b13b 100644 --- a/op-e2e/system_test.go +++ b/op-e2e/system_test.go @@ -318,10 +318,10 @@ func TestValidatorSystemUpgradeToV2(t *testing.T) { validatorHelper.Delegate(cfg.Secrets.Challenger1, validatorAddr, cfg.DeployConfig.ValidatorManagerMinActivateAmount.ToInt()) // Capture initial asset amount and validator weight - beforeAmount, err := validatorHelper.AssetMgrContract.TotalKroAssets(&bind.CallOpts{}, validatorAddr) + beforeDelegateAmount, err := validatorHelper.AssetMgrContract.TotalKroAssets(&bind.CallOpts{}, validatorAddr) require.NoError(t, err) - beforeWeight, err := validatorHelper.AssetMgrContract.ReflectiveWeight(&bind.CallOpts{}, validatorAddr) + beforeDepositAmount, err := validatorHelper.AssetMgrContract.TotalValidatorKro(&bind.CallOpts{}, validatorAddr) require.NoError(t, err) l2OutputOracle, err := bindings.NewL2OutputOracleCaller(cfg.L1Deployments.L2OutputOracleProxy, l1Client) @@ -360,17 +360,19 @@ func TestValidatorSystemUpgradeToV2(t *testing.T) { baseReward := new(big.Int).Mul(evt.BaseReward, finalizedOutputNum) validatorReward := new(big.Int).Mul(evt.ValidatorReward, finalizedOutputNum) - afterAmount, err := validatorHelper.AssetMgrContract.TotalKroAssets(&bind.CallOpts{}, validatorAddr) + afterDelegateAmount, err := validatorHelper.AssetMgrContract.TotalKroAssets(&bind.CallOpts{}, validatorAddr) require.NoError(t, err) - require.Equal(t, new(big.Int).Add(beforeAmount, baseReward), afterAmount) + require.Equal(t, new(big.Int).Add(beforeDelegateAmount, baseReward), afterDelegateAmount) - afterWeight, err := validatorHelper.AssetMgrContract.ReflectiveWeight(&bind.CallOpts{}, validatorAddr) + afterDepositAmount, err := validatorHelper.AssetMgrContract.TotalValidatorKro(&bind.CallOpts{}, validatorAddr) require.NoError(t, err) - require.Equal(t, new(big.Int).Add(beforeWeight, new(big.Int).Add(baseReward, validatorReward)), afterWeight) + require.Equal(t, new(big.Int).Add(beforeDepositAmount, validatorReward), afterDepositAmount) + reflectiveWeight, err := validatorHelper.AssetMgrContract.ReflectiveWeight(&bind.CallOpts{}, validatorAddr) + require.NoError(t, err) actualWeight, err := validatorHelper.ValMgrContract.GetWeight(&bind.CallOpts{}, validatorAddr) require.NoError(t, err) - require.Equal(t, afterWeight, actualWeight) + require.Equal(t, reflectiveWeight, actualWeight) return default: @@ -1762,8 +1764,8 @@ func TestChallengeV2(t *testing.T) { validatorHelper := sys.ValidatorHelper() // Register to ValidatorManager to be a challenger - beforeAmount := cfg.DeployConfig.ValidatorManagerMinActivateAmount.ToInt() - validatorHelper.RegisterToValMgr(cfg.Secrets.Challenger1, beforeAmount, cfg.Secrets.Addresses().Challenger1) + validatorHelper.RegisterToValMgr(cfg.Secrets.Challenger1, + cfg.DeployConfig.ValidatorManagerMinActivateAmount.ToInt(), cfg.Secrets.Addresses().Challenger1) l2OutputOracle, err := bindings.NewL2OutputOracleCaller(cfg.L1Deployments.L2OutputOracleProxy, l1Client) require.NoError(t, err) @@ -1777,6 +1779,7 @@ func TestChallengeV2(t *testing.T) { targetOutputOracleIndex := uint64(math.Ceil(float64(testdata.TargetBlockNumber) / float64(cfg.DeployConfig.L2OutputOracleSubmissionInterval))) challengerAddr := cfg.Secrets.Addresses().Challenger1 validatorAddr := cfg.Secrets.Addresses().TrustedValidator + beforeAmount, err := validatorHelper.AssetMgrContract.TotalValidatorKro(&bind.CallOpts{}, validatorAddr) // Subscribe slash event in ValidatorManager slashedCh := make(chan *bindings.ValidatorManagerSlashed, 1) @@ -1800,19 +1803,13 @@ func TestChallengeV2(t *testing.T) { require.Equal(t, targetOutputOracleIndex, evt.OutputIndex.Uint64()) slashedAmount := evt.Amount - afterAmount, err := validatorHelper.AssetMgrContract.TotalKroAssets(&bind.CallOpts{}, validatorAddr) + afterAmount, err := validatorHelper.AssetMgrContract.TotalValidatorKro(&bind.CallOpts{}, validatorAddr) require.NoError(t, err) require.Equal(t, new(big.Int).Sub(beforeAmount, slashedAmount).Uint64(), afterAmount.Uint64()) - taxNum, err := validatorHelper.AssetMgrContract.TAXNUMERATOR(&bind.CallOpts{}) - require.NoError(t, err) - taxDenom, err := validatorHelper.AssetMgrContract.TAXDENOMINATOR(&bind.CallOpts{}) - require.NoError(t, err) - taxAmount := new(big.Int).Div(new(big.Int).Mul(slashedAmount, taxNum), taxDenom) - - scBalance, err := validatorHelper.AssetTokenContract.BalanceOf(&bind.CallOpts{}, cfg.L1Deployments.SecurityCouncilProxy) + inJail, err := validatorHelper.ValMgrContract.InJail(&bind.CallOpts{}, validatorAddr) require.NoError(t, err) - require.Equal(t, taxAmount.Uint64(), scBalance.Uint64()) + require.True(t, inJail) slashed = true default: @@ -1821,6 +1818,10 @@ func TestChallengeV2(t *testing.T) { if challengeStatus == chal.StatusReadyToProve { challengeCreated = true + + bond, err := validatorHelper.AssetMgrContract.TotalValidatorKroBonded(&bind.CallOpts{}, challengerAddr) + require.NoError(t, err) + require.Equal(t, cfg.DeployConfig.AssetManagerBondAmount.ToInt().Uint64(), bond.Uint64()) } if !challengeCreated { continue diff --git a/packages/contracts/deploy-config/devnetL1-template.json b/packages/contracts/deploy-config/devnetL1-template.json index f3c555a27..7ddeb0d09 100644 --- a/packages/contracts/deploy-config/devnetL1-template.json +++ b/packages/contracts/deploy-config/devnetL1-template.json @@ -39,7 +39,7 @@ "validatorPoolRequiredBondAmount": "0x1", "validatorPoolMaxUnbond": 10, "validatorPoolRoundDuration": 4, - "validatorPoolTerminateOutputIndex": "0xa", + "validatorPoolTerminateOutputIndex": "0x32", "validatorManagerTrustedValidator": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "validatorManagerMinRegisterAmount": "0x32", "validatorManagerMinActivateAmount": "0x64",