Skip to content

Commit

Permalink
feat: integrate the aggoracle contracts (#244)
Browse files Browse the repository at this point in the history
* integrate new contracts for the aggoracle (wip)

* feat: integrate the GlobalExitRootManager L2 sovereign chain

* fix: backport changes from fep-type1 branch

* chore: remove empty test/e2e.go

* test: update the aggoracle e2e test

* fix: logs

* refactor: rely only on single L1 bridge syncer in the e2e, cleanup logs, handle context cancellation

* refactor: simulated backend so that it correctly initializes bridge contract

* fix: tests

* fix: bridge balance logs

* add extract rpc error data helper function

* extract L1SetupResult and L2SetupResult

* some more cleanups

* fix: update import paths for l2-sovereign-chain contracts and use the Shanghai spec

* refactor: rename waitTxToBeSuccessOrFail to waitForTxResult and update logging messages

* chore: update cdk-contracts-tooling dependency to v0.0.1

* fix: remove duplicated recipes in test/makefile

* test: chaingersender ut

* test: IsGERInjected unit test

* remove removal of mock files

* rename
  • Loading branch information
Stefan-Ethernal authored Dec 23, 2024
1 parent 6e5bd04 commit 52770bf
Show file tree
Hide file tree
Showing 58 changed files with 3,912 additions and 649 deletions.
2 changes: 1 addition & 1 deletion agglayer/mock_agglayer_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

108 changes: 64 additions & 44 deletions aggoracle/chaingersender/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@ import (
"math/big"
"time"

"github.com/0xPolygon/cdk-contracts-tooling/contracts/manual/pessimisticglobalexitroot"
cfgTypes "github.com/0xPolygon/cdk/config/types"
"github.com/0xPolygon/cdk-contracts-tooling/contracts/l2-sovereign-chain/globalexitrootmanagerl2sovereignchain"
cdkcommon "github.com/0xPolygon/cdk/common"
cfgtypes "github.com/0xPolygon/cdk/config/types"
"github.com/0xPolygon/cdk/log"
"github.com/0xPolygon/zkevm-ethtx-manager/ethtxmanager"
ethtxtypes "github.com/0xPolygon/zkevm-ethtx-manager/types"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)

const insertGERFuncName = "insertGlobalExitRoot"

type EthClienter interface {
ethereum.LogFilterer
ethereum.BlockNumberReader
Expand All @@ -39,97 +43,113 @@ type EthTxManager interface {
) (common.Hash, error)
}

type EVMChainGERSender struct {
logger *log.Logger
gerContract *pessimisticglobalexitroot.Pessimisticglobalexitroot
gerAddr common.Address
client EthClienter
ethTxMan EthTxManager
gasOffset uint64
waitPeriodMonitorTx time.Duration
type L2GERManager interface {
GlobalExitRootMap(opts *bind.CallOpts, ger [common.HashLength]byte) (*big.Int, error)
}

type EVMConfig struct {
GlobalExitRootL2Addr common.Address `mapstructure:"GlobalExitRootL2"`
URLRPCL2 string `mapstructure:"URLRPCL2"`
ChainIDL2 uint64 `mapstructure:"ChainIDL2"`
GasOffset uint64 `mapstructure:"GasOffset"`
WaitPeriodMonitorTx cfgTypes.Duration `mapstructure:"WaitPeriodMonitorTx"`
WaitPeriodMonitorTx cfgtypes.Duration `mapstructure:"WaitPeriodMonitorTx"`
EthTxManager ethtxmanager.Config `mapstructure:"EthTxManager"`
}

type EVMChainGERSender struct {
logger *log.Logger

l2GERManager L2GERManager
l2GERManagerAddr common.Address
l2GERManagerAbi *abi.ABI

ethTxMan EthTxManager
gasOffset uint64
waitPeriodMonitorTx time.Duration
}

func NewEVMChainGERSender(
logger *log.Logger,
l2GlobalExitRoot common.Address,
l2GERManagerAddr common.Address,
l2Client EthClienter,
ethTxMan EthTxManager,
gasOffset uint64,
waitPeriodMonitorTx time.Duration,
) (*EVMChainGERSender, error) {
gerContract, err := pessimisticglobalexitroot.NewPessimisticglobalexitroot(l2GlobalExitRoot, l2Client)
l2GERManager, err := globalexitrootmanagerl2sovereignchain.NewGlobalexitrootmanagerl2sovereignchain(
l2GERManagerAddr, l2Client)
if err != nil {
return nil, err
}

l2GERAbi, err := globalexitrootmanagerl2sovereignchain.Globalexitrootmanagerl2sovereignchainMetaData.GetAbi()
if err != nil {
return nil, err
}

return &EVMChainGERSender{
logger: logger,
gerContract: gerContract,
gerAddr: l2GlobalExitRoot,
client: l2Client,
l2GERManager: l2GERManager,
l2GERManagerAddr: l2GERManagerAddr,
l2GERManagerAbi: l2GERAbi,
ethTxMan: ethTxMan,
gasOffset: gasOffset,
waitPeriodMonitorTx: waitPeriodMonitorTx,
}, nil
}

func (c *EVMChainGERSender) IsGERInjected(ger common.Hash) (bool, error) {
timestamp, err := c.gerContract.GlobalExitRootMap(&bind.CallOpts{Pending: false}, ger)
blockHashBigInt, err := c.l2GERManager.GlobalExitRootMap(&bind.CallOpts{Pending: false}, ger)
if err != nil {
return false, fmt.Errorf("error calling gerContract.GlobalExitRootMap: %w", err)
return false, fmt.Errorf("failed to check if global exit root is injected %s: %w", ger, err)
}

return timestamp.Cmp(common.Big0) != 0, nil
return common.BigToHash(blockHashBigInt) != cdkcommon.ZeroHash, nil
}

func (c *EVMChainGERSender) InjectGER(ctx context.Context, ger common.Hash) error {
ticker := time.NewTicker(c.waitPeriodMonitorTx)
defer ticker.Stop()

gerABI, err := pessimisticglobalexitroot.PessimisticglobalexitrootMetaData.GetAbi()
updateGERTxInput, err := c.l2GERManagerAbi.Pack(insertGERFuncName, ger)
if err != nil {
return err
}

updateGERTxInput, err := gerABI.Pack("updateGlobalExitRoot", ger)
id, err := c.ethTxMan.Add(ctx, &c.l2GERManagerAddr, common.Big0, updateGERTxInput, c.gasOffset, nil)
if err != nil {
return err
}

id, err := c.ethTxMan.Add(ctx, &c.gerAddr, big.NewInt(0), updateGERTxInput, c.gasOffset, nil)
if err != nil {
return err
}
for {
<-ticker.C

c.logger.Debugf("waiting for tx %s to be mined", id.Hex())
res, err := c.ethTxMan.Result(ctx, id)
if err != nil {
c.logger.Error("error calling ethTxMan.Result: ", err)
}

switch res.Status {
case ethtxtypes.MonitoredTxStatusCreated,
ethtxtypes.MonitoredTxStatusSent:
continue
case ethtxtypes.MonitoredTxStatusFailed:
return fmt.Errorf("tx %s failed", res.ID)
case ethtxtypes.MonitoredTxStatusMined,
ethtxtypes.MonitoredTxStatusSafe,
ethtxtypes.MonitoredTxStatusFinalized:
select {
case <-ctx.Done():
c.logger.Infof("context cancelled")
return nil
default:
c.logger.Error("unexpected tx status: ", res.Status)

case <-ticker.C:
c.logger.Debugf("waiting for tx %s to be mined", id.Hex())
res, err := c.ethTxMan.Result(ctx, id)
if err != nil {
c.logger.Errorf("failed to check the transaction %s status: %s", id.Hex(), err)
return err
}

switch res.Status {
case ethtxtypes.MonitoredTxStatusCreated,
ethtxtypes.MonitoredTxStatusSent:
continue
case ethtxtypes.MonitoredTxStatusFailed:
return fmt.Errorf("inject GER tx %s failed", id.Hex())
case ethtxtypes.MonitoredTxStatusMined,
ethtxtypes.MonitoredTxStatusSafe,
ethtxtypes.MonitoredTxStatusFinalized:
c.logger.Debugf("inject GER tx %s was successfully mined at block %d", id.Hex(), res.MinedAtBlockNumber)

return nil
default:
c.logger.Error("unexpected tx status:", res.Status)
}
}
}
}
168 changes: 168 additions & 0 deletions aggoracle/chaingersender/evm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package chaingersender

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

"github.com/0xPolygon/cdk/aggoracle/mocks"
"github.com/0xPolygon/cdk/log"
"github.com/0xPolygon/zkevm-ethtx-manager/types"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

func TestEVMChainGERSender_InjectGER(t *testing.T) {
insertGERFuncABI := `[{
"inputs": [
{
"internalType": "bytes32",
"name": "_newRoot",
"type": "bytes32"
}
],
"name": "insertGlobalExitRoot",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}]`
l2GERManagerAddr := common.HexToAddress("0x123")
l2GERManagerAbi, err := abi.JSON(strings.NewReader(insertGERFuncABI))
require.NoError(t, err)

ger := common.HexToHash("0x456")
txID := common.HexToHash("0x789")

tests := []struct {
name string
addReturnTxID common.Hash
addReturnErr error
resultReturn types.MonitoredTxResult
resultReturnErr error
expectedErr string
}{
{
name: "successful injection",
addReturnTxID: txID,
addReturnErr: nil,
resultReturn: types.MonitoredTxResult{Status: types.MonitoredTxStatusMined, MinedAtBlockNumber: big.NewInt(123)},
resultReturnErr: nil,
expectedErr: "",
},
{
name: "injection fails due to transaction failure",
addReturnTxID: txID,
addReturnErr: nil,
resultReturn: types.MonitoredTxResult{Status: types.MonitoredTxStatusFailed},
resultReturnErr: nil,
expectedErr: "inject GER tx",
},
{
name: "injection fails due to Add method error",
addReturnTxID: common.Hash{},
addReturnErr: errors.New("add error"),
resultReturn: types.MonitoredTxResult{},
resultReturnErr: nil,
expectedErr: "add error",
},
{
name: "injection fails due to Result method error",
addReturnTxID: txID,
addReturnErr: nil,
resultReturn: types.MonitoredTxResult{},
resultReturnErr: errors.New("result error"),
expectedErr: "result error",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx, cancelFn := context.WithTimeout(context.Background(), time.Millisecond*500)
defer cancelFn()

ethTxMan := new(mocks.EthTxManagerMock)
ethTxMan.
On("Add", ctx, &l2GERManagerAddr, common.Big0, mock.Anything, mock.Anything, mock.Anything).
Return(tt.addReturnTxID, tt.addReturnErr)
ethTxMan.
On("Result", ctx, tt.addReturnTxID).
Return(tt.resultReturn, tt.resultReturnErr)

sender := &EVMChainGERSender{
logger: log.GetDefaultLogger(),
l2GERManagerAddr: l2GERManagerAddr,
l2GERManagerAbi: &l2GERManagerAbi,
ethTxMan: ethTxMan,
waitPeriodMonitorTx: time.Millisecond * 10,
}

err := sender.InjectGER(ctx, ger)
if tt.expectedErr == "" {
require.NoError(t, err)
} else {
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectedErr)
}
})
}
}

func TestEVMChainGERSender_IsGERInjected(t *testing.T) {
tests := []struct {
name string
mockReturn *big.Int
mockError error
expectedResult bool
expectedErrMsg string
}{
{
name: "GER is injected",
mockReturn: big.NewInt(1),
mockError: nil,
expectedResult: true,
expectedErrMsg: "",
},
{
name: "GER is not injected",
mockReturn: big.NewInt(0),
mockError: nil,
expectedResult: false,
expectedErrMsg: "",
},
{
name: "Error checking GER injection",
mockReturn: nil,
mockError: errors.New("some error"),
expectedResult: false,
expectedErrMsg: "failed to check if global exit root is injected",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockL2GERManager := new(mocks.L2GERManagerMock)
mockL2GERManager.On("GlobalExitRootMap", mock.Anything, mock.Anything).
Return(tt.mockReturn, tt.mockError)

evmChainGERSender := &EVMChainGERSender{
l2GERManager: mockL2GERManager,
}

ger := common.HexToHash("0x12345")
result, err := evmChainGERSender.IsGERInjected(ger)
if tt.expectedErrMsg != "" {
require.ErrorContains(t, err, tt.expectedErrMsg)
} else {
require.NoError(t, err)
}
require.Equal(t, tt.expectedResult, result)

mockL2GERManager.AssertExpectations(t)
})
}
}
Loading

0 comments on commit 52770bf

Please sign in to comment.