Skip to content
This repository was archived by the owner on Sep 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion assertions/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ go_library(

go_test(
name = "assertions_test",
size = "small",
size = "medium",
srcs = [
"manager_test.go",
"poster_test.go",
Expand All @@ -52,13 +52,16 @@ go_test(
"//solgen/go/bridgegen",
"//solgen/go/mocksgen",
"//solgen/go/rollupgen",
"//solgen/go/testgen",
"//testing",
"//testing/casttest",
"//testing/mocks/state-provider",
"//testing/setup:setup_lib",
"@com_github_ethereum_go_ethereum//accounts/abi",
"@com_github_ethereum_go_ethereum//accounts/abi/bind",
"@com_github_ethereum_go_ethereum//common",
"@com_github_ethereum_go_ethereum//common/hexutil",
"@com_github_ethereum_go_ethereum//core/types",
"@com_github_stretchr_testify//require",
],
)
2 changes: 2 additions & 0 deletions assertions/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ func TestFastConfirmation(t *testing.T) {
ctx := context.Background()
testData, err := setup.ChainsWithEdgeChallengeManager(
setup.WithMockOneStepProver(),
setup.WithAutoDeposit(),
setup.WithChallengeTestingOpts(
challenge_testing.WithLayerZeroHeights(&protocol.LayerZeroHeights{
BlockChallengeHeight: 64,
Expand Down Expand Up @@ -417,6 +418,7 @@ func TestFastConfirmationWithSafe(t *testing.T) {
ctx := context.Background()
testData, err := setup.ChainsWithEdgeChallengeManager(
// setup.WithMockBridge(),
setup.WithAutoDeposit(),
setup.WithMockOneStepProver(),
setup.WithChallengeTestingOpts(
challenge_testing.WithLayerZeroHeights(&protocol.LayerZeroHeights{
Expand Down
143 changes: 130 additions & 13 deletions assertions/poster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,135 @@ package assertions_test

import (
"context"
"math/big"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
gethtypes "github.com/ethereum/go-ethereum/core/types"

"github.com/offchainlabs/bold/assertions"
protocol "github.com/offchainlabs/bold/chain-abstraction"
cm "github.com/offchainlabs/bold/challenge-manager"
"github.com/offchainlabs/bold/challenge-manager/types"
"github.com/offchainlabs/bold/solgen/go/bridgegen"
"github.com/offchainlabs/bold/solgen/go/mocksgen"
"github.com/offchainlabs/bold/solgen/go/rollupgen"
"github.com/offchainlabs/bold/solgen/go/testgen"
challenge_testing "github.com/offchainlabs/bold/testing"
statemanager "github.com/offchainlabs/bold/testing/mocks/state-provider"
"github.com/offchainlabs/bold/testing/setup"
)

func TestPostAssertion(t *testing.T) {
ctx := context.Background()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
chainSetup, chalManager, assertionManager, stateManager := setupAssertionPosting(t)
aliceChain := chainSetup.Chains[0]

// Force the posting of a new sequencer batch.
forceSequencerMessageBatchPosting(
t, ctx, chainSetup.Accounts[0].TxOpts, chainSetup.Addrs.SequencerInbox, chainSetup.Backend,
)

// We have enabled auto-deposits for this test, so we expect that before starting the challenge manager,
// Alice has an ERC20 deposit token balance of 0. After starting, we should expect a non-zero balance.
rollup, err := rollupgen.NewRollupUserLogic(chainSetup.Addrs.Rollup, chainSetup.Backend)
require.NoError(t, err)
requiredStake, err := rollup.BaseStake(&bind.CallOpts{})
require.NoError(t, err)
stakeTokenAddr, err := rollup.StakeToken(&bind.CallOpts{})
require.NoError(t, err)

erc20, err := testgen.NewERC20Token(stakeTokenAddr, chainSetup.Backend)
require.NoError(t, err)
balance, err := erc20.BalanceOf(&bind.CallOpts{}, aliceChain.StakerAddress())
require.NoError(t, err)
require.True(t, big.NewInt(0).Cmp(balance) == 0)

chalManager.Start(ctx)

// Wait a little for the chain watcher to be ready.
time.Sleep(time.Second)

preState, err := stateManager.ExecutionStateAfterPreviousState(ctx, 0, protocol.GoGlobalState{})
require.NoError(t, err)
postState, err := stateManager.ExecutionStateAfterPreviousState(ctx, 1, preState.GlobalState)
require.NoError(t, err)
nextState, err := stateManager.ExecutionStateAfterPreviousState(ctx, 2, postState.GlobalState)
require.NoError(t, err)

// Expect a non-zero balance equal to the required stake after the challenge manager auto-deposited.
balance, err = erc20.BalanceOf(&bind.CallOpts{}, aliceChain.StakerAddress())
require.NoError(t, err)
require.True(t, requiredStake.Cmp(balance) == 0)

// Verify that alice can post an assertion correctly.
posted, err := assertionManager.PostAssertion(ctx)
require.NoError(t, err)
require.Equal(t, true, posted.IsSome())

creationInfo, err := aliceChain.ReadAssertionCreationInfo(ctx, posted.Unwrap().Id())
require.NoError(t, err)
require.Equal(t, postState, protocol.GoExecutionStateFromSolidity(creationInfo.AfterState))

// Wait a little and advance the chain to allow the next assertion to be posted.
time.Sleep(time.Second * 5)
chainSetup.Backend.Commit()

// Expect a zero ERC20 balance after the first staked assertion was posted.
balance, err = erc20.BalanceOf(&bind.CallOpts{}, aliceChain.StakerAddress())
require.NoError(t, err)
require.True(t, big.NewInt(0).Cmp(balance) == 0)

posted, err = assertionManager.PostAssertion(ctx)
require.NoError(t, err)
require.Equal(t, true, posted.IsSome())

creationInfo, err = aliceChain.ReadAssertionCreationInfo(ctx, posted.Unwrap().Id())
require.NoError(t, err)
require.Equal(t, nextState, protocol.GoExecutionStateFromSolidity(creationInfo.AfterState))

// Continue to expect a zero ERC20 balance after the second assertion was posted, as no new
// stake was expected for the validator.
time.Sleep(time.Second * 5)
chainSetup.Backend.Commit()

balance, err = erc20.BalanceOf(&bind.CallOpts{}, chainSetup.Accounts[0].TxOpts.From)
require.NoError(t, err)
require.True(t, big.NewInt(0).Cmp(balance) == 0)

// We then filter all the transactions to the staken address from the validator and expect
// there was only a single deposit event (a transfer event with from set to 0x0).
it, err := erc20.FilterTransfer(
&bind.FilterOpts{
Start: 0,
End: nil,
},
[]common.Address{{}},
[]common.Address{aliceChain.StakerAddress()},
)
require.NoError(t, err)
defer func() {
if err = it.Close(); err != nil {
t.Error(err)
}
}()
totalTransfers := 0
for it.Next() {
totalTransfers++
}
require.Equal(t, 1, totalTransfers, "Expected only one deposit event by the staker")
}

func setupAssertionPosting(t *testing.T) (*setup.ChainSetup, *cm.Manager, *assertions.Manager, *statemanager.L2StateBackend) {
setup, err := setup.ChainsWithEdgeChallengeManager(
// setup.WithMockBridge(),
setup.WithMockOneStepProver(),
setup.WithAutoDeposit(),
setup.WithChallengeTestingOpts(
challenge_testing.WithLayerZeroHeights(&protocol.LayerZeroHeights{
BlockChallengeHeight: 64,
Expand Down Expand Up @@ -77,19 +184,29 @@ func TestPostAssertion(t *testing.T) {
cm.OverrideAssertionManager(assertionManager),
)
require.NoError(t, err)
chalManager.Start(ctx)
return setup, chalManager, assertionManager, stateManager

preState, err := stateManager.ExecutionStateAfterPreviousState(ctx, 0, protocol.GoGlobalState{})
require.NoError(t, err)
postState, err := stateManager.ExecutionStateAfterPreviousState(ctx, 1, preState.GlobalState)
require.NoError(t, err)

time.Sleep(time.Second)
}

posted, err := assertionManager.PostAssertion(ctx)
func forceSequencerMessageBatchPosting(
t *testing.T,
ctx context.Context,
sequencerOpts *bind.TransactOpts,
seqInboxAddr common.Address,
backend *setup.SimulatedBackendWrapper,
) {
batchCompressedBytes := hexutil.MustDecode("0x94643ec208c5558027fa768281f28aa273f01537942cd58cdd9c17e97e30281f")
message := append([]byte{0}, batchCompressedBytes...)
seqNum := new(big.Int).Lsh(common.Big1, 256)
seqNum.Sub(seqNum, common.Big1)
seqInbox, err := bridgegen.NewSequencerInbox(seqInboxAddr, backend)
require.NoError(t, err)
require.Equal(t, true, posted.IsSome())
creationInfo, err := aliceChain.ReadAssertionCreationInfo(ctx, posted.Unwrap().Id())
tx, err := seqInbox.AddSequencerL2BatchFromOrigin8f111f3c(
sequencerOpts, seqNum, message, big.NewInt(1), common.Address{}, big.NewInt(0), big.NewInt(0),
)
require.NoError(t, err)
require.Equal(t, postState, protocol.GoExecutionStateFromSolidity(creationInfo.AfterState))
require.NoError(t, challenge_testing.WaitForTx(ctx, backend, tx))
receipt, err := backend.TransactionReceipt(ctx, tx.Hash())
require.NoError(t, err)
require.Equal(t, gethtypes.ReceiptStatusSuccessful, receipt.Status)
}
10 changes: 8 additions & 2 deletions chain-abstraction/sol-implementation/assertion_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -630,8 +630,14 @@ func (a *AssertionChain) createAndStakeOnAssertion(
return nil, errors.Wrapf(err, "could not fetch assertion with computed hash %#x", computedHash)
default:
}
if err = a.autoDepositFunds(ctx, parentAssertionCreationInfo.RequiredStake); err != nil {
return nil, errors.Wrapf(err, "could not auto-deposit funds for assertion creation")
staked, err := a.IsStaked(ctx)
if err != nil {
return nil, err
}
if !staked {
if err = a.autoDepositFunds(ctx, parentAssertionCreationInfo.RequiredStake); err != nil {
return nil, errors.Wrapf(err, "could not auto-deposit funds for assertion creation")
}
}
receipt, err := a.transact(ctx, a.backend, func(opts *bind.TransactOpts) (*types.Transaction, error) {
return stakeFn(
Expand Down
128 changes: 70 additions & 58 deletions testing/setup/rollup_stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ type ChainSetup struct {
numAccountsToGen uint64
numFundedAccounts uint64
minimumAssertionPeriod int64
autoDeposit bool
challengeTestingOpts []challenge_testing.Opt
StateManagerOpts []statemanager.Opt
StakeTokenAddress common.Address
Expand Down Expand Up @@ -236,10 +237,17 @@ func WithNumFundedAccounts(n uint64) Opt {
}
}

func WithAutoDeposit() Opt {
return func(setup *ChainSetup) {
setup.autoDeposit = true
}
}

func ChainsWithEdgeChallengeManager(opts ...Opt) (*ChainSetup, error) {
ctx := context.Background()
setp := &ChainSetup{
numAccountsToGen: 4,
autoDeposit: false,
}
for _, o := range opts {
o(setp)
Expand Down Expand Up @@ -274,22 +282,24 @@ func ChainsWithEdgeChallengeManager(opts ...Opt) (*ChainSetup, error) {
if !ok {
return nil, errors.New("could not set value")
}
accs[0].TxOpts.Value = value
mintTx, err := tokenBindings.Deposit(accs[0].TxOpts)
if err != nil {
return nil, err
}
if waitErr := challenge_testing.WaitForTx(ctx, backend, mintTx); waitErr != nil {
return nil, errors.Wrap(waitErr, "errored waiting for transaction")
}
receipt, err = backend.TransactionReceipt(ctx, mintTx.Hash())
if err != nil {
return nil, err
}
if receipt.Status != types.ReceiptStatusSuccessful {
return nil, errors.New("receipt not successful")
if !setp.autoDeposit {
accs[0].TxOpts.Value = value
mintTx, err3 := tokenBindings.Deposit(accs[0].TxOpts)
if err3 != nil {
return nil, err3
}
if waitErr := challenge_testing.WaitForTx(ctx, backend, mintTx); waitErr != nil {
return nil, errors.Wrap(waitErr, "errored waiting for transaction")
}
receipt, err = backend.TransactionReceipt(ctx, mintTx.Hash())
if err != nil {
return nil, err
}
if receipt.Status != types.ReceiptStatusSuccessful {
return nil, errors.New("receipt not successful")
}
accs[0].TxOpts.Value = big.NewInt(0)
}
accs[0].TxOpts.Value = big.NewInt(0)

prod := false
wasmModuleRoot := common.Hash{}
Expand Down Expand Up @@ -445,49 +455,51 @@ func ChainsWithEdgeChallengeManager(opts ...Opt) (*ChainSetup, error) {
if !ok {
return nil, errors.New("could not set big int")
}
for i := 0; i < len(accs); i++ {
acc := accs[i]
transferTx, err := tokenBindings.Transfer(accs[0].TxOpts, acc.TxOpts.From, seed)
if err != nil {
return nil, errors.Wrap(err, "could not approve account")
}
if waitErr := challenge_testing.WaitForTx(ctx, backend, transferTx); waitErr != nil {
return nil, errors.Wrap(waitErr, "errored waiting for transfer transaction")
}
receipt, err := backend.TransactionReceipt(ctx, transferTx.Hash())
if err != nil {
return nil, errors.Wrap(err, "could not get tx receipt")
}
if receipt.Status != types.ReceiptStatusSuccessful {
return nil, errors.New("receipt not successful")
}
approveTx, err := tokenBindings.Approve(acc.TxOpts, addresses.Rollup, value)
if err != nil {
return nil, errors.Wrap(err, "could not approve account")
}
if waitErr := challenge_testing.WaitForTx(ctx, backend, approveTx); waitErr != nil {
return nil, errors.Wrap(waitErr, "errored waiting for approval transaction")
}
receipt, err = backend.TransactionReceipt(ctx, approveTx.Hash())
if err != nil {
return nil, errors.Wrap(err, "could not get tx receipt")
}
if receipt.Status != types.ReceiptStatusSuccessful {
return nil, errors.New("receipt not successful")
}
approveTx, err = tokenBindings.Approve(acc.TxOpts, chalManagerAddr, value)
if err != nil {
return nil, errors.Wrap(err, "could not approve account")
}
if waitErr := challenge_testing.WaitForTx(ctx, backend, approveTx); waitErr != nil {
return nil, errors.Wrap(waitErr, "errored waiting for approval transaction")
}
receipt, err = backend.TransactionReceipt(ctx, approveTx.Hash())
if err != nil {
return nil, errors.Wrap(err, "could not get tx receipt")
}
if receipt.Status != types.ReceiptStatusSuccessful {
return nil, errors.New("receipt not successful")
if !setp.autoDeposit {
for i := 0; i < len(accs); i++ {
acc := accs[i]
transferTx, err := tokenBindings.Transfer(accs[0].TxOpts, acc.TxOpts.From, seed)
if err != nil {
return nil, errors.Wrap(err, "could not approve account")
}
if waitErr := challenge_testing.WaitForTx(ctx, backend, transferTx); waitErr != nil {
return nil, errors.Wrap(waitErr, "errored waiting for transfer transaction")
}
receipt, err := backend.TransactionReceipt(ctx, transferTx.Hash())
if err != nil {
return nil, errors.Wrap(err, "could not get tx receipt")
}
if receipt.Status != types.ReceiptStatusSuccessful {
return nil, errors.New("receipt not successful")
}
approveTx, err := tokenBindings.Approve(acc.TxOpts, addresses.Rollup, value)
if err != nil {
return nil, errors.Wrap(err, "could not approve account")
}
if waitErr := challenge_testing.WaitForTx(ctx, backend, approveTx); waitErr != nil {
return nil, errors.Wrap(waitErr, "errored waiting for approval transaction")
}
receipt, err = backend.TransactionReceipt(ctx, approveTx.Hash())
if err != nil {
return nil, errors.Wrap(err, "could not get tx receipt")
}
if receipt.Status != types.ReceiptStatusSuccessful {
return nil, errors.New("receipt not successful")
}
approveTx, err = tokenBindings.Approve(acc.TxOpts, chalManagerAddr, value)
if err != nil {
return nil, errors.Wrap(err, "could not approve account")
}
if waitErr := challenge_testing.WaitForTx(ctx, backend, approveTx); waitErr != nil {
return nil, errors.Wrap(waitErr, "errored waiting for approval transaction")
}
receipt, err = backend.TransactionReceipt(ctx, approveTx.Hash())
if err != nil {
return nil, errors.Wrap(err, "could not get tx receipt")
}
if receipt.Status != types.ReceiptStatusSuccessful {
return nil, errors.New("receipt not successful")
}
}
}

Expand Down
Loading