Skip to content

Commit

Permalink
Introduce nonce for consensus events on L1
Browse files Browse the repository at this point in the history
  • Loading branch information
mdehoog committed Jan 1, 2025
1 parent f3b9f0c commit ef4a40d
Show file tree
Hide file tree
Showing 68 changed files with 3,313 additions and 235 deletions.
2 changes: 2 additions & 0 deletions .semgrep/rules/sol-rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,9 @@ rules:
}
paths:
exclude:
- packages/contracts-bedrock/src/L1/SystemConfigIsthmus.sol
- packages/contracts-bedrock/src/L1/SystemConfigInterop.sol
- packages/contracts-bedrock/src/L1/OptimismPortalIsthmus.sol
- packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol

- id: sol-safety-proper-initializer
Expand Down
3 changes: 1 addition & 2 deletions op-chain-ops/cmd/deposit-hash/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ func main() {

for _, ethLog := range l1Receipt.Logs {
if ethLog.Topics[0].String() == depositLogTopic.String() {

reconstructedDep, err := derive.UnmarshalDepositLogEvent(ethLog)
reconstructedDep, err := derive.UnmarshalDepositLogEventIgnoreNonce(ethLog)
if err != nil {
log.Crit("Failed to parse deposit event ", "err", err)
}
Expand Down
34 changes: 27 additions & 7 deletions op-chain-ops/genesis/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -996,13 +996,8 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Header, l2GenesisBlockHa
Hash: l2GenesisBlockHash,
Number: l2GenesisBlockNumber,
},
L2Time: l1StartBlock.Time,
SystemConfig: eth.SystemConfig{
BatcherAddr: d.BatchSenderAddress,
Overhead: eth.Bytes32(common.BigToHash(new(big.Int).SetUint64(d.GasPriceOracleOverhead))),
Scalar: eth.Bytes32(d.FeeScalar()),
GasLimit: uint64(d.L2GenesisBlockGasLimit),
},
L2Time: l1StartBlock.Time,
SystemConfig: d.GenesisSystemConfig(),
},
BlockTime: d.L2BlockTime,
MaxSequencerDrift: d.MaxSequencerDrift,
Expand All @@ -1027,6 +1022,31 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Header, l2GenesisBlockHa
}, nil
}

// GenesisSystemConfig converts a DeployConfig to a eth.SystemConfig. If Ecotone is active at genesis, the
// Overhead value is considered a noop.
func (d *DeployConfig) GenesisSystemConfig() eth.SystemConfig {
depositNonce := uint64(0)
configUpdateNonce := uint64(0)
// if Isthmus is active at genesis, we must ensure the nonces are set correctly
if d.L2GenesisIsthmusTimeOffset != nil && *d.L2GenesisIsthmusTimeOffset == 0 {
// SystemConfig emits 3 ConfigUpdate events which increments the nonce by 3
configUpdateNonce += 3
// If a custom gas token is set, a TransactionDeposited event is emitted from
// the OptimismPortal, which increments the deposit nonce by 1
if d.UseCustomGasToken {
depositNonce += 1
}
}
return eth.SystemConfig{
BatcherAddr: d.BatchSenderAddress,
Overhead: eth.Bytes32(common.BigToHash(new(big.Int).SetUint64(d.GasPriceOracleOverhead))),
Scalar: d.FeeScalar(),
GasLimit: uint64(d.L2GenesisBlockGasLimit),
DepositNonce: depositNonce,
ConfigUpdateNonce: configUpdateNonce,
}
}

// NewDeployConfig reads a config file given a path on the filesystem.
func NewDeployConfig(path string) (*DeployConfig, error) {
file, err := os.ReadFile(path)
Expand Down
1 change: 1 addition & 0 deletions op-chain-ops/interopgen/recipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ func InteropL2DevConfig(l1ChainID, l2ChainID uint64, addrs devkeys.Addresses) (*
L2GenesisFjordTimeOffset: new(hexutil.Uint64),
L2GenesisGraniteTimeOffset: new(hexutil.Uint64),
L2GenesisHoloceneTimeOffset: new(hexutil.Uint64),
L2GenesisIsthmusTimeOffset: new(hexutil.Uint64),
L2GenesisInteropTimeOffset: new(hexutil.Uint64),
L1CancunTimeOffset: new(hexutil.Uint64),
UseInterop: true,
Expand Down
2 changes: 1 addition & 1 deletion op-e2e/actions/helpers/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ func (s *CrossLayerUser) CheckDepositTx(t Testing, l1TxHash common.Hash, index i
require.False(t, l2Success)
} else {
require.Less(t, index, len(depositReceipt.Logs), "must have enough logs in receipt")
reconstructedDep, err := derive.UnmarshalDepositLogEvent(depositReceipt.Logs[index])
reconstructedDep, err := derive.UnmarshalDepositLogEventIgnoreNonce(depositReceipt.Logs[index])
require.NoError(t, err, "Could not reconstruct L2 Deposit")
l2Tx := types.NewTx(reconstructedDep)
s.L2.CheckReceipt(t, l2Success, l2Tx.Hash())
Expand Down
15 changes: 7 additions & 8 deletions op-e2e/e2eutils/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"path"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -205,6 +204,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *
FjordTime: deployConf.FjordTime(uint64(deployConf.L1GenesisBlockTimestamp)),
GraniteTime: deployConf.GraniteTime(uint64(deployConf.L1GenesisBlockTimestamp)),
HoloceneTime: deployConf.HoloceneTime(uint64(deployConf.L1GenesisBlockTimestamp)),
IsthmusTime: deployConf.IsthmusTime(uint64(deployConf.L1GenesisBlockTimestamp)),
InteropTime: deployConf.InteropTime(uint64(deployConf.L1GenesisBlockTimestamp)),
AltDAConfig: pcfg,
}
Expand All @@ -226,16 +226,12 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *
}

func SystemConfigFromDeployConfig(deployConfig *genesis.DeployConfig) eth.SystemConfig {
return eth.SystemConfig{
BatcherAddr: deployConfig.BatchSenderAddress,
Overhead: eth.Bytes32(common.BigToHash(new(big.Int).SetUint64(deployConfig.GasPriceOracleOverhead))),
Scalar: eth.Bytes32(deployConfig.FeeScalar()),
GasLimit: uint64(deployConfig.L2GenesisBlockGasLimit),
}
return deployConfig.GenesisSystemConfig()
}

func ApplyDeployConfigForks(deployConfig *genesis.DeployConfig) {
isHolocene := os.Getenv("OP_E2E_USE_HOLOCENE") == "true"
isIsthmus := os.Getenv("OP_E2E_USE_ISTHMUS") == "true"
isHolocene := isIsthmus || os.Getenv("OP_E2E_USE_HOLOCENE") == "true"
isGranite := isHolocene || os.Getenv("OP_E2E_USE_GRANITE") == "true"
isFjord := isGranite || os.Getenv("OP_E2E_USE_FJORD") == "true"
isEcotone := isFjord || os.Getenv("OP_E2E_USE_ECOTONE") == "true"
Expand All @@ -255,6 +251,9 @@ func ApplyDeployConfigForks(deployConfig *genesis.DeployConfig) {
if isHolocene {
deployConfig.L2GenesisHoloceneTimeOffset = new(hexutil.Uint64)
}
if isIsthmus {
deployConfig.L2GenesisIsthmusTimeOffset = new(hexutil.Uint64)
}
// Canyon and lower is activated by default
deployConfig.L2GenesisCanyonTimeOffset = new(hexutil.Uint64)
deployConfig.L2GenesisRegolithTimeOffset = new(hexutil.Uint64)
Expand Down
2 changes: 1 addition & 1 deletion op-e2e/system/bridge/bridge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func TestERC20BridgeDeposits(t *testing.T) {
depositEvent, err := receipts.FindLog(depositReceipt.Logs, portal.ParseTransactionDeposited)
require.NoError(t, err, "Should emit deposit event")

depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw)
depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw)
require.NoError(t, err)
_, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash())
require.NoError(t, err)
Expand Down
1 change: 1 addition & 0 deletions op-e2e/system/e2esys/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,7 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System,
FjordTime: cfg.DeployConfig.FjordTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)),
GraniteTime: cfg.DeployConfig.GraniteTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)),
HoloceneTime: cfg.DeployConfig.HoloceneTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)),
IsthmusTime: cfg.DeployConfig.IsthmusTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)),
InteropTime: cfg.DeployConfig.InteropTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)),
ProtocolVersionsAddress: cfg.L1Deployments.ProtocolVersionsProxy,
AltDAConfig: rollupAltDAConfig,
Expand Down
6 changes: 3 additions & 3 deletions op-e2e/system/gastoken/gastoken_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ func setCustomGasToken(t *testing.T, cfg e2esys.SystemConfig, sys *e2esys.System

depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited)
require.NoError(t, err, "Should emit deposit event")
depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw)
depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw)

require.NoError(t, err)
l2Client := sys.NodeClient("sequencer")
Expand Down Expand Up @@ -326,7 +326,7 @@ func checkDeposit(t *testing.T, gto gasTokenTestOpts, enabled bool) {
// compute the deposit transaction hash + poll for it
depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited)
require.NoError(t, err, "Should emit deposit event")
depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw)
depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw)
require.NoError(t, err)
_, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash())
require.NoError(t, err)
Expand Down Expand Up @@ -496,7 +496,7 @@ func checkFeeWithdrawal(t *testing.T, gto gasTokenTestOpts, enabled bool) {
// Compute the deposit transaction hash + poll for it
depositEvent, err := receipts.FindLog(receipt.Logs, optimismPortal.ParseTransactionDeposited)
require.NoError(t, err, "Should emit deposit event")
depositTx, err := derive.UnmarshalDepositLogEvent(&depositEvent.Raw)
depositTx, err := derive.UnmarshalDepositLogEventIgnoreNonce(&depositEvent.Raw)
require.NoError(t, err)
_, err = wait.ForReceiptOK(context.Background(), l2Client, types.NewTx(depositTx).Hash())
require.NoError(t, err)
Expand Down
2 changes: 1 addition & 1 deletion op-e2e/system/helpers/tx_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func SendDepositTx(t *testing.T, cfg e2esys.SystemConfig, l1Client *ethclient.Cl
t.Logf("SendDepositTx: included on L1")

// Wait for transaction to be included on L2
reconstructedDep, err := derive.UnmarshalDepositLogEvent(l1Receipt.Logs[0])
reconstructedDep, err := derive.UnmarshalDepositLogEventIgnoreNonce(l1Receipt.Logs[0])
require.NoError(t, err, "Could not reconstruct L2 Deposit")
tx = types.NewTx(reconstructedDep)
l2Receipt, err := wait.ForReceipt(ctx, l2Client, tx.Hash(), l2Opts.ExpectedStatus)
Expand Down
19 changes: 8 additions & 11 deletions op-node/rollup/derive/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,38 +65,35 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex
// case we need to fetch all transaction receipts from the L1 origin block so we can scan for
// user deposits.
if l2Parent.L1Origin.Number != epoch.Number {
info, receipts, err := ba.l1.FetchReceipts(ctx, epoch.Hash)
if err != nil {
var receipts types.Receipts
if l1Info, receipts, err = ba.l1.FetchReceipts(ctx, epoch.Hash); err != nil {
return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info and receipts: %w", err))
}
if l2Parent.L1Origin.Hash != info.ParentHash() {
if l2Parent.L1Origin.Hash != l1Info.ParentHash() {
return nil, NewResetError(
fmt.Errorf("cannot create new block with L1 origin %s (parent %s) on top of L1 origin %s",
epoch, info.ParentHash(), l2Parent.L1Origin))
epoch, l1Info.ParentHash(), l2Parent.L1Origin))
}

deposits, err := DeriveDeposits(receipts, ba.rollupCfg.DepositContractAddress)
if err != nil {
if depositTxs, sysConfig.DepositNonce, err = DeriveDeposits(receipts, ba.rollupCfg.DepositContractAddress, sysConfig.DepositNonce); err != nil {
// deposits may never be ignored. Failing to process them is a critical error.
return nil, NewCriticalError(fmt.Errorf("failed to derive some deposits: %w", err))
}

// apply sysCfg changes
if err := UpdateSystemConfigWithL1Receipts(&sysConfig, receipts, ba.rollupCfg, info.Time()); err != nil {
if err = UpdateSystemConfigWithL1Receipts(&sysConfig, receipts, ba.rollupCfg, l1Info.Time()); err != nil {
return nil, NewCriticalError(fmt.Errorf("failed to apply derived L1 sysCfg updates: %w", err))
}

l1Info = info
depositTxs = deposits
seqNumber = 0
} else {
if l2Parent.L1Origin.Hash != epoch.Hash {
return nil, NewResetError(fmt.Errorf("cannot create new block with L1 origin %s in conflict with L1 origin %s", epoch, l2Parent.L1Origin))
}
info, err := ba.l1.InfoByHash(ctx, epoch.Hash)
l1Info, err = ba.l1.InfoByHash(ctx, epoch.Hash)
if err != nil {
return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info: %w", err))
}
l1Info = info
depositTxs = nil
seqNumber = l2Parent.SequenceNumber + 1
}
Expand Down
72 changes: 58 additions & 14 deletions op-node/rollup/derive/deposit_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,41 +17,58 @@ import (
var (
DepositEventABI = "TransactionDeposited(address,address,uint256,bytes)"
DepositEventABIHash = crypto.Keccak256Hash([]byte(DepositEventABI))
DepositEventVersion0 = common.Hash{}
DepositEventVersion0 = uint64(0)
DepositEventVersion1 = uint64(1)
)

// UnmarshalDepositLogEventIgnoreNonce decodes an EVM log entry emitted by the deposit contract into typed deposit data.
// Same as UnmarshalDepositLogEvent, but it force-ensures that the deposit nonce is correct.
// This is useful for testing or non-consensus applications.
func UnmarshalDepositLogEventIgnoreNonce(ev *types.Log) (*types.DepositTx, error) {
previousNonce := uint64(0)
if len(ev.Topics) == 4 {
nonce, version := UnpackNonceAndVersion(ev.Topics[3])
switch version {
case DepositEventVersion1:
previousNonce = nonce - 1
}
}
dep, _, err := UnmarshalDepositLogEvent(ev, previousNonce)
return dep, err
}

// UnmarshalDepositLogEvent decodes an EVM log entry emitted by the deposit contract into typed deposit data.
//
// parse log data for:
//
// event TransactionDeposited(
// address indexed from,
// address indexed to,
// uint256 indexed version,
// uint256 indexed nonceAndVersion,
// bytes opaqueData
// );
//
// Additionally, the event log-index and
func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) {
func UnmarshalDepositLogEvent(ev *types.Log, currentNonce uint64) (*types.DepositTx, uint64, error) {
if len(ev.Topics) != 4 {
return nil, fmt.Errorf("expected 4 event topics (event identity, indexed from, indexed to, indexed version), got %d", len(ev.Topics))
return nil, currentNonce, fmt.Errorf("expected 4 event topics (event identity, indexed from, indexed to, indexed version), got %d", len(ev.Topics))
}
if ev.Topics[0] != DepositEventABIHash {
return nil, fmt.Errorf("invalid deposit event selector: %s, expected %s", ev.Topics[0], DepositEventABIHash)
return nil, currentNonce, fmt.Errorf("invalid deposit event selector: %s, expected %s", ev.Topics[0], DepositEventABIHash)
}
if len(ev.Data) < 64 {
return nil, fmt.Errorf("incomplate opaqueData slice header (%d bytes): %x", len(ev.Data), ev.Data)
return nil, currentNonce, fmt.Errorf("incomplate opaqueData slice header (%d bytes): %x", len(ev.Data), ev.Data)
}
if len(ev.Data)%32 != 0 {
return nil, fmt.Errorf("expected log data to be multiple of 32 bytes: got %d bytes", len(ev.Data))
return nil, currentNonce, fmt.Errorf("expected log data to be multiple of 32 bytes: got %d bytes", len(ev.Data))
}

// indexed 0
from := common.BytesToAddress(ev.Topics[1][12:])
// indexed 1
to := common.BytesToAddress(ev.Topics[2][12:])
// indexed 2
version := ev.Topics[3]
nonceAndVersion := ev.Topics[3]
// unindexed data
// Solidity serializes the event's Data field as follows:
// abi.encode(abi.encodPacked(uint256 mint, uint256 value, uint64 gasLimit, uint8 isCreation, bytes data))
Expand All @@ -60,14 +77,14 @@ func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) {
var opaqueContentOffset uint256.Int
opaqueContentOffset.SetBytes(ev.Data[0:32])
if !opaqueContentOffset.IsUint64() || opaqueContentOffset.Uint64() != 32 {
return nil, fmt.Errorf("invalid opaqueData slice header offset: %d", opaqueContentOffset.Uint64())
return nil, currentNonce, fmt.Errorf("invalid opaqueData slice header offset: %d", opaqueContentOffset.Uint64())
}
// The next 32 bytes indicate the length of the opaqueData content.
var opaqueContentLength uint256.Int
opaqueContentLength.SetBytes(ev.Data[32:64])
// Make sure the length is an uint64, it's not larger than the remaining data, and the log is using minimal padding (i.e. can't add 32 bytes without exceeding data)
if !opaqueContentLength.IsUint64() || opaqueContentLength.Uint64() > uint64(len(ev.Data)-64) || opaqueContentLength.Uint64()+32 <= uint64(len(ev.Data)-64) {
return nil, fmt.Errorf("invalid opaqueData slice header length: %d", opaqueContentLength.Uint64())
return nil, currentNonce, fmt.Errorf("invalid opaqueData slice header length: %d", opaqueContentLength.Uint64())
}
// The remaining data is the opaqueData which is tightly packed
// and then padded to 32 bytes by the EVM.
Expand All @@ -83,17 +100,39 @@ func UnmarshalDepositLogEvent(ev *types.Log) (*types.DepositTx, error) {
dep.From = from
dep.IsSystemTransaction = false

newNonce, version := UnpackNonceAndVersion(nonceAndVersion)

var err error
switch version {
case DepositEventVersion0:
err = unmarshalDepositVersion0(&dep, to, opaqueData)
case DepositEventVersion1:
if newNonce != currentNonce+1 {
return nil, currentNonce, fmt.Errorf("invalid deposit nonce, expected %d got %d", currentNonce+1, newNonce)
}
err = unmarshalDepositVersion1(&dep, to, opaqueData)
default:
return nil, fmt.Errorf("invalid deposit version, got %s", version)
return nil, currentNonce, fmt.Errorf("invalid deposit version, got %d", version)
}
if err != nil {
return nil, fmt.Errorf("failed to decode deposit (version %s): %w", version, err)
return nil, currentNonce, fmt.Errorf("failed to decode deposit (version %d): %w", version, err)
}
return &dep, nil
return &dep, newNonce, nil
}

func UnpackNonceAndVersion(nonceAndVersion common.Hash) (nonce uint64, version uint64) {
i := new(big.Int).SetBytes(nonceAndVersion[:])
mask64 := new(big.Int).SetBytes(common.Hex2Bytes("ffffffffffffffff"))
version = mask64.And(mask64, i).Uint64()
nonce = i.Rsh(i, 128).Uint64()
return
}

func PackNonceAndVersion(nonce uint64, version uint64) common.Hash {
i := new(big.Int).SetUint64(nonce)
i.Lsh(i, 128)
i.Or(i, new(big.Int).SetUint64(version))
return common.BytesToHash(i.Bytes())
}

func unmarshalDepositVersion0(dep *types.DepositTx, to common.Address, opaqueData []byte) error {
Expand Down Expand Up @@ -140,6 +179,11 @@ func unmarshalDepositVersion0(dep *types.DepositTx, to common.Address, opaqueDat
return nil
}

func unmarshalDepositVersion1(dep *types.DepositTx, to common.Address, opaqueData []byte) error {
// version 1 simply adds a nonce; the rest is the same
return unmarshalDepositVersion0(dep, to, opaqueData)
}

// MarshalDepositLogEvent returns an EVM log entry that encodes a TransactionDeposited event from the deposit contract.
// This is the reverse of the deposit transaction derivation.
func MarshalDepositLogEvent(depositContractAddr common.Address, deposit *types.DepositTx) (*types.Log, error) {
Expand All @@ -151,7 +195,7 @@ func MarshalDepositLogEvent(depositContractAddr common.Address, deposit *types.D
DepositEventABIHash,
eth.AddressAsLeftPaddedHash(deposit.From),
toBytes,
DepositEventVersion0,
PackNonceAndVersion(0, DepositEventVersion0),
}

data := make([]byte, 64, 64+3*32)
Expand Down
4 changes: 2 additions & 2 deletions op-node/rollup/derive/deposit_log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestUnmarshalLogEvent(t *testing.T) {
log.TxIndex = uint(rng.Intn(10000))
log.Index = uint(source.LogIndex)
log.BlockHash = source.L1BlockHash
depOutput, err := UnmarshalDepositLogEvent(log)
depOutput, _, err := UnmarshalDepositLogEvent(log, 0)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -121,7 +121,7 @@ func TestDeriveUserDeposits(t *testing.T) {
if err != nil {
t.Fatal(err)
}
got, err := UserDeposits(receipts, MockDepositContractAddr)
got, _, err := UserDeposits(receipts, MockDepositContractAddr, 0)
require.NoError(t, err)
require.Equal(t, len(got), len(expectedDeposits))
for d, depTx := range got {
Expand Down
Loading

0 comments on commit ef4a40d

Please sign in to comment.