diff --git a/assertions/manager.go b/assertions/manager.go index c7d54594a..021562a7c 100644 --- a/assertions/manager.go +++ b/assertions/manager.go @@ -286,7 +286,7 @@ func (m *Manager) Start(ctx context.Context) { if err2 != nil { return false, err2 } - if err2 := m.chain.Deposit(ctx, latestConfirmedInfo.RequiredStake); err2 != nil { + if err2 := m.chain.AutoDepositTokenForStaking(ctx, latestConfirmedInfo.RequiredStake); err2 != nil { return false, err2 } return true, nil diff --git a/chain-abstraction/interfaces.go b/chain-abstraction/interfaces.go index 061837128..56e0fa34b 100644 --- a/chain-abstraction/interfaces.go +++ b/chain-abstraction/interfaces.go @@ -166,7 +166,7 @@ type AssertionChain interface { TopLevelClaimHeights(ctx context.Context, edgeId EdgeId) (OriginHeights, error) // Mutating methods. - Deposit( + AutoDepositTokenForStaking( ctx context.Context, amount *big.Int, ) error diff --git a/chain-abstraction/sol-implementation/assertion_chain.go b/chain-abstraction/sol-implementation/assertion_chain.go index 0326bc2cd..4350381ee 100644 --- a/chain-abstraction/sol-implementation/assertion_chain.go +++ b/chain-abstraction/sol-implementation/assertion_chain.go @@ -353,7 +353,7 @@ func (a *AssertionChain) LatestConfirmed(ctx context.Context, opts *bind.CallOpt // Returns true if the staker's address is currently staked in the assertion chain. func (a *AssertionChain) IsStaked(ctx context.Context) (bool, error) { - return a.rollup.IsStaked(a.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), a.StakerAddress()) + return a.rollup.IsStaked(&bind.CallOpts{Context: ctx}, a.StakerAddress()) } // RollupAddress for the assertion chain. @@ -397,25 +397,36 @@ func (a *AssertionChain) IsChallengeComplete( return challengeConfirmed, nil } -func (a *AssertionChain) Deposit( +// AutoDepositTokenForStaking ensures that the validator has enough funds to stake +// on assertions if not already staked, and then deposits the difference required to participate. +func (a *AssertionChain) AutoDepositTokenForStaking( ctx context.Context, amount *big.Int, ) error { + staked, err := a.IsStaked(ctx) + if err != nil { + return err + } + if staked { + return nil + } return a.autoDepositFunds(ctx, amount) } +// Attempts to auto-wrap ETH to WETH with the required amount that is specified to the function. +// This function uses `latest` onchain data to determine the current balance of the staker +// and deposits the difference between the required amount and the current balance. func (a *AssertionChain) autoDepositFunds(ctx context.Context, amount *big.Int) error { if !a.autoDeposit { return nil } - callOpts := a.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}) // The validity of the stake token address containing code is checked in the constructor // of the assertion chain. erc20, err := testgen.NewERC20Token(a.stakeTokenAddr, a.backend) if err != nil { return err } - balance, err := erc20.BalanceOf(callOpts, a.txOpts.From) + balance, err := erc20.BalanceOf(&bind.CallOpts{Context: ctx}, a.txOpts.From) if err != nil { return err } @@ -444,18 +455,17 @@ func (a *AssertionChain) autoDepositFunds(ctx context.Context, amount *big.Int) func (a *AssertionChain) ApproveAllowances( ctx context.Context, ) error { - opts := a.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}) // The validity of the stake token address containing code is checked in the constructor // of the assertion chain. erc20, err := testgen.NewERC20Token(a.stakeTokenAddr, a.backend) if err != nil { return err } - rollupAllowance, err := erc20.Allowance(opts, a.txOpts.From, a.rollupAddr) + rollupAllowance, err := erc20.Allowance(&bind.CallOpts{Context: ctx}, a.txOpts.From, a.rollupAddr) if err != nil { return err } - chalManagerAllowance, err := erc20.Allowance(opts, a.txOpts.From, a.chalManagerAddr) + chalManagerAllowance, err := erc20.Allowance(&bind.CallOpts{Context: ctx}, a.txOpts.From, a.chalManagerAddr) if err != nil { return err } diff --git a/chain-abstraction/sol-implementation/assertion_chain_test.go b/chain-abstraction/sol-implementation/assertion_chain_test.go index 6c0dbd18b..8c411c79f 100644 --- a/chain-abstraction/sol-implementation/assertion_chain_test.go +++ b/chain-abstraction/sol-implementation/assertion_chain_test.go @@ -525,9 +525,67 @@ func Test_autoDepositFunds(t *testing.T) { expectedNewBalance := new(big.Int).Add(balance, big.NewInt(20)) ctx := context.Background() - require.NoError(t, setupCfg.Chains[0].Deposit(ctx, expectedNewBalance)) + require.NoError(t, setupCfg.Chains[0].AutoDepositTokenForStaking(ctx, expectedNewBalance)) newBalance, err := erc20.BalanceOf(&bind.CallOpts{}, account.AccountAddr) require.NoError(t, err) require.Equal(t, expectedNewBalance, newBalance) } + +func Test_autoDepositFunds_SkipsIfAlreadyStaked(t *testing.T) { + setupCfg, err := setup.ChainsWithEdgeChallengeManager(setup.WithMockOneStepProver()) + require.NoError(t, err) + rollupAddr := setupCfg.Addrs.Rollup + rollup, err := rollupgen.NewRollupUserLogic(rollupAddr, setupCfg.Backend) + require.NoError(t, err) + stakeTokenAddr, err := rollup.StakeToken(&bind.CallOpts{}) + require.NoError(t, err) + erc20, err := mocksgen.NewTestWETH9(stakeTokenAddr, setupCfg.Backend) + require.NoError(t, err) + account := setupCfg.Accounts[1] + assertionChain := setupCfg.Chains[0] + + balance, err := erc20.BalanceOf(&bind.CallOpts{}, account.AccountAddr) + require.NoError(t, err) + + expectedNewBalance := new(big.Int).Add(balance, big.NewInt(20)) + ctx := context.Background() + require.NoError(t, assertionChain.AutoDepositTokenForStaking(ctx, expectedNewBalance)) + + newBalance, err := erc20.BalanceOf(&bind.CallOpts{}, account.AccountAddr) + require.NoError(t, err) + require.Equal(t, expectedNewBalance, newBalance) + + // Tries to stake on an assertion. + genesisHash, err := assertionChain.GenesisAssertionHash(ctx) + require.NoError(t, err) + genesisInfo, err := assertionChain.ReadAssertionCreationInfo(ctx, protocol.AssertionHash{Hash: genesisHash}) + require.NoError(t, err) + postState := &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{ + BlockHash: common.BytesToHash([]byte("foo")), + SendRoot: common.Hash{}, + Batch: 1, + PosInBatch: 0, + }, + MachineStatus: protocol.MachineStatusFinished, + } + _, err = assertionChain.NewStakeOnNewAssertion(ctx, genesisInfo, postState) + require.NoError(t, err) + + // Check we are staked. + staked, err := assertionChain.IsStaked(ctx) + require.NoError(t, err) + require.True(t, staked) + + // Attempt to auto-deposit again. + oldBalance := newBalance + evenBiggerBalance := new(big.Int).Add(oldBalance, big.NewInt(100)) + require.NoError(t, setupCfg.Chains[0].AutoDepositTokenForStaking(ctx, evenBiggerBalance)) + + // Check that we our balance does not increase if we try to auto-deposit again given we are + // already staked as a validator. In fact, expect it decreased. + newBalance, err = erc20.BalanceOf(&bind.CallOpts{}, account.AccountAddr) + require.NoError(t, err) + require.True(t, oldBalance.Cmp(newBalance) > 0) +} diff --git a/testing/mocks/mocks.go b/testing/mocks/mocks.go index b8892b597..fb218a673 100644 --- a/testing/mocks/mocks.go +++ b/testing/mocks/mocks.go @@ -557,7 +557,7 @@ func (m *MockProtocol) IsStaked(ctx context.Context) (bool, error) { return args.Get(0).(bool), args.Error(1) } -func (m *MockProtocol) Deposit( +func (m *MockProtocol) AutoDepositTokenForStaking( ctx context.Context, amount *big.Int, ) error {