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 6 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
3 changes: 3 additions & 0 deletions assertions/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
)
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