Commit 870a59be authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into jm/test-new-bridge-iface

parents 1825982a dd4dce86
---
'@eth-optimism/proxyd': patch
---
proxyd: Add req_id to log
---
'@eth-optimism/proxyd': minor
---
Include nonce in sender rate limit
---
'@eth-optimism/common-ts': minor
---
Add option to configure body parser
---
'@eth-optimism/l2geth': patch
---
Has l2geth return a NonceToHigh response if the txn nonce is greater than the expected nonce.
...@@ -846,9 +846,9 @@ workflows: ...@@ -846,9 +846,9 @@ workflows:
requires: requires:
- yarn-monorepo - yarn-monorepo
- js-lint-test: - js-lint-test:
name: drippie-mon-tests name: chain-mon-tests
coverage_flag: drippie-mon-tests coverage_flag: chain-mon-tests
package_name: drippie-mon package_name: chain-mon
dependencies: "(common-ts|contracts-periphery|core-utils|sdk)" dependencies: "(common-ts|contracts-periphery|core-utils|sdk)"
requires: requires:
- yarn-monorepo - yarn-monorepo
......
.github .github
.vscode
node_modules node_modules
**/node_modules
.env .env
**/.env **/.env
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
/packages/contracts-periphery @ethereum-optimism/contract-reviewers /packages/contracts-periphery @ethereum-optimism/contract-reviewers
/packages/core-utils @ethereum-optimism/legacy-reviewers /packages/core-utils @ethereum-optimism/legacy-reviewers
/packages/data-transport-layer @ethereum-optimism/legacy-reviewers /packages/data-transport-layer @ethereum-optimism/legacy-reviewers
/packages/drippie-mon @smartcontracts /packages/chain-mon @smartcontracts
/packages/fault-detector @ethereum-optimism/legacy-reviewers /packages/fault-detector @ethereum-optimism/legacy-reviewers
/packages/hardhat-deploy-config @ethereum-optimism/legacy-reviewers /packages/hardhat-deploy-config @ethereum-optimism/legacy-reviewers
/packages/message-relayer @ethereum-optimism/legacy-reviewers /packages/message-relayer @ethereum-optimism/legacy-reviewers
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
"changeProcessCWD": true "changeProcessCWD": true
}, },
{ {
"directory": "packages/drippie-mon", "directory": "packages/chain-mon",
"changeProcessCWD": true "changeProcessCWD": true
}, },
{ {
...@@ -45,4 +45,4 @@ ...@@ -45,4 +45,4 @@
"eslint.format.enable": true, "eslint.format.enable": true,
"editorconfig.generateAuto": false, "editorconfig.generateAuto": false,
"files.trimTrailingWhitespace": true "files.trimTrailingWhitespace": true
} }
\ No newline at end of file
...@@ -57,7 +57,7 @@ Refer to the Directory Structure section below to understand which packages are ...@@ -57,7 +57,7 @@ Refer to the Directory Structure section below to understand which packages are
│ ├── <a href="./packages/contracts-periphery">contracts-periphery</a>: Peripheral contracts for Optimism │ ├── <a href="./packages/contracts-periphery">contracts-periphery</a>: Peripheral contracts for Optimism
│ ├── <a href="./packages/core-utils">core-utils</a>: Low-level utilities that make building Optimism easier │ ├── <a href="./packages/core-utils">core-utils</a>: Low-level utilities that make building Optimism easier
│ ├── <a href="./packages/data-transport-layer">data-transport-layer</a>: Service for indexing Optimism-related L1 data │ ├── <a href="./packages/data-transport-layer">data-transport-layer</a>: Service for indexing Optimism-related L1 data
│ ├── <a href="./packages/drippie-mon">drippie-mon</a>: Service for monitoring Drippie instances │ ├── <a href="./packages/chain-mon">chain-mon</a>: Chain monitoring services
│ ├── <a href="./packages/fault-detector">fault-detector</a>: Service for detecting Sequencer faults │ ├── <a href="./packages/fault-detector">fault-detector</a>: Service for detecting Sequencer faults
│ ├── <a href="./packages/message-relayer">message-relayer</a>: Tool for automatically relaying L1<>L2 messages in development │ ├── <a href="./packages/message-relayer">message-relayer</a>: Tool for automatically relaying L1<>L2 messages in development
│ ├── <a href="./packages/replica-healthcheck">replica-healthcheck</a>: Service for monitoring the health of a replica node │ ├── <a href="./packages/replica-healthcheck">replica-healthcheck</a>: Service for monitoring the health of a replica node
......
...@@ -37,7 +37,7 @@ flag_management: ...@@ -37,7 +37,7 @@ flag_management:
- name: actor-tests-tests - name: actor-tests-tests
- name: contracts-periphery-tests - name: contracts-periphery-tests
- name: dtl-tests - name: dtl-tests
- name: drippie-mon-tests - name: chain-mon-tests
- name: fault-detector-tests - name: fault-detector-tests
- name: message-relayer-tests - name: message-relayer-tests
- name: replica-healthcheck-tests - name: replica-healthcheck-tests
......
...@@ -555,8 +555,10 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { ...@@ -555,8 +555,10 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
} }
// Ensure the transaction adheres to nonce ordering // Ensure the transaction adheres to nonce ordering
if rcfg.UsingOVM { if rcfg.UsingOVM {
if pool.currentState.GetNonce(from) != tx.Nonce() { if pool.currentState.GetNonce(from) > tx.Nonce() {
return ErrNonceTooLow return ErrNonceTooLow
} else if pool.currentState.GetNonce(from) < tx.Nonce() {
return ErrNonceTooHigh
} }
} else { } else {
if pool.currentState.GetNonce(from) > tx.Nonce() { if pool.currentState.GetNonce(from) > tx.Nonce() {
......
...@@ -3,21 +3,18 @@ package batcher ...@@ -3,21 +3,18 @@ package batcher
import ( import (
"context" "context"
"fmt" "fmt"
"math/big"
_ "net/http/pprof" _ "net/http/pprof"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"time" "time"
"github.com/urfave/cli"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
opsigner "github.com/ethereum-optimism/optimism/op-signer/client"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/urfave/cli"
) )
const ( const (
...@@ -30,98 +27,75 @@ const ( ...@@ -30,98 +27,75 @@ const (
// closure that executes the service and blocks until the service exits. The use // closure that executes the service and blocks until the service exits. The use
// of a closure allows the parameters bound to the top-level main package, e.g. // of a closure allows the parameters bound to the top-level main package, e.g.
// GitVersion, to be captured and used once the function is executed. // GitVersion, to be captured and used once the function is executed.
func Main(version string) func(cliCtx *cli.Context) error { func Main(version string, cliCtx *cli.Context) error {
return func(cliCtx *cli.Context) error { cfg := NewConfig(cliCtx)
cfg := NewConfig(cliCtx) if err := cfg.Check(); err != nil {
if err := cfg.Check(); err != nil { return fmt.Errorf("invalid CLI flags: %w", err)
return fmt.Errorf("invalid CLI flags: %w", err) }
}
l := oplog.NewLogger(cfg.LogConfig)
l.Info("Initializing Batch Submitter")
var batchSubmitter *BatchSubmitter l := oplog.NewLogger(cfg.LogConfig)
if !cfg.SignerConfig.Enabled() { l.Info("Initializing Batch Submitter")
bs, err := NewBatchSubmitter(cfg, l)
if err != nil {
l.Error("Unable to create Batch Submitter", "error", err)
return err
}
batchSubmitter = bs
} else {
signerClient, err := opsigner.NewSignerClientFromConfig(l, cfg.SignerConfig)
if err != nil {
l.Error("Unable to create Signer Client", "error", err)
return err
}
signer := func(chainID *big.Int) SignerFn {
return func(ctx context.Context, rawTx types.TxData) (*types.Transaction, error) {
tx := types.NewTx(rawTx)
return signerClient.SignTransaction(ctx, chainID, tx)
}
}
bs, err := NewBatchSubmitterWithSigner(cfg, common.HexToAddress(cfg.SignerConfig.Address), signer, l)
if err != nil {
l.Error("Unable to create Batch Submitter with signer", "error", err)
return err
}
batchSubmitter = bs
}
l.Info("Starting Batch Submitter") batchSubmitter, err := NewBatchSubmitterFromCLIConfig(cfg, l)
if err != nil {
l.Error("Unable to create Batch Submitter", "error", err)
return err
}
if err := batchSubmitter.Start(); err != nil { l.Info("Starting Batch Submitter")
l.Error("Unable to start Batch Submitter", "error", err)
return err
}
defer batchSubmitter.Stop()
ctx, cancel := context.WithCancel(context.Background()) if err := batchSubmitter.Start(); err != nil {
l.Error("Unable to start Batch Submitter", "error", err)
return err
}
defer batchSubmitter.Stop()
l.Info("Batch Submitter started") ctx, cancel := context.WithCancel(context.Background())
pprofConfig := cfg.PprofConfig
if pprofConfig.Enabled {
l.Info("starting pprof", "addr", pprofConfig.ListenAddr, "port", pprofConfig.ListenPort)
go func() {
if err := oppprof.ListenAndServe(ctx, pprofConfig.ListenAddr, pprofConfig.ListenPort); err != nil {
l.Error("error starting pprof", "err", err)
}
}()
}
registry := opmetrics.NewRegistry() l.Info("Batch Submitter started")
metricsCfg := cfg.MetricsConfig pprofConfig := cfg.PprofConfig
if metricsCfg.Enabled { if pprofConfig.Enabled {
l.Info("starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort) l.Info("starting pprof", "addr", pprofConfig.ListenAddr, "port", pprofConfig.ListenPort)
go func() { go func() {
if err := opmetrics.ListenAndServe(ctx, registry, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil { if err := oppprof.ListenAndServe(ctx, pprofConfig.ListenAddr, pprofConfig.ListenPort); err != nil {
l.Error("error starting metrics server", err) l.Error("error starting pprof", "err", err)
} }
}() }()
opmetrics.LaunchBalanceMetrics(ctx, l, registry, "", batchSubmitter.cfg.L1Client, batchSubmitter.addr) }
}
rpcCfg := cfg.RPCConfig registry := opmetrics.NewRegistry()
server := oprpc.NewServer( metricsCfg := cfg.MetricsConfig
rpcCfg.ListenAddr, if metricsCfg.Enabled {
rpcCfg.ListenPort, l.Info("starting metrics server", "addr", metricsCfg.ListenAddr, "port", metricsCfg.ListenPort)
version, go func() {
) if err := opmetrics.ListenAndServe(ctx, registry, metricsCfg.ListenAddr, metricsCfg.ListenPort); err != nil {
if err := server.Start(); err != nil { l.Error("error starting metrics server", err)
cancel() }
return fmt.Errorf("error starting RPC server: %w", err) }()
} opmetrics.LaunchBalanceMetrics(ctx, l, registry, "", batchSubmitter.L1Client, batchSubmitter.From)
}
interruptChannel := make(chan os.Signal, 1) rpcCfg := cfg.RPCConfig
signal.Notify(interruptChannel, []os.Signal{ server := oprpc.NewServer(
os.Interrupt, rpcCfg.ListenAddr,
os.Kill, rpcCfg.ListenPort,
syscall.SIGTERM, version,
syscall.SIGQUIT, )
}...) if err := server.Start(); err != nil {
<-interruptChannel
cancel() cancel()
_ = server.Stop() return fmt.Errorf("error starting RPC server: %w", err)
return nil
} }
interruptChannel := make(chan os.Signal, 1)
signal.Notify(interruptChannel, []os.Signal{
os.Interrupt,
os.Kill,
syscall.SIGTERM,
syscall.SIGQUIT,
}...)
<-interruptChannel
cancel()
_ = server.Stop()
return nil
} }
package batcher
import (
"bytes"
"errors"
"fmt"
"io"
"math"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/core/types"
)
type (
// channelBuilder uses a ChannelOut to create a channel with output frame
// size approximation.
channelBuilder struct {
cfg ChannelConfig
// marked as full if a) max RLP input bytes, b) max num frames or c) max
// allowed frame index (uint16) has been reached
fullErr error
// current channel
co *derive.ChannelOut
// list of blocks in the channel. Saved in case the channel must be rebuilt
blocks []*types.Block
// frames data queue, to be send as txs
frames []taggedData
}
ChannelConfig struct {
// ChannelTimeout is the maximum duration, in seconds, to attempt completing
// an opened channel. The batcher can decide to set it shorter than the
// actual timeout, since submitting continued channel data to L1 is not
// instantaneous. It's not worth it to work with nearly timed-out channels.
ChannelTimeout uint64
// The maximum byte-size a frame can have.
MaxFrameSize uint64
// The target number of frames to create per channel. Note that if the
// realized compression ratio is worse than the approximate, more frames may
// actually be created. This also depends on how close TargetFrameSize is to
// MaxFrameSize.
TargetFrameSize uint64
// The target number of frames to create in this channel. If the realized
// compression ratio is worse than approxComprRatio, additional leftover
// frame(s) might get created.
TargetNumFrames int
// Approximated compression ratio to assume. Should be slightly smaller than
// average from experiments to avoid the chances of creating a small
// additional leftover frame.
ApproxComprRatio float64
}
ChannelFullError struct {
Err error
}
)
func (e *ChannelFullError) Error() string {
return "channel full: " + e.Err.Error()
}
func (e *ChannelFullError) Unwrap() error {
return e.Err
}
var (
ErrInputTargetReached = errors.New("target amount of input data reached")
ErrMaxFrameIndex = errors.New("max frame index reached (uint16)")
)
// InputThreshold calculates the input data threshold in bytes from the given
// parameters.
func (c ChannelConfig) InputThreshold() uint64 {
return uint64(float64(c.TargetNumFrames) * float64(c.TargetFrameSize) / c.ApproxComprRatio)
}
func newChannelBuilder(cfg ChannelConfig) (*channelBuilder, error) {
co, err := derive.NewChannelOut()
if err != nil {
return nil, err
}
return &channelBuilder{
cfg: cfg,
co: co,
}, nil
}
func (c *channelBuilder) ID() derive.ChannelID {
return c.co.ID()
}
// InputBytes returns to total amount of input bytes added to the channel.
func (c *channelBuilder) InputBytes() int {
return c.co.InputBytes()
}
// Blocks returns a backup list of all blocks that were added to the channel. It
// can be used in case the channel needs to be rebuilt.
func (c *channelBuilder) Blocks() []*types.Block {
return c.blocks
}
// Reset resets the internal state of the channel builder so that it can be
// reused. Note that a new channel id is also generated by Reset.
func (c *channelBuilder) Reset() error {
c.blocks = c.blocks[:0]
c.frames = c.frames[:0]
return c.co.Reset()
}
// AddBlock adds a block to the channel compression pipeline. IsFull should be
// called aftewards to test whether the channel is full. If full, a new channel
// must be started.
//
// AddBlock returns a ChannelFullError if called even though the channel is
// already full. See description of FullErr for details.
//
// Call OutputFrames() afterwards to create frames.
func (c *channelBuilder) AddBlock(block *types.Block) error {
if c.IsFull() {
return c.FullErr()
}
_, err := c.co.AddBlock(block)
if errors.Is(err, derive.ErrTooManyRLPBytes) {
c.setFullErr(err)
return c.FullErr()
} else if err != nil {
return fmt.Errorf("adding block to channel out: %w", err)
}
c.blocks = append(c.blocks, block)
if c.InputTargetReached() {
c.setFullErr(ErrInputTargetReached)
// Adding this block still worked, so don't return error, just mark as full
}
return nil
}
// InputTargetReached says whether the target amount of input data has been
// reached in this channel builder. No more blocks can be added afterwards.
func (c *channelBuilder) InputTargetReached() bool {
return uint64(c.co.InputBytes()) >= c.cfg.InputThreshold()
}
// IsFull returns whether the channel is full.
// FullErr returns the reason for the channel being full.
func (c *channelBuilder) IsFull() bool {
return c.fullErr != nil
}
// FullErr returns the reason why the channel is full. If not full yet, it
// returns nil.
//
// It returns a ChannelFullError wrapping one of three possible reasons for the
// channel being full:
// - ErrInputTargetReached if the target amount of input data has been reached,
// - derive.MaxRLPBytesPerChannel if the general maximum amount of input data
// would have been exceeded by the latest AddBlock call,
// - ErrMaxFrameIndex if the maximum number of frames has been generated (uint16)
func (c *channelBuilder) FullErr() error {
return c.fullErr
}
func (c *channelBuilder) setFullErr(err error) {
c.fullErr = &ChannelFullError{Err: err}
}
// OutputFrames creates new frames with the channel out. It should be called
// after AddBlock and before iterating over available frames with HasFrame and
// NextFrame.
//
// If the input data target hasn't been reached yet, it will conservatively only
// pull readily available frames from the compression output.
// If the target has been reached, the channel is closed and all remaining
// frames will be created, possibly with a small leftover frame.
func (c *channelBuilder) OutputFrames() error {
if c.IsFull() {
return c.closeAndOutputAllFrames()
}
return c.outputReadyFrames()
}
// outputReadyFrames creates new frames as long as there's enough data ready in
// the channel out compression pipeline.
//
// This is part of an optimization to already generate frames and send them off
// as txs while still collecting blocks in the channel builder.
func (c *channelBuilder) outputReadyFrames() error {
// TODO: Decide whether we want to fill frames to max size and use target
// only for estimation, or use target size.
for c.co.ReadyBytes() >= int(c.cfg.MaxFrameSize) {
if err := c.outputFrame(); err == io.EOF {
return nil
} else if err != nil {
return err
}
}
return nil
}
func (c *channelBuilder) closeAndOutputAllFrames() error {
if err := c.co.Close(); err != nil {
return fmt.Errorf("closing channel out: %w", err)
}
for {
if err := c.outputFrame(); err == io.EOF {
return nil
} else if err != nil {
return err
}
}
}
// outputFrame creates one new frame and adds it to the frames queue.
// Note that compressed output data must be available on the underlying
// ChannelOut, or an empty frame will be produced.
func (c *channelBuilder) outputFrame() error {
var buf bytes.Buffer
fn, err := c.co.OutputFrame(&buf, c.cfg.MaxFrameSize)
if err != io.EOF && err != nil {
return fmt.Errorf("writing frame[%d]: %w", fn, err)
}
// Mark as full if max index reached
// TODO: If there's still data in the compression pipeline of the channel out,
// we would miss it and the whole channel would be broken because the last
// frames would never be generated...
// Hitting the max index is impossible with current parameters, so ignore for
// now. Note that in order to properly catch this, we'd need to call Flush
// after every block addition to estimate how many more frames are coming.
if fn == math.MaxUint16 {
c.setFullErr(ErrMaxFrameIndex)
}
frame := taggedData{
id: txID{chID: c.co.ID(), frameNumber: fn},
data: buf.Bytes(),
}
c.frames = append(c.frames, frame)
return err // possibly io.EOF (last frame)
}
// HasFrame returns whether there's any available frame. If true, it can be
// popped using NextFrame().
//
// Call OutputFrames before to create new frames from the channel out
// compression pipeline.
func (c *channelBuilder) HasFrame() bool {
return len(c.frames) > 0
}
func (c *channelBuilder) NumFrames() int {
return len(c.frames)
}
// NextFrame returns the next available frame.
// HasFrame must be called prior to check if there's a next frame available.
// Panics if called when there's no next frame.
func (c *channelBuilder) NextFrame() (txID, []byte) {
if len(c.frames) == 0 {
panic("no next frame")
}
f := c.frames[0]
c.frames = c.frames[1:]
return f.id, f.data
}
// PushFrame adds the frame back to the internal frames queue. Panics if not of
// the same channel.
func (c *channelBuilder) PushFrame(id txID, frame []byte) {
if id.chID != c.ID() {
panic("wrong channel")
}
c.frames = append(c.frames, taggedData{id: id, data: frame})
}
package batcher package batcher
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"io" "io"
...@@ -9,6 +8,7 @@ import ( ...@@ -9,6 +8,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -46,28 +46,28 @@ type taggedData struct { ...@@ -46,28 +46,28 @@ type taggedData struct {
// channel. // channel.
// Functions on channelManager are not safe for concurrent access. // Functions on channelManager are not safe for concurrent access.
type channelManager struct { type channelManager struct {
log log.Logger log log.Logger
channelTimeout uint64 cfg ChannelConfig
// All blocks since the last request for new tx data. // All blocks since the last request for new tx data.
blocks []*types.Block blocks []*types.Block
datas []taggedData // last block hash - for reorg detection
tip common.Hash
// Pending data returned by TxData waiting on Tx Confirmed/Failed // Pending data returned by TxData waiting on Tx Confirmed/Failed
// id of the pending channel
pendingChannel derive.ChannelID // pending channel builder
// list of blocks in the channel. Saved in case the channel must be rebuilt pendingChannel *channelBuilder
pendingBlocks []*types.Block
// Set of unconfirmed txID -> frame data. For tx resubmission // Set of unconfirmed txID -> frame data. For tx resubmission
pendingTransactions map[txID][]byte pendingTransactions map[txID][]byte
// Set of confirmed txID -> inclusion block. For determining if the channel is timed out // Set of confirmed txID -> inclusion block. For determining if the channel is timed out
confirmedTransactions map[txID]eth.BlockID confirmedTransactions map[txID]eth.BlockID
} }
func NewChannelManager(log log.Logger, channelTimeout uint64) *channelManager { func NewChannelManager(log log.Logger, cfg ChannelConfig) *channelManager {
return &channelManager{ return &channelManager{
log: log, log: log,
channelTimeout: channelTimeout, cfg: cfg,
pendingTransactions: make(map[txID][]byte), pendingTransactions: make(map[txID][]byte),
confirmedTransactions: make(map[txID]eth.BlockID), confirmedTransactions: make(map[txID]eth.BlockID),
} }
...@@ -78,7 +78,8 @@ func NewChannelManager(log log.Logger, channelTimeout uint64) *channelManager { ...@@ -78,7 +78,8 @@ func NewChannelManager(log log.Logger, channelTimeout uint64) *channelManager {
func (s *channelManager) Clear() { func (s *channelManager) Clear() {
s.log.Trace("clearing channel manager state") s.log.Trace("clearing channel manager state")
s.blocks = s.blocks[:0] s.blocks = s.blocks[:0]
s.datas = s.datas[:0] s.tip = common.Hash{}
s.clearPendingChannel()
} }
// TxFailed records a transaction as failed. It will attempt to resubmit the data // TxFailed records a transaction as failed. It will attempt to resubmit the data
...@@ -86,10 +87,10 @@ func (s *channelManager) Clear() { ...@@ -86,10 +87,10 @@ func (s *channelManager) Clear() {
func (s *channelManager) TxFailed(id txID) { func (s *channelManager) TxFailed(id txID) {
if data, ok := s.pendingTransactions[id]; ok { if data, ok := s.pendingTransactions[id]; ok {
s.log.Trace("marked transaction as failed", "id", id) s.log.Trace("marked transaction as failed", "id", id)
s.datas = append(s.datas, taggedData{data, id}) s.pendingChannel.PushFrame(id, data)
delete(s.pendingTransactions, id) delete(s.pendingTransactions, id)
} else { } else {
s.log.Info("marked transaction as failed despite having no record of it.", "id", id) s.log.Warn("unknown transaction marked as failed", "id", id)
} }
} }
...@@ -100,7 +101,7 @@ func (s *channelManager) TxFailed(id txID) { ...@@ -100,7 +101,7 @@ func (s *channelManager) TxFailed(id txID) {
func (s *channelManager) TxConfirmed(id txID, inclusionBlock eth.BlockID) { func (s *channelManager) TxConfirmed(id txID, inclusionBlock eth.BlockID) {
s.log.Trace("marked transaction as confirmed", "id", id, "block", inclusionBlock) s.log.Trace("marked transaction as confirmed", "id", id, "block", inclusionBlock)
if _, ok := s.pendingTransactions[id]; !ok { if _, ok := s.pendingTransactions[id]; !ok {
s.log.Info("marked transaction as confirmed despite having no record of it", "id", id, "block", inclusionBlock) s.log.Warn("unknown transaction marked as confirmed", "id", id, "block", inclusionBlock)
// TODO: This can occur if we clear the channel while there are still pending transactions // TODO: This can occur if we clear the channel while there are still pending transactions
// We need to keep track of stale transactions instead // We need to keep track of stale transactions instead
return return
...@@ -111,13 +112,13 @@ func (s *channelManager) TxConfirmed(id txID, inclusionBlock eth.BlockID) { ...@@ -111,13 +112,13 @@ func (s *channelManager) TxConfirmed(id txID, inclusionBlock eth.BlockID) {
// If this channel timed out, put the pending blocks back into the local saved blocks // If this channel timed out, put the pending blocks back into the local saved blocks
// and then reset this state so it can try to build a new channel. // and then reset this state so it can try to build a new channel.
if s.pendingChannelIsTimedOut() { if s.pendingChannelIsTimedOut() {
s.log.Warn("Channel timed out", "chID", s.pendingChannel) s.log.Warn("Channel timed out", "chID", s.pendingChannel.ID())
s.blocks = append(s.pendingBlocks, s.blocks...) s.blocks = append(s.pendingChannel.Blocks(), s.blocks...)
s.clearPendingChannel() s.clearPendingChannel()
} }
// If we are done with this channel, record that. // If we are done with this channel, record that.
if s.pendingChannelIsFullySubmitted() { if s.pendingChannelIsFullySubmitted() {
s.log.Info("Channel is fully submitted", "chID", s.pendingChannel) s.log.Info("Channel is fully submitted", "chID", s.pendingChannel.ID())
s.clearPendingChannel() s.clearPendingChannel()
} }
} }
...@@ -125,8 +126,7 @@ func (s *channelManager) TxConfirmed(id txID, inclusionBlock eth.BlockID) { ...@@ -125,8 +126,7 @@ func (s *channelManager) TxConfirmed(id txID, inclusionBlock eth.BlockID) {
// clearPendingChannel resets all pending state back to an initialized but empty state. // clearPendingChannel resets all pending state back to an initialized but empty state.
// TODO: Create separate "pending" state // TODO: Create separate "pending" state
func (s *channelManager) clearPendingChannel() { func (s *channelManager) clearPendingChannel() {
s.pendingChannel = derive.ChannelID{} s.pendingChannel = nil
s.pendingBlocks = nil
s.pendingTransactions = make(map[txID][]byte) s.pendingTransactions = make(map[txID][]byte)
s.confirmedTransactions = make(map[txID]eth.BlockID) s.confirmedTransactions = make(map[txID]eth.BlockID)
} }
...@@ -135,7 +135,7 @@ func (s *channelManager) clearPendingChannel() { ...@@ -135,7 +135,7 @@ func (s *channelManager) clearPendingChannel() {
// A channel has timed out if the difference in L1 Inclusion blocks between // A channel has timed out if the difference in L1 Inclusion blocks between
// the first & last included block is greater than or equal to the channel timeout. // the first & last included block is greater than or equal to the channel timeout.
func (s *channelManager) pendingChannelIsTimedOut() bool { func (s *channelManager) pendingChannelIsTimedOut() bool {
if s.pendingChannel == (derive.ChannelID{}) { if s.pendingChannel == nil {
return false // no channel to be timed out return false // no channel to be timed out
} }
// No confirmed transactions => not timed out // No confirmed transactions => not timed out
...@@ -153,130 +153,133 @@ func (s *channelManager) pendingChannelIsTimedOut() bool { ...@@ -153,130 +153,133 @@ func (s *channelManager) pendingChannelIsTimedOut() bool {
max = inclusionBlock.Number max = inclusionBlock.Number
} }
} }
return max-min >= s.channelTimeout return max-min >= s.cfg.ChannelTimeout
} }
// pendingChannelIsFullySubmitted returns true if the channel has been fully submitted. // pendingChannelIsFullySubmitted returns true if the channel has been fully submitted.
func (s *channelManager) pendingChannelIsFullySubmitted() bool { func (s *channelManager) pendingChannelIsFullySubmitted() bool {
if s.pendingChannel == (derive.ChannelID{}) { if s.pendingChannel == nil {
return false // todo: can decide either way here. Nonsensical answer though return false // todo: can decide either way here. Nonsensical answer though
} }
return len(s.pendingTransactions)+len(s.datas) == 0 return s.pendingChannel.IsFull() && len(s.pendingTransactions)+s.pendingChannel.NumFrames() == 0
}
// blocksToFrames turns a set of blocks into a set of frames inside a channel.
// It will only create a single channel which contains up to `MAX_RLP_BYTES`. Any
// blocks not added to the channel are returned. It uses the max supplied frame size.
func blocksToFrames(blocks []*types.Block, maxFrameSize uint64) (derive.ChannelID, [][]byte, []*types.Block, error) {
ch, err := derive.NewChannelOut()
if err != nil {
return derive.ChannelID{}, nil, nil, err
}
i := 0
for ; i < len(blocks); i++ {
if err := ch.AddBlock(blocks[i]); err == derive.ErrTooManyRLPBytes {
break
} else if err != nil {
return derive.ChannelID{}, nil, nil, err
}
}
if err := ch.Close(); err != nil {
return derive.ChannelID{}, nil, nil, err
}
var frames [][]byte
for {
var buf bytes.Buffer
buf.WriteByte(derive.DerivationVersion0)
err := ch.OutputFrame(&buf, maxFrameSize-1)
if err != io.EOF && err != nil {
return derive.ChannelID{}, nil, nil, err
}
frames = append(frames, buf.Bytes())
if err == io.EOF {
break
}
}
return ch.ID(), frames, blocks[i:], nil
} }
// nextTxData pops off s.datas & handles updating the internal state // nextTxData pops off s.datas & handles updating the internal state
func (s *channelManager) nextTxData() ([]byte, txID, error) { func (s *channelManager) nextTxData() ([]byte, txID, error) {
if len(s.datas) != 0 { if s.pendingChannel == nil || !s.pendingChannel.HasFrame() {
r := s.datas[0] s.log.Trace("no next tx data")
s.log.Trace("returning next tx data", "id", r.id)
s.pendingTransactions[r.id] = r.data
s.datas = s.datas[1:]
return r.data, r.id, nil
} else {
return nil, txID{}, io.EOF // TODO: not enough data error instead return nil, txID{}, io.EOF // TODO: not enough data error instead
} }
id, data := s.pendingChannel.NextFrame()
// prepend version byte for first frame of transaction
// TODO: more memory efficient solution; shouldn't be responsibility of
// channelBuilder though.
data = append([]byte{0}, data...)
s.log.Trace("returning next tx data", "id", id)
s.pendingTransactions[id] = data
return data, id, nil
} }
// TxData returns the next tx.data that should be submitted to L1. // TxData returns the next tx data that should be submitted to L1.
// It is very simple & currently ignores the l1Head provided (this will change). //
// It may buffer very large channels as well. // It currently only uses one frame per transaction. If the pending channel is
// full, it only returns the remaining frames of this channel until it got
// successfully fully sent to L1. It returns io.EOF if there's no pending frame.
//
// It currently ignores the l1Head provided and doesn't track channel timeouts
// or the sequencer window span yet.
func (s *channelManager) TxData(l1Head eth.L1BlockRef) ([]byte, txID, error) { func (s *channelManager) TxData(l1Head eth.L1BlockRef) ([]byte, txID, error) {
channelPending := s.pendingChannel != (derive.ChannelID{}) dataPending := s.pendingChannel != nil && s.pendingChannel.HasFrame()
s.log.Debug("Requested tx data", "l1Head", l1Head, "channel_pending", channelPending, "block_count", len(s.blocks)) s.log.Debug("Requested tx data", "l1Head", l1Head, "data_pending", dataPending, "blocks_pending", len(s.blocks))
// Short circuit if there is a pending channel. // Short circuit if there is a pending frame.
// We either submit the next frame from that channel or if dataPending {
if channelPending {
return s.nextTxData() return s.nextTxData()
} }
// No pending frame, so we have to add new blocks to the channel
// If we have no saved blocks, we will not be able to create valid frames // If we have no saved blocks, we will not be able to create valid frames
if len(s.blocks) == 0 { if len(s.blocks) == 0 {
return nil, txID{}, io.EOF return nil, txID{}, io.EOF
} }
// Select range of blocks if err := s.ensurePendingChannel(l1Head); err != nil {
end := len(s.blocks)
if end > 100 {
end = 100
}
blocks := s.blocks[:end]
s.blocks = s.blocks[end:]
chID, frames, leftOverBlocks, err := blocksToFrames(blocks, 120_000)
// If the range of blocks serialized to be too large, restore
// blocks that could not be included inside the channel
if len(leftOverBlocks) != 0 {
s.blocks = append(leftOverBlocks, s.blocks...)
}
// TODO: Ensure that len(frames) < math.MaxUint16. Should not happen though. One tricky part
// is ensuring that s.blocks is properly restored.
if err != nil {
s.log.Warn("Failed to create channel from blocks", "err", err)
return nil, txID{}, err return nil, txID{}, err
} }
s.log.Info("Created channel", "chID", chID, "frame_count", len(frames), "l1Head", l1Head)
var t []taggedData if err := s.addBlocks(); err != nil {
for i, data := range frames { return nil, txID{}, err
t = append(t, taggedData{data: data, id: txID{chID: chID, frameNumber: uint16(i)}})
} }
// Load up pending state. Note: pending transactions is taken care of by nextTxData if err := s.pendingChannel.OutputFrames(); err != nil {
s.datas = t return nil, txID{}, fmt.Errorf("creating frames with channel builder: %w", err)
s.pendingChannel = chID }
s.pendingBlocks = blocks[:len(leftOverBlocks)]
return s.nextTxData() return s.nextTxData()
}
func (s *channelManager) ensurePendingChannel(l1Head eth.L1BlockRef) error {
if s.pendingChannel != nil {
return nil
}
cb, err := newChannelBuilder(s.cfg)
if err != nil {
return fmt.Errorf("creating new channel: %w", err)
}
s.pendingChannel = cb
s.log.Info("Created channel", "chID", cb.ID(), "l1Head", l1Head)
return nil
}
// addBlocks adds blocks from the blocks queue to the pending channel until
// either the queue got exhausted or the channel is full.
func (s *channelManager) addBlocks() error {
var blocksAdded int
var _chFullErr *ChannelFullError // throw away, just for type checking
for i, block := range s.blocks {
if err := s.pendingChannel.AddBlock(block); errors.As(err, &_chFullErr) {
// current block didn't get added because channel is already full
break
} else if err != nil {
return fmt.Errorf("adding block[%d] to channel builder: %w", i, err)
}
blocksAdded += 1
// current block got added but channel is now full
if s.pendingChannel.IsFull() {
break
}
}
s.log.Debug("Added blocks to channel",
"blocks_added", blocksAdded,
"channel_full", s.pendingChannel.IsFull(),
"blocks_pending", len(s.blocks)-blocksAdded,
"input_bytes", s.pendingChannel.InputBytes(),
)
if blocksAdded == len(s.blocks) {
// all blocks processed, reuse slice
s.blocks = s.blocks[:0]
} else {
// remove processed blocks
s.blocks = s.blocks[blocksAdded:]
}
return nil
} }
// AddL2Block saves an L2 block to the internal state. It returns ErrReorg // AddL2Block saves an L2 block to the internal state. It returns ErrReorg
// if the block does not extend the last block loaded into the state. // if the block does not extend the last block loaded into the state.
// If no block is already in the channel, the the parent hash check is skipped. // If no blocks were added yet, the parent hash check is skipped.
// TODO: Phantom last block b/c if the local state is fully drained we can reorg without realizing it.
func (s *channelManager) AddL2Block(block *types.Block) error { func (s *channelManager) AddL2Block(block *types.Block) error {
if len(s.blocks) > 0 { if s.tip != (common.Hash{}) && s.tip != block.ParentHash() {
if s.blocks[len(s.blocks)-1].Hash() != block.ParentHash() { return ErrReorg
return ErrReorg
}
} }
s.blocks = append(s.blocks, block) s.blocks = append(s.blocks, block)
s.tip = block.Hash()
return nil return nil
} }
package batcher package batcher
import ( import (
"math/big"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/urfave/cli" "github.com/urfave/cli"
"github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-node/sources"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
opsigner "github.com/ethereum-optimism/optimism/op-signer/client" opsigner "github.com/ethereum-optimism/optimism/op-signer/client"
) )
type Config struct { type Config struct {
log log.Logger
L1Client *ethclient.Client
L2Client *ethclient.Client
RollupNode *sources.RollupClient
PollInterval time.Duration
TxManagerConfig txmgr.Config
From common.Address
SignerFnFactory opcrypto.SignerFactory
ChainID *big.Int
// Where to send the batch txs to.
BatchInboxAddress common.Address
// Channel creation parameters
Channel ChannelConfig
}
type CLIConfig struct {
/* Required Params */ /* Required Params */
// L1EthRpc is the HTTP provider URL for L1. // L1EthRpc is the HTTP provider URL for L1.
...@@ -25,12 +50,6 @@ type Config struct { ...@@ -25,12 +50,6 @@ type Config struct {
// RollupRpc is the HTTP provider URL for the L2 rollup node. // RollupRpc is the HTTP provider URL for the L2 rollup node.
RollupRpc string RollupRpc string
// MinL1TxSize is the minimum size of a batch tx submitted to L1.
MinL1TxSize uint64
// MaxL1TxSize is the maximum size of a batch tx submitted to L1.
MaxL1TxSize uint64
// ChannelTimeout is the maximum amount of time to attempt completing an opened channel, // ChannelTimeout is the maximum amount of time to attempt completing an opened channel,
// as opposed to submitting missing blocks in new channels // as opposed to submitting missing blocks in new channels
ChannelTimeout uint64 ChannelTimeout uint64
...@@ -72,6 +91,19 @@ type Config struct { ...@@ -72,6 +91,19 @@ type Config struct {
/* Optional Params */ /* Optional Params */
// MaxL1TxSize is the maximum size of a batch tx submitted to L1.
MaxL1TxSize uint64
// TargetL1TxSize is the target size of a batch tx submitted to L1.
TargetL1TxSize uint64
// TargetNumFrames is the target number of frames per channel.
TargetNumFrames int
// ApproxComprRatio is the approximate compression ratio (<= 1.0) of the used
// compression algorithm.
ApproxComprRatio float64
LogConfig oplog.CLIConfig LogConfig oplog.CLIConfig
MetricsConfig opmetrics.CLIConfig MetricsConfig opmetrics.CLIConfig
...@@ -82,7 +114,7 @@ type Config struct { ...@@ -82,7 +114,7 @@ type Config struct {
SignerConfig opsigner.CLIConfig SignerConfig opsigner.CLIConfig
} }
func (c Config) Check() error { func (c CLIConfig) Check() error {
if err := c.RPCConfig.Check(); err != nil { if err := c.RPCConfig.Check(); err != nil {
return err return err
} }
...@@ -102,19 +134,23 @@ func (c Config) Check() error { ...@@ -102,19 +134,23 @@ func (c Config) Check() error {
} }
// NewConfig parses the Config from the provided flags or environment variables. // NewConfig parses the Config from the provided flags or environment variables.
func NewConfig(ctx *cli.Context) Config { func NewConfig(ctx *cli.Context) CLIConfig {
return Config{ return CLIConfig{
/* Required Flags */ /* Required Flags */
L1EthRpc: ctx.GlobalString(flags.L1EthRpcFlag.Name), L1EthRpc: ctx.GlobalString(flags.L1EthRpcFlag.Name),
L2EthRpc: ctx.GlobalString(flags.L2EthRpcFlag.Name), L2EthRpc: ctx.GlobalString(flags.L2EthRpcFlag.Name),
RollupRpc: ctx.GlobalString(flags.RollupRpcFlag.Name), RollupRpc: ctx.GlobalString(flags.RollupRpcFlag.Name),
MinL1TxSize: ctx.GlobalUint64(flags.MinL1TxSizeBytesFlag.Name), ChannelTimeout: ctx.GlobalUint64(flags.ChannelTimeoutFlag.Name),
PollInterval: ctx.GlobalDuration(flags.PollIntervalFlag.Name),
NumConfirmations: ctx.GlobalUint64(flags.NumConfirmationsFlag.Name),
SafeAbortNonceTooLowCount: ctx.GlobalUint64(flags.SafeAbortNonceTooLowCountFlag.Name),
ResubmissionTimeout: ctx.GlobalDuration(flags.ResubmissionTimeoutFlag.Name),
/* Optional Flags */
MaxL1TxSize: ctx.GlobalUint64(flags.MaxL1TxSizeBytesFlag.Name), MaxL1TxSize: ctx.GlobalUint64(flags.MaxL1TxSizeBytesFlag.Name),
ChannelTimeout: ctx.GlobalUint64(flags.ChannelTimeoutFlag.Name), TargetL1TxSize: ctx.GlobalUint64(flags.TargetL1TxSizeBytesFlag.Name),
PollInterval: ctx.GlobalDuration(flags.PollIntervalFlag.Name), TargetNumFrames: ctx.GlobalInt(flags.TargetNumFramesFlag.Name),
NumConfirmations: ctx.GlobalUint64(flags.NumConfirmationsFlag.Name), ApproxComprRatio: ctx.GlobalFloat64(flags.ApproxComprRatioFlag.Name),
SafeAbortNonceTooLowCount: ctx.GlobalUint64(flags.SafeAbortNonceTooLowCountFlag.Name),
ResubmissionTimeout: ctx.GlobalDuration(flags.ResubmissionTimeoutFlag.Name),
Mnemonic: ctx.GlobalString(flags.MnemonicFlag.Name), Mnemonic: ctx.GlobalString(flags.MnemonicFlag.Name),
SequencerHDPath: ctx.GlobalString(flags.SequencerHDPathFlag.Name), SequencerHDPath: ctx.GlobalString(flags.SequencerHDPathFlag.Name),
PrivateKey: ctx.GlobalString(flags.PrivateKeyFlag.Name), PrivateKey: ctx.GlobalString(flags.PrivateKeyFlag.Name),
......
...@@ -2,34 +2,28 @@ package batcher ...@@ -2,34 +2,28 @@ package batcher
import ( import (
"context" "context"
"crypto/ecdsa"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math/big" "math/big"
"strings" _ "net/http/pprof"
"sync" "sync"
"time" "time"
hdwallet "github.com/ethereum-optimism/go-ethereum-hdwallet"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
// BatchSubmitter encapsulates a service responsible for submitting L2 tx // BatchSubmitter encapsulates a service responsible for submitting L2 tx
// batches to L1 for availability. // batches to L1 for availability.
type BatchSubmitter struct { type BatchSubmitter struct {
Config // directly embed the config + sources
txMgr *TransactionManager txMgr *TransactionManager
addr common.Address
cfg DriverConfig
wg sync.WaitGroup wg sync.WaitGroup
done chan struct{} done chan struct{}
log log.Logger
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
...@@ -40,63 +34,16 @@ type BatchSubmitter struct { ...@@ -40,63 +34,16 @@ type BatchSubmitter struct {
state *channelManager state *channelManager
} }
// NewBatchSubmitter initializes the BatchSubmitter, gathering any resources // NewBatchSubmitterFromCLIConfig initializes the BatchSubmitter, gathering any resources
// that will be needed during operation. // that will be needed during operation.
func NewBatchSubmitter(cfg Config, l log.Logger) (*BatchSubmitter, error) { func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*BatchSubmitter, error) {
var err error ctx := context.Background()
var sequencerPrivKey *ecdsa.PrivateKey
var addr common.Address
if cfg.PrivateKey != "" && cfg.Mnemonic != "" {
return nil, errors.New("cannot specify both a private key and a mnemonic")
}
if cfg.PrivateKey == "" {
// Parse wallet private key that will be used to submit L2 txs to the batch
// inbox address.
wallet, err := hdwallet.NewFromMnemonic(cfg.Mnemonic)
if err != nil {
return nil, err
}
acc := accounts.Account{
URL: accounts.URL{
Path: cfg.SequencerHDPath,
},
}
addr, err = wallet.Address(acc)
if err != nil {
return nil, err
}
sequencerPrivKey, err = wallet.PrivateKey(acc)
if err != nil {
return nil, err
}
} else {
sequencerPrivKey, err = crypto.HexToECDSA(strings.TrimPrefix(cfg.PrivateKey, "0x"))
if err != nil {
return nil, err
}
addr = crypto.PubkeyToAddress(sequencerPrivKey.PublicKey)
}
signer := func(chainID *big.Int) SignerFn { signer, fromAddress, err := opcrypto.SignerFactoryFromConfig(l, cfg.PrivateKey, cfg.Mnemonic, cfg.SequencerHDPath, cfg.SignerConfig)
s := types.LatestSignerForChainID(chainID) if err != nil {
return func(_ context.Context, rawTx types.TxData) (*types.Transaction, error) { return nil, err
return types.SignNewTx(sequencerPrivKey, s, rawTx)
}
} }
return NewBatchSubmitterWithSigner(cfg, addr, signer, l)
}
type SignerFactory func(chainID *big.Int) SignerFn
func NewBatchSubmitterWithSigner(cfg Config, addr common.Address, signer SignerFactory, l log.Logger) (*BatchSubmitter, error) {
ctx := context.Background()
batchInboxAddress, err := parseAddress(cfg.SequencerBatchInboxAddress) batchInboxAddress, err := parseAddress(cfg.SequencerBatchInboxAddress)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -124,13 +71,6 @@ func NewBatchSubmitterWithSigner(cfg Config, addr common.Address, signer SignerF ...@@ -124,13 +71,6 @@ func NewBatchSubmitterWithSigner(cfg Config, addr common.Address, signer SignerF
return nil, err return nil, err
} }
sequencerBalance, err := l1Client.BalanceAt(ctx, addr, nil)
if err != nil {
return nil, err
}
log.Info("starting batch submitter", "submitter_addr", addr, "submitter_bal", sequencerBalance)
txManagerConfig := txmgr.Config{ txManagerConfig := txmgr.Config{
Log: l, Log: l,
Name: "Batch Submitter", Name: "Batch Submitter",
...@@ -140,34 +80,53 @@ func NewBatchSubmitterWithSigner(cfg Config, addr common.Address, signer SignerF ...@@ -140,34 +80,53 @@ func NewBatchSubmitterWithSigner(cfg Config, addr common.Address, signer SignerF
SafeAbortNonceTooLowCount: cfg.SafeAbortNonceTooLowCount, SafeAbortNonceTooLowCount: cfg.SafeAbortNonceTooLowCount,
} }
batcherCfg := DriverConfig{ batcherCfg := Config{
Log: l,
Name: "Batch Submitter",
L1Client: l1Client, L1Client: l1Client,
L2Client: l2Client, L2Client: l2Client,
RollupNode: rollupClient, RollupNode: rollupClient,
MinL1TxSize: cfg.MinL1TxSize,
MaxL1TxSize: cfg.MaxL1TxSize,
BatchInboxAddress: batchInboxAddress,
ChannelTimeout: cfg.ChannelTimeout,
ChainID: chainID, ChainID: chainID,
PollInterval: cfg.PollInterval, PollInterval: cfg.PollInterval,
TxManagerConfig: txManagerConfig,
From: fromAddress,
SignerFnFactory: signer,
BatchInboxAddress: batchInboxAddress,
Channel: ChannelConfig{
ChannelTimeout: cfg.ChannelTimeout,
MaxFrameSize: cfg.MaxL1TxSize - 1, // subtract 1 byte for version
TargetFrameSize: cfg.TargetL1TxSize - 1, // subtract 1 byte for version
TargetNumFrames: cfg.TargetNumFrames,
ApproxComprRatio: cfg.ApproxComprRatio,
},
} }
return NewBatchSubmitter(batcherCfg, l)
}
// NewBatchSubmitter initializes the BatchSubmitter, gathering any resources
// that will be needed during operation.
func NewBatchSubmitter(cfg Config, l log.Logger) (*BatchSubmitter, error) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
balance, err := cfg.L1Client.BalanceAt(ctx, cfg.From, nil)
if err != nil {
cancel()
return nil, err
}
cfg.log = l
cfg.log.Info("creating batch submitter", "submitter_addr", cfg.From, "submitter_bal", balance)
return &BatchSubmitter{ return &BatchSubmitter{
cfg: batcherCfg, Config: cfg,
addr: addr, txMgr: NewTransactionManager(l, cfg.TxManagerConfig, cfg.BatchInboxAddress, cfg.ChainID, cfg.From, cfg.L1Client, cfg.SignerFnFactory(cfg.ChainID)),
txMgr: NewTransactionManager(l, txManagerConfig, batchInboxAddress, chainID, addr, l1Client, signer(chainID)), done: make(chan struct{}),
done: make(chan struct{}),
log: l,
state: NewChannelManager(l, cfg.ChannelTimeout),
// TODO: this context only exists because the event loop doesn't reach done // TODO: this context only exists because the event loop doesn't reach done
// if the tx manager is blocking forever due to e.g. insufficient balance. // if the tx manager is blocking forever due to e.g. insufficient balance.
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
state: NewChannelManager(l, cfg.Channel),
}, nil }, nil
} }
func (l *BatchSubmitter) Start() error { func (l *BatchSubmitter) Start() error {
...@@ -214,7 +173,7 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) { ...@@ -214,7 +173,7 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) {
// loadBlockIntoState fetches & stores a single block into `state`. It returns the block it loaded. // loadBlockIntoState fetches & stores a single block into `state`. It returns the block it loaded.
func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uint64) (eth.BlockID, error) { func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uint64) (eth.BlockID, error) {
ctx, cancel := context.WithTimeout(ctx, networkTimeout) ctx, cancel := context.WithTimeout(ctx, networkTimeout)
block, err := l.cfg.L2Client.BlockByNumber(ctx, new(big.Int).SetUint64(blockNumber)) block, err := l.L2Client.BlockByNumber(ctx, new(big.Int).SetUint64(blockNumber))
cancel() cancel()
if err != nil { if err != nil {
return eth.BlockID{}, err return eth.BlockID{}, err
...@@ -232,7 +191,7 @@ func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uin ...@@ -232,7 +191,7 @@ func (l *BatchSubmitter) loadBlockIntoState(ctx context.Context, blockNumber uin
func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth.BlockID, eth.BlockID, error) { func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth.BlockID, eth.BlockID, error) {
childCtx, cancel := context.WithTimeout(ctx, networkTimeout) childCtx, cancel := context.WithTimeout(ctx, networkTimeout)
defer cancel() defer cancel()
syncStatus, err := l.cfg.RollupNode.SyncStatus(childCtx) syncStatus, err := l.RollupNode.SyncStatus(childCtx)
// Ensure that we have the sync status // Ensure that we have the sync status
if err != nil { if err != nil {
return eth.BlockID{}, eth.BlockID{}, fmt.Errorf("failed to get sync status: %w", err) return eth.BlockID{}, eth.BlockID{}, fmt.Errorf("failed to get sync status: %w", err)
...@@ -273,7 +232,7 @@ func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth. ...@@ -273,7 +232,7 @@ func (l *BatchSubmitter) calculateL2BlockRangeToStore(ctx context.Context) (eth.
func (l *BatchSubmitter) loop() { func (l *BatchSubmitter) loop() {
defer l.wg.Done() defer l.wg.Done()
ticker := time.NewTicker(l.cfg.PollInterval) ticker := time.NewTicker(l.PollInterval)
defer ticker.Stop() defer ticker.Stop()
for { for {
select { select {
......
package batcher
import (
"math/big"
"time"
"github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
type DriverConfig struct {
Log log.Logger
Name string
// API to submit txs to
L1Client *ethclient.Client
// API to hit for batch data
L2Client *ethclient.Client
RollupNode *sources.RollupClient
// Limit the size of txs
MinL1TxSize uint64
MaxL1TxSize uint64
// Where to send the batch txs to.
BatchInboxAddress common.Address
// The batcher can decide to set it shorter than the actual timeout,
// since submitting continued channel data to L1 is not instantaneous.
// It's not worth it to work with nearly timed-out channels.
ChannelTimeout uint64
// Chain ID of the L1 chain to submit txs to.
ChainID *big.Int
PollInterval time.Duration
}
...@@ -12,12 +12,13 @@ import ( ...@@ -12,12 +12,13 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
) )
const networkTimeout = 2 * time.Second // How long a single network request can take. TODO: put in a config somewhere const networkTimeout = 2 * time.Second // How long a single network request can take. TODO: put in a config somewhere
type SignerFn func(ctx context.Context, rawTx types.TxData) (*types.Transaction, error)
// TransactionManager wraps the simple txmgr package to make it easy to send & wait for transactions // TransactionManager wraps the simple txmgr package to make it easy to send & wait for transactions
type TransactionManager struct { type TransactionManager struct {
// Config // Config
...@@ -27,11 +28,11 @@ type TransactionManager struct { ...@@ -27,11 +28,11 @@ type TransactionManager struct {
// Outside world // Outside world
txMgr txmgr.TxManager txMgr txmgr.TxManager
l1Client *ethclient.Client l1Client *ethclient.Client
signerFn SignerFn signerFn opcrypto.SignerFn
log log.Logger log log.Logger
} }
func NewTransactionManager(log log.Logger, txMgrConfg txmgr.Config, batchInboxAddress common.Address, chainID *big.Int, senderAddress common.Address, l1Client *ethclient.Client, signerFn SignerFn) *TransactionManager { func NewTransactionManager(log log.Logger, txMgrConfg txmgr.Config, batchInboxAddress common.Address, chainID *big.Int, senderAddress common.Address, l1Client *ethclient.Client, signerFn opcrypto.SignerFn) *TransactionManager {
t := &TransactionManager{ t := &TransactionManager{
batchInboxAddress: batchInboxAddress, batchInboxAddress: batchInboxAddress,
senderAddress: senderAddress, senderAddress: senderAddress,
...@@ -78,12 +79,20 @@ func (t *TransactionManager) calcGasTipAndFeeCap(ctx context.Context) (gasTipCap ...@@ -78,12 +79,20 @@ func (t *TransactionManager) calcGasTipAndFeeCap(ctx context.Context) (gasTipCap
return nil, nil, fmt.Errorf("failed to get suggested gas tip cap: %w", err) return nil, nil, fmt.Errorf("failed to get suggested gas tip cap: %w", err)
} }
if gasTipCap == nil {
t.log.Warn("unexpected unset gasTipCap, using default 2 gwei")
gasTipCap = new(big.Int).SetUint64(params.GWei * 2)
}
childCtx, cancel = context.WithTimeout(ctx, networkTimeout) childCtx, cancel = context.WithTimeout(ctx, networkTimeout)
head, err := t.l1Client.HeaderByNumber(childCtx, nil) head, err := t.l1Client.HeaderByNumber(childCtx, nil)
cancel() cancel()
if err != nil { if err != nil || head == nil {
return nil, nil, fmt.Errorf("failed to get L1 head block for fee cap: %w", err) return nil, nil, fmt.Errorf("failed to get L1 head block for fee cap: %w", err)
} }
if head.BaseFee == nil {
return nil, nil, fmt.Errorf("failed to get L1 basefee in block %d for fee cap", head.Number)
}
gasFeeCap = txmgr.CalcGasFeeCap(head.BaseFee, gasTipCap) gasFeeCap = txmgr.CalcGasFeeCap(head.BaseFee, gasTipCap)
return gasTipCap, gasFeeCap, nil return gasTipCap, gasFeeCap, nil
...@@ -123,7 +132,8 @@ func (t *TransactionManager) CraftTx(ctx context.Context, data []byte) (*types.T ...@@ -123,7 +132,8 @@ func (t *TransactionManager) CraftTx(ctx context.Context, data []byte) (*types.T
ctx, cancel = context.WithTimeout(ctx, networkTimeout) ctx, cancel = context.WithTimeout(ctx, networkTimeout)
defer cancel() defer cancel()
return t.signerFn(ctx, rawTx) tx := types.NewTx(rawTx)
return t.signerFn(ctx, t.senderAddress, tx)
} }
// UpdateGasPrice signs an otherwise identical txn to the one provided but with // UpdateGasPrice signs an otherwise identical txn to the one provided but with
...@@ -148,5 +158,6 @@ func (t *TransactionManager) UpdateGasPrice(ctx context.Context, tx *types.Trans ...@@ -148,5 +158,6 @@ func (t *TransactionManager) UpdateGasPrice(ctx context.Context, tx *types.Trans
// Only log the new tip/fee cap because the updateGasPrice closure reuses the same initial transaction // Only log the new tip/fee cap because the updateGasPrice closure reuses the same initial transaction
t.log.Trace("updating gas price", "tip_cap", gasTipCap, "fee_cap", gasFeeCap) t.log.Trace("updating gas price", "tip_cap", gasTipCap, "fee_cap", gasFeeCap)
return t.signerFn(ctx, rawTx) finalTx := types.NewTx(rawTx)
return t.signerFn(ctx, t.senderAddress, finalTx)
} }
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
) )
var ( var (
Version = "v0.10.11" Version = "v0.10.12"
GitCommit = "" GitCommit = ""
GitDate = "" GitDate = ""
) )
...@@ -29,9 +29,17 @@ func main() { ...@@ -29,9 +29,17 @@ func main() {
app.Description = "Service for generating and submitting L2 tx batches " + app.Description = "Service for generating and submitting L2 tx batches " +
"to L1" "to L1"
app.Action = batcher.Main(Version) app.Action = curryMain(Version)
err := app.Run(os.Args) err := app.Run(os.Args)
if err != nil { if err != nil {
log.Crit("Application failed", "message", err) log.Crit("Application failed", "message", err)
} }
} }
// curryMain transforms the batcher.Main function into an app.Action
// This is done to capture the Version of the batcher.
func curryMain(version string) func(ctx *cli.Context) error {
return func(ctx *cli.Context) error {
return batcher.Main(version, ctx)
}
}
...@@ -14,7 +14,7 @@ import ( ...@@ -14,7 +14,7 @@ import (
const envVarPrefix = "OP_BATCHER" const envVarPrefix = "OP_BATCHER"
var ( var (
/* Required Flags */ /* Required flags */
L1EthRpcFlag = cli.StringFlag{ L1EthRpcFlag = cli.StringFlag{
Name: "l1-eth-rpc", Name: "l1-eth-rpc",
...@@ -34,21 +34,9 @@ var ( ...@@ -34,21 +34,9 @@ var (
Required: true, Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ROLLUP_RPC"), EnvVar: opservice.PrefixEnvVar(envVarPrefix, "ROLLUP_RPC"),
} }
MinL1TxSizeBytesFlag = cli.Uint64Flag{
Name: "min-l1-tx-size-bytes",
Usage: "The minimum size of a batch tx submitted to L1.",
Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "MIN_L1_TX_SIZE_BYTES"),
}
MaxL1TxSizeBytesFlag = cli.Uint64Flag{
Name: "max-l1-tx-size-bytes",
Usage: "The maximum size of a batch tx submitted to L1.",
Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "MAX_L1_TX_SIZE_BYTES"),
}
ChannelTimeoutFlag = cli.Uint64Flag{ ChannelTimeoutFlag = cli.Uint64Flag{
Name: "channel-timeout", Name: "channel-timeout",
Usage: "The maximum amount of time to attempt completing an opened channel, as opposed to submitting L2 blocks into a new channel.", Usage: "The maximum duration (in seconds) to attempt completing an opened channel, as opposed to submitting L2 blocks into a new channel.",
Required: true, Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "CHANNEL_TIMEOUT"), EnvVar: opservice.PrefixEnvVar(envVarPrefix, "CHANNEL_TIMEOUT"),
} }
...@@ -81,6 +69,39 @@ var ( ...@@ -81,6 +69,39 @@ var (
Required: true, Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "RESUBMISSION_TIMEOUT"), EnvVar: opservice.PrefixEnvVar(envVarPrefix, "RESUBMISSION_TIMEOUT"),
} }
SequencerBatchInboxAddressFlag = cli.StringFlag{
Name: "sequencer-batch-inbox-address",
Usage: "L1 Address to receive batch transactions",
Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "SEQUENCER_BATCH_INBOX_ADDRESS"),
}
/* Optional flags */
MaxL1TxSizeBytesFlag = cli.Uint64Flag{
Name: "max-l1-tx-size-bytes",
Usage: "The maximum size of a batch tx submitted to L1.",
Value: 120_000,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "MAX_L1_TX_SIZE_BYTES"),
}
TargetL1TxSizeBytesFlag = cli.Uint64Flag{
Name: "target-l1-tx-size-bytes",
Usage: "The target size of a batch tx submitted to L1.",
Value: 100_000,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "TARGET_L1_TX_SIZE_BYTES"),
}
TargetNumFramesFlag = cli.IntFlag{
Name: "target-num-frames",
Usage: "The target number of frames to create per channel",
Value: 1,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "TARGET_NUM_FRAMES"),
}
ApproxComprRatioFlag = cli.Float64Flag{
Name: "approx-compr-ratio",
Usage: "The approximate compression ratio (<= 1.0)",
Value: 1.0,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "APPROX_COMPR_RATIO"),
}
MnemonicFlag = cli.StringFlag{ MnemonicFlag = cli.StringFlag{
Name: "mnemonic", Name: "mnemonic",
Usage: "The mnemonic used to derive the wallets for either the " + Usage: "The mnemonic used to derive the wallets for either the " +
...@@ -98,20 +119,12 @@ var ( ...@@ -98,20 +119,12 @@ var (
Usage: "The private key to use with the l2output wallet. Must not be used with mnemonic.", Usage: "The private key to use with the l2output wallet. Must not be used with mnemonic.",
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "PRIVATE_KEY"), EnvVar: opservice.PrefixEnvVar(envVarPrefix, "PRIVATE_KEY"),
} }
SequencerBatchInboxAddressFlag = cli.StringFlag{
Name: "sequencer-batch-inbox-address",
Usage: "L1 Address to receive batch transactions",
Required: true,
EnvVar: opservice.PrefixEnvVar(envVarPrefix, "SEQUENCER_BATCH_INBOX_ADDRESS"),
}
) )
var requiredFlags = []cli.Flag{ var requiredFlags = []cli.Flag{
L1EthRpcFlag, L1EthRpcFlag,
L2EthRpcFlag, L2EthRpcFlag,
RollupRpcFlag, RollupRpcFlag,
MinL1TxSizeBytesFlag,
MaxL1TxSizeBytesFlag,
ChannelTimeoutFlag, ChannelTimeoutFlag,
PollIntervalFlag, PollIntervalFlag,
NumConfirmationsFlag, NumConfirmationsFlag,
...@@ -121,6 +134,10 @@ var requiredFlags = []cli.Flag{ ...@@ -121,6 +134,10 @@ var requiredFlags = []cli.Flag{
} }
var optionalFlags = []cli.Flag{ var optionalFlags = []cli.Flag{
MaxL1TxSizeBytesFlag,
TargetL1TxSizeBytesFlag,
TargetNumFramesFlag,
ApproxComprRatioFlag,
MnemonicFlag, MnemonicFlag,
SequencerHDPathFlag, SequencerHDPathFlag,
PrivateKeyFlag, PrivateKeyFlag,
......
...@@ -3,9 +3,8 @@ module github.com/ethereum-optimism/optimism/op-batcher ...@@ -3,9 +3,8 @@ module github.com/ethereum-optimism/optimism/op-batcher
go 1.18 go 1.18
require ( require (
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 github.com/ethereum-optimism/optimism/op-node v0.10.12
github.com/ethereum-optimism/optimism/op-node v0.10.11 github.com/ethereum-optimism/optimism/op-service v0.10.12
github.com/ethereum-optimism/optimism/op-service v0.10.11
github.com/ethereum-optimism/optimism/op-signer v0.1.0 github.com/ethereum-optimism/optimism/op-signer v0.1.0
github.com/ethereum/go-ethereum v1.10.26 github.com/ethereum/go-ethereum v1.10.26
github.com/urfave/cli v1.22.9 github.com/urfave/cli v1.22.9
...@@ -24,7 +23,8 @@ require ( ...@@ -24,7 +23,8 @@ require (
github.com/deckarep/golang-set v1.8.0 // indirect github.com/deckarep/golang-set v1.8.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/dyson/certman v0.3.0 // indirect github.com/dyson/certman v0.3.0 // indirect
github.com/ethereum-optimism/optimism/op-bindings v0.10.11 // indirect github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 // indirect
github.com/ethereum-optimism/optimism/op-bindings v0.10.12 // indirect
github.com/fjl/memsize v0.0.1 // indirect github.com/fjl/memsize v0.0.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
......
...@@ -109,12 +109,12 @@ github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z ...@@ -109,12 +109,12 @@ github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY=
github.com/ethereum-optimism/optimism/op-bindings v0.10.11 h1:RDiRyHo0G/UuxHZQdMJyqIuHtWvpionuFNfczNaWCcM= github.com/ethereum-optimism/optimism/op-bindings v0.10.12 h1:/B1gaCLwZYy9Rja3MiceV3R642bygp937kZjnYwRxA0=
github.com/ethereum-optimism/optimism/op-bindings v0.10.11/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c= github.com/ethereum-optimism/optimism/op-bindings v0.10.12/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c=
github.com/ethereum-optimism/optimism/op-node v0.10.11 h1:ED72b68ainzcXr5/cLOYRwv+LdE4hRDnkq3SmNRY1+Q= github.com/ethereum-optimism/optimism/op-node v0.10.12 h1:yOxMThwwz1rEDDM0xTjS+6jqJwgKRtrYM6h4Pdf0zno=
github.com/ethereum-optimism/optimism/op-node v0.10.11/go.mod h1:/CDpkMxc3mDklZ1nqz2lmxfeUyAUz7yC/OLmX8egAUw= github.com/ethereum-optimism/optimism/op-node v0.10.12/go.mod h1:z+DiFb82Vnn5zM3VEwc2OXK2V/JBg6MLg7ejTbsxye8=
github.com/ethereum-optimism/optimism/op-service v0.10.11 h1:o+SazhFXlE3EM9Re5KIPEQklZ9uTI8rNkjl0h5OwRtU= github.com/ethereum-optimism/optimism/op-service v0.10.12 h1:Y7pR3/b8eeHYkmo2V5z7sj8jaraYqm2Azyph5lbiIxo=
github.com/ethereum-optimism/optimism/op-service v0.10.11/go.mod h1:wbtHqi1fv00B3agj7a2zdP3OFanEfGZ23zPgGgFCF/c= github.com/ethereum-optimism/optimism/op-service v0.10.12/go.mod h1:Ibbun+aic0rjQBV8yBf9kohqIj6mQ8nSTWbZjHv+Q7Q=
github.com/ethereum-optimism/optimism/op-signer v0.1.0 h1:wH44Deai43YQWO0pEd44pDm3BahdAtSmrOHKiPvTB8Y= github.com/ethereum-optimism/optimism/op-signer v0.1.0 h1:wH44Deai43YQWO0pEd44pDm3BahdAtSmrOHKiPvTB8Y=
github.com/ethereum-optimism/optimism/op-signer v0.1.0/go.mod h1:u8sN6X/c20pP9F1Ey7jH3fi19D08Y+T9ep3PGJfdyi8= github.com/ethereum-optimism/optimism/op-signer v0.1.0/go.mod h1:u8sN6X/c20pP9F1Ey7jH3fi19D08Y+T9ep3PGJfdyi8=
github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ= github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ=
...@@ -391,7 +391,7 @@ github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w ...@@ -391,7 +391,7 @@ github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d h1:vmirMegf1vqPJ+lDBxLQ0MAt3tz+JL57UPxu44JBOjA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
......
package main
import (
"errors"
"os"
"github.com/mattn/go-isatty"
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-chain-ops/eof"
"github.com/ethereum/go-ethereum/log"
)
func main() {
log.Root().SetHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(isatty.IsTerminal(os.Stderr.Fd()))))
app := &cli.App{
Name: "eof-crawler",
Usage: "Scan a Geth database for EOF-prefixed contracts",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "db-path",
Usage: "Path to the geth LevelDB",
},
&cli.StringFlag{
Name: "out",
Value: "eof-contracts.json",
Usage: "Path to the output file",
},
},
Action: func(ctx *cli.Context) error {
dbPath := ctx.String("db-path")
if len(dbPath) == 0 {
return errors.New("Must specify a db-path")
}
out := ctx.String("out")
return eof.IndexEOFContracts(dbPath, out)
},
}
if err := app.Run(os.Args); err != nil {
log.Crit("error indexing state", "err", err)
}
}
package main package main
import ( import (
"bytes"
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"math/big"
"os" "os"
"strings"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis/migration"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethclient/gethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
) )
// TODO(tynes): handle connecting directly to a LevelDB based StateDB // abiTrue represents the storage representation of the boolean
// value true.
var abiTrue = common.Hash{31: 0x01}
// callFrame represents the response returned from geth's
// `debug_traceTransaction` callTracer
type callFrame struct {
Type string `json:"type"`
From string `json:"from"`
To string `json:"to,omitempty"`
Value string `json:"value,omitempty"`
Gas string `json:"gas"`
GasUsed string `json:"gasUsed"`
Input string `json:"input"`
Output string `json:"output,omitempty"`
Error string `json:"error,omitempty"`
Calls []callFrame `json:"calls,omitempty"`
}
// BigValue turns a 0x prefixed string into a `big.Int`
func (c *callFrame) BigValue() *big.Int {
v := strings.TrimPrefix(c.Value, "0x")
b, _ := new(big.Int).SetString(v, 16)
return b
}
// suspiciousWithdrawal represents a pending withdrawal that failed for some
// reason after the migration. These are written to disk so that they can
// be manually inspected.
type suspiciousWithdrawal struct {
Withdrawal *crossdomain.Withdrawal `json:"withdrawal"`
Legacy *crossdomain.LegacyWithdrawal `json:"legacy"`
Trace callFrame `json:"trace"`
Index int `json:"index"`
Reason string `json:"reason"`
}
func main() { func main() {
log.Root().SetHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(isatty.IsTerminal(os.Stderr.Fd())))) log.Root().SetHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(isatty.IsTerminal(os.Stderr.Fd()))))
app := &cli.App{ app := &cli.App{
Name: "withdrawals", Name: "withdrawals",
Usage: "fetches all pending withdrawals", Usage: "submits pending withdrawals",
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "l1-rpc-url", Name: "l1-rpc-url",
...@@ -33,63 +87,344 @@ func main() { ...@@ -33,63 +87,344 @@ func main() {
Usage: "RPC URL for an L2 Node", Usage: "RPC URL for an L2 Node",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "l1-cross-domain-messenger-address", Name: "optimism-portal-address",
Usage: "Address of the OptimismPortal on L1",
},
&cli.StringFlag{
Name: "l1-crossdomain-messenger-address",
Usage: "Address of the L1CrossDomainMessenger", Usage: "Address of the L1CrossDomainMessenger",
}, },
&cli.Uint64Flag{ &cli.StringFlag{
Name: "start", Name: "l1-standard-bridge-address",
Usage: "Start height to search for events", Usage: "Address of the L1StandardBridge",
},
&cli.StringFlag{
Name: "ovm-messages",
Usage: "Path to ovm-messages.json",
},
&cli.StringFlag{
Name: "evm-messages",
Usage: "Path to evm-messages.json",
}, },
&cli.Uint64Flag{ &cli.StringFlag{
Name: "end", Name: "private-key",
Usage: "End height to search for events", Usage: "Key to sign transactions with",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "outfile", Name: "bad-withdrawals-out",
Usage: "Path to output file", Value: "bad-withdrawals.json",
Value: "out.json", Usage: "Path to write JSON file of bad withdrawals to manually inspect",
}, },
}, },
Action: func(ctx *cli.Context) error { Action: func(ctx *cli.Context) error {
l1RpcURL := ctx.String("l1-rpc-url") clients, err := newClients(ctx)
l2RpcURL := ctx.String("l2-rpc-url") if err != nil {
return err
}
l1Client, err := ethclient.Dial(l1RpcURL) // initialize the contract bindings
contracts, err := newContracts(ctx, clients.L1Client, clients.L2Client)
if err != nil { if err != nil {
return err return err
} }
l2Client, err := ethclient.Dial(l2RpcURL) l1xdmAddr := common.HexToAddress(ctx.String("l1-crossdomain-messenger-address"))
l1ChainID, err := clients.L1Client.ChainID(context.Background())
if err != nil { if err != nil {
return err return err
} }
backends := crossdomain.NewBackends(l1Client, l2Client) // create the set of withdrawals
wds, err := newWithdrawals(ctx, l1ChainID)
if err != nil {
return err
}
l1xDomainMessenger := ctx.String("l1-cross-domain-messenger-address") period, err := contracts.OptimismPortal.FINALIZATIONPERIODSECONDS(&bind.CallOpts{})
if l1xDomainMessenger == "" { if err != nil {
return errors.New("Must pass in L1CrossDomainMessenger address") return err
} }
l1xDomainMessengerAddr := common.HexToAddress(l1xDomainMessenger) bedrockStartingBlockNumber, err := contracts.L2OutputOracle.StartingBlockNumber(&bind.CallOpts{})
messengers, err := crossdomain.NewMessengers(backends, l1xDomainMessengerAddr)
if err != nil { if err != nil {
return err return err
} }
start := ctx.Uint64("start") bedrockStartingBlock, err := clients.L2Client.BlockByNumber(context.Background(), bedrockStartingBlockNumber)
end := ctx.Uint64("end") if err != nil {
return err
}
log.Info("Withdrawal config", "finalization-period", period, "bedrock-starting-block-number", bedrockStartingBlockNumber, "bedrock-starting-block-hash", bedrockStartingBlock.Hash().Hex())
// All messages are expected to be version 0 messages if !bytes.Equal(bedrockStartingBlock.Extra(), genesis.BedrockTransitionBlockExtraData) {
withdrawals, err := crossdomain.GetPendingWithdrawals(messengers, common.Big0, start, end) return errors.New("genesis block mismatch")
}
outfile := ctx.String("bad-withdrawals-out")
f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o755)
if err != nil { if err != nil {
return err return err
} }
outfile := ctx.String("outfile") // create a transactor
if err := writeJSONFile(outfile, withdrawals); err != nil { opts, err := newTransactor(ctx)
if err != nil {
return err return err
} }
// Need this to compare in event parsing
l1StandardBridgeAddress := common.HexToAddress(ctx.String("l1-standard-bridge-address"))
// iterate over all of the withdrawals and submit them
for i, wd := range wds {
log.Info("Processing withdrawal", "index", i)
// migrate the withdrawal
withdrawal, err := crossdomain.MigrateWithdrawal(wd, &l1xdmAddr)
if err != nil {
return err
}
// Pass to Portal
hash, err := withdrawal.Hash()
if err != nil {
return err
}
lcdm := wd.CrossDomainMessage()
legacyXdmHash, err := lcdm.Hash()
if err != nil {
return err
}
// check to see if the withdrawal has already been successfully
// relayed or received
isSuccess, err := contracts.L1CrossDomainMessenger.SuccessfulMessages(&bind.CallOpts{}, legacyXdmHash)
if err != nil {
return err
}
isFailed, err := contracts.L1CrossDomainMessenger.FailedMessages(&bind.CallOpts{}, legacyXdmHash)
if err != nil {
return err
}
xdmHash := crypto.Keccak256Hash(withdrawal.Data)
if err != nil {
return err
}
isSuccessNew, err := contracts.L1CrossDomainMessenger.SuccessfulMessages(&bind.CallOpts{}, xdmHash)
if err != nil {
return err
}
isFailedNew, err := contracts.L1CrossDomainMessenger.FailedMessages(&bind.CallOpts{}, xdmHash)
if err != nil {
return err
}
log.Info("cross domain messenger status", "hash", legacyXdmHash.Hex(), "success", isSuccess, "failed", isFailed, "is-success-new", isSuccessNew, "is-failed-new", isFailedNew)
// compute the storage slot
slot, err := withdrawal.StorageSlot()
if err != nil {
return err
}
// successful messages can be skipped, received messages failed
// their execution and should be replayed
if isSuccessNew {
log.Info("Message already relayed", "index", i, "hash", hash, "slot", slot)
continue
}
// check the storage value of the slot to ensure that it is in
// the L2 storage. Without this check, the proof will fail
storageValue, err := clients.L2Client.StorageAt(context.Background(), predeploys.L2ToL1MessagePasserAddr, slot, nil)
if err != nil {
return err
}
log.Debug("L2ToL1MessagePasser status", "value", common.Bytes2Hex(storageValue))
// the value should be set to a boolean in storage
if !bytes.Equal(storageValue, abiTrue.Bytes()) {
return fmt.Errorf("storage slot %x not found in state", slot)
}
legacySlot, err := wd.StorageSlot()
if err != nil {
return err
}
legacyStorageValue, err := clients.L2Client.StorageAt(context.Background(), predeploys.LegacyMessagePasserAddr, legacySlot, nil)
if err != nil {
return err
}
log.Debug("LegacyMessagePasser status", "value", common.Bytes2Hex(legacyStorageValue))
// check to see if its already been proven
proven, err := contracts.OptimismPortal.ProvenWithdrawals(&bind.CallOpts{}, hash)
if err != nil {
return err
}
// if it has not been proven, then prove it
if proven.Timestamp.Cmp(common.Big0) == 0 {
log.Info("Proving withdrawal to OptimismPortal")
if err := proveWithdrawalTransaction(contracts, clients, opts, withdrawal, bedrockStartingBlockNumber, period); err != nil {
return err
}
} else {
log.Info("Withdrawal already proven to OptimismPortal")
}
// check to see if the withdrawal has been finalized already
isFinalized, err := contracts.OptimismPortal.FinalizedWithdrawals(&bind.CallOpts{}, hash)
if err != nil {
return err
}
if !isFinalized {
// Get the ETH balance of the withdrawal target *before* the finalization
targetBalBefore, err := clients.L1Client.BalanceAt(context.Background(), *wd.Target, nil)
if err != nil {
return err
}
log.Debug("Balance before finalization", "balance", targetBalBefore, "account", *wd.Target)
log.Info("Finalizing withdrawal")
receipt, err := finalizeWithdrawalTransaction(contracts, clients, opts, wd, withdrawal)
if err != nil {
return err
}
log.Info("withdrawal finalized", "tx-hash", receipt.TxHash, "withdrawal-hash", hash)
finalizationTrace, err := callTrace(clients, receipt)
if err != nil {
return nil
}
isSuccessNewPost, err := contracts.L1CrossDomainMessenger.SuccessfulMessages(&bind.CallOpts{}, xdmHash)
if err != nil {
return err
}
// This would indicate that there is a replayability problem
if isSuccess && isSuccessNewPost {
if err := writeSuspicious(f, withdrawal, wd, finalizationTrace, i, "should revert"); err != nil {
return err
}
panic("DOUBLE PLAYED DEPOSIT ALLOWED")
}
callFrame := findWithdrawalCall(&finalizationTrace, wd, l1xdmAddr)
if callFrame == nil {
if err := writeSuspicious(f, withdrawal, wd, finalizationTrace, i, "cannot find callframe"); err != nil {
return err
}
continue
}
traceJson, err := json.MarshalIndent(callFrame, "", " ")
if err != nil {
return err
}
log.Debug(fmt.Sprintf("%v", string(traceJson)))
abi, err := bindings.L1StandardBridgeMetaData.GetAbi()
if err != nil {
return err
}
calldata := hexutil.MustDecode(callFrame.Input)
// this must be the L1 standard bridge
method, err := abi.MethodById(calldata)
// Handle L1StandardBridge specific logic
if err == nil {
args, err := method.Inputs.Unpack(calldata[4:])
if err != nil {
return err
}
log.Info("decoded calldata", "name", method.Name)
switch method.Name {
case "finalizeERC20Withdrawal":
if err := handleFinalizeERC20Withdrawal(args, receipt, l1StandardBridgeAddress); err != nil {
return err
}
case "finalizeETHWithdrawal":
if err := handleFinalizeETHWithdrawal(args); err != nil {
return err
}
default:
log.Info("Unhandled method", "name", method.Name)
}
}
// Ensure that the target's balance was increasedData correctly
wdValue, err := wd.Value()
if err != nil {
return err
}
if method != nil {
log.Info("withdrawal action", "function", method.Name, "value", wdValue)
} else {
log.Info("unknown method", "to", wd.Target, "data", hexutil.Encode(wd.Data))
if err := writeSuspicious(f, withdrawal, wd, finalizationTrace, i, "unknown method"); err != nil {
return err
}
}
// check that the user's intents are actually executed
if common.HexToAddress(callFrame.To) != *wd.Target {
log.Info("target mismatch", "index", i)
if err := writeSuspicious(f, withdrawal, wd, finalizationTrace, i, "target mismatch"); err != nil {
return err
}
continue
}
if !bytes.Equal(hexutil.MustDecode(callFrame.Input), wd.Data) {
log.Info("calldata mismatch", "index", i)
if err := writeSuspicious(f, withdrawal, wd, finalizationTrace, i, "calldata mismatch"); err != nil {
return err
}
continue
}
if callFrame.BigValue().Cmp(wdValue) != 0 {
log.Info("value mismatch", "index", i)
if err := writeSuspicious(f, withdrawal, wd, finalizationTrace, i, "value mismatch"); err != nil {
return err
}
continue
}
// Get the ETH balance of the withdrawal target *after* the finalization
targetBalAfter, err := clients.L1Client.BalanceAt(context.Background(), *wd.Target, nil)
if err != nil {
return err
}
diff := new(big.Int).Sub(targetBalAfter, targetBalBefore)
log.Debug("balances", "before", targetBalBefore, "after", targetBalAfter, "diff", diff)
isSuccessNewPost, err = contracts.L1CrossDomainMessenger.SuccessfulMessages(&bind.CallOpts{}, xdmHash)
if err != nil {
return err
}
if diff.Cmp(wdValue) != 0 && isSuccessNewPost && isSuccess {
log.Info("native eth balance diff mismatch", "index", i, "diff", diff, "val", wdValue)
if err := writeSuspicious(f, withdrawal, wd, finalizationTrace, i, "balance mismatch"); err != nil {
return err
}
continue
}
} else {
log.Info("Already finalized")
}
}
return nil return nil
}, },
} }
...@@ -99,14 +434,525 @@ func main() { ...@@ -99,14 +434,525 @@ func main() {
} }
} }
func writeJSONFile(outfile string, input interface{}) error { // callTrace will call `debug_traceTransaction` on a remote node
f, err := os.OpenFile(outfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) func callTrace(c *clients, receipt *types.Receipt) (callFrame, error) {
var finalizationTrace callFrame
tracer := "callTracer"
traceConfig := tracers.TraceConfig{
Tracer: &tracer,
}
err := c.L1RpcClient.Call(&finalizationTrace, "debug_traceTransaction", receipt.TxHash, traceConfig)
if err != nil {
return finalizationTrace, err
}
return finalizationTrace, err
}
// handleFinalizeETHWithdrawal will ensure that the calldata is correct
func handleFinalizeETHWithdrawal(args []any) error {
from, ok := args[0].(common.Address)
if !ok {
return fmt.Errorf("invalid type: from")
}
to, ok := args[1].(common.Address)
if !ok {
return fmt.Errorf("invalid type: to")
}
amount, ok := args[2].(*big.Int)
if !ok {
return fmt.Errorf("invalid type: amount")
}
extraData, ok := args[3].([]byte)
if !ok {
return fmt.Errorf("invalid type: extraData")
}
log.Info(
"decoded calldata",
"from", from,
"to", to,
"amount", amount,
"extraData", extraData,
)
return nil
}
// handleFinalizeERC20Withdrawal will look at the receipt logs and make
// assertions that the values are correct
func handleFinalizeERC20Withdrawal(args []any, receipt *types.Receipt, l1StandardBridgeAddress common.Address) error {
erc20Abi, err := bindings.ERC20MetaData.GetAbi()
if err != nil {
return err
}
transferEvent := erc20Abi.Events["Transfer"]
// Handle logic for ERC20 withdrawals
l1Token, ok := args[0].(common.Address)
if !ok {
return fmt.Errorf("invalid abi")
}
l2Token, ok := args[1].(common.Address)
if !ok {
return fmt.Errorf("invalid abi")
}
from, ok := args[2].(common.Address)
if !ok {
return fmt.Errorf("invalid abi")
}
to, ok := args[3].(common.Address)
if !ok {
return fmt.Errorf("invalid abi")
}
amount, ok := args[4].(*big.Int)
if !ok {
return fmt.Errorf("invalid abi")
}
extraData, ok := args[5].([]byte)
if !ok {
return fmt.Errorf("invalid abi")
}
log.Info(
"decoded calldata",
"l1Token", l1Token,
"l2Token", l2Token,
"from", from,
"to", to,
"amount", amount,
"extraData", extraData,
)
// Look for the ERC20 token transfer topic
for _, l := range receipt.Logs {
topic := l.Topics[0]
if topic == transferEvent.ID {
if l.Address == l1Token {
a, _ := transferEvent.Inputs.Unpack(l.Data)
if len(l.Topics) < 3 {
return fmt.Errorf("")
}
_from := common.BytesToAddress(l.Topics[1].Bytes())
_to := common.BytesToAddress(l.Topics[2].Bytes())
// from the L1StandardBridge
if _from != l1StandardBridgeAddress {
return fmt.Errorf("from mismatch: %x - %x", _from, l1StandardBridgeAddress)
}
if to != _to {
return fmt.Errorf("to mismatch: %x - %x", to, _to)
}
_amount, ok := a[0].(*big.Int)
if !ok {
return fmt.Errorf("invalid abi in transfer event")
}
if amount.Cmp(_amount) != 0 {
return fmt.Errorf("amount mismatch: %d - %d", amount, _amount)
}
}
}
}
return nil
}
// proveWithdrawalTransaction will build the data required for proving a
// withdrawal and then send the transaction and make sure that it is included
// and successful and then wait for the finalization period to elapse.
func proveWithdrawalTransaction(c *contracts, cl *clients, opts *bind.TransactOpts, withdrawal *crossdomain.Withdrawal, bn, finalizationPeriod *big.Int) error {
l2OutputIndex, outputRootProof, trieNodes, err := createOutput(withdrawal, c.L2OutputOracle, bn, cl)
if err != nil {
return err
}
hash, err := withdrawal.Hash()
if err != nil {
return err
}
wdTx := withdrawal.WithdrawalTransaction()
tx, err := c.OptimismPortal.ProveWithdrawalTransaction(
opts,
wdTx,
l2OutputIndex,
outputRootProof,
trieNodes,
)
if err != nil {
return err
}
receipt, err := bind.WaitMined(context.Background(), cl.L1Client, tx)
if err != nil {
return err
}
if receipt.Status != types.ReceiptStatusSuccessful {
return errors.New("withdrawal proof unsuccessful")
}
log.Info("withdrawal proved", "tx-hash", tx.Hash(), "withdrawal-hash", hash)
block, err := cl.L1Client.BlockByHash(context.Background(), receipt.BlockHash)
if err != nil { if err != nil {
return err return err
} }
defer f.Close() initialTime := block.Time()
for {
log.Info("waiting for finalization")
if block.Time() >= initialTime+finalizationPeriod.Uint64() {
log.Info("can be finalized")
break
}
time.Sleep(1 * time.Second)
block, err = cl.L1Client.BlockByNumber(context.Background(), nil)
if err != nil {
return err
}
}
return nil
}
func finalizeWithdrawalTransaction(
c *contracts,
cl *clients,
opts *bind.TransactOpts,
wd *crossdomain.LegacyWithdrawal,
withdrawal *crossdomain.Withdrawal,
) (*types.Receipt, error) {
if wd.Target == nil {
return nil, errors.New("withdrawal target is nil, should never happen")
}
wdTx := withdrawal.WithdrawalTransaction()
// Finalize withdrawal
tx, err := c.OptimismPortal.FinalizeWithdrawalTransaction(
opts,
wdTx,
)
if err != nil {
return nil, err
}
receipt, err := bind.WaitMined(context.Background(), cl.L1Client, tx)
if err != nil {
return nil, err
}
if receipt.Status != types.ReceiptStatusSuccessful {
return nil, errors.New("withdrawal finalize unsuccessful")
}
return receipt, nil
}
// contracts represents a set of bound contracts
type contracts struct {
OptimismPortal *bindings.OptimismPortal
L1CrossDomainMessenger *bindings.L1CrossDomainMessenger
L2OutputOracle *bindings.L2OutputOracle
}
// newContracts will create a contracts struct with the contract bindings
// preconfigured
func newContracts(ctx *cli.Context, l1Backend, l2Backend bind.ContractBackend) (*contracts, error) {
optimismPortalAddress := ctx.String("optimism-portal-address")
if len(optimismPortalAddress) == 0 {
return nil, errors.New("OptimismPortal address not configured")
}
optimismPortalAddr := common.HexToAddress(optimismPortalAddress)
portal, err := bindings.NewOptimismPortal(optimismPortalAddr, l1Backend)
if err != nil {
return nil, err
}
l1xdmAddress := ctx.String("l1-crossdomain-messenger-address")
if l1xdmAddress == "" {
return nil, errors.New("L1CrossDomainMessenger address not configured")
}
l1xdmAddr := common.HexToAddress(l1xdmAddress)
l1CrossDomainMessenger, err := bindings.NewL1CrossDomainMessenger(l1xdmAddr, l1Backend)
if err != nil {
return nil, err
}
l2OracleAddr, err := portal.L2ORACLE(&bind.CallOpts{})
if err != nil {
return nil, err
}
oracle, err := bindings.NewL2OutputOracle(l2OracleAddr, l1Backend)
if err != nil {
return nil, err
}
log.Info(
"Addresses",
"l1-crossdomain-messenger", l1xdmAddr,
"optimism-portal", optimismPortalAddr,
"l2-output-oracle", l2OracleAddr,
)
return &contracts{
OptimismPortal: portal,
L1CrossDomainMessenger: l1CrossDomainMessenger,
L2OutputOracle: oracle,
}, nil
}
// clients represents a set of initialized RPC clients
type clients struct {
L1Client *ethclient.Client
L2Client *ethclient.Client
L1RpcClient *rpc.Client
L2RpcClient *rpc.Client
L1GethClient *gethclient.Client
L2GethClient *gethclient.Client
}
// newClients will create new RPC clients
func newClients(ctx *cli.Context) (*clients, error) {
l1RpcURL := ctx.String("l1-rpc-url")
l1Client, err := ethclient.Dial(l1RpcURL)
if err != nil {
return nil, err
}
l1ChainID, err := l1Client.ChainID(context.Background())
if err != nil {
return nil, err
}
l2RpcURL := ctx.String("l2-rpc-url")
l2Client, err := ethclient.Dial(l2RpcURL)
if err != nil {
return nil, err
}
l2ChainID, err := l2Client.ChainID(context.Background())
if err != nil {
return nil, err
}
l1RpcClient, err := rpc.DialContext(context.Background(), l1RpcURL)
if err != nil {
return nil, err
}
l2RpcClient, err := rpc.DialContext(context.Background(), l2RpcURL)
if err != nil {
return nil, err
}
l1GethClient := gethclient.New(l1RpcClient)
l2GethClient := gethclient.New(l2RpcClient)
log.Info(
"Set up RPC clients",
"l1-chain-id", l1ChainID,
"l2-chain-id", l2ChainID,
)
return &clients{
L1Client: l1Client,
L2Client: l2Client,
L1RpcClient: l1RpcClient,
L2RpcClient: l2RpcClient,
L1GethClient: l1GethClient,
L2GethClient: l2GethClient,
}, nil
}
// newWithdrawals will create a set of legacy withdrawals
func newWithdrawals(ctx *cli.Context, l1ChainID *big.Int) ([]*crossdomain.LegacyWithdrawal, error) {
ovmMsgs := ctx.String("ovm-messages")
evmMsgs := ctx.String("evm-messages")
log.Debug("Migration data", "ovm-path", ovmMsgs, "evm-messages", evmMsgs)
ovmMessages, err := migration.NewSentMessage(ovmMsgs)
if err != nil {
return nil, err
}
// use empty ovmMessages if its not mainnet. The mainnet messages are
// committed to in git.
if l1ChainID.Cmp(common.Big1) != 0 {
log.Info("not using ovm messages because its not mainnet")
ovmMessages = []*migration.SentMessage{}
}
evmMessages, err := migration.NewSentMessage(evmMsgs)
if err != nil {
return nil, err
}
migrationData := migration.MigrationData{
OvmMessages: ovmMessages,
EvmMessages: evmMessages,
}
wds, err := migrationData.ToWithdrawals()
if err != nil {
return nil, err
}
if len(wds) == 0 {
return nil, errors.New("no withdrawals")
}
log.Info("Converted migration data to withdrawals successfully", "count", len(wds))
return wds, nil
}
// newTransactor creates a new transact context given a cli context
func newTransactor(ctx *cli.Context) (*bind.TransactOpts, error) {
if ctx.String("private-key") == "" {
return nil, errors.New("No private key to transact with")
}
privateKey, err := crypto.HexToECDSA(strings.TrimPrefix(ctx.String("private-key"), "0x"))
if err != nil {
return nil, err
}
l1RpcURL := ctx.String("l1-rpc-url")
l1Client, err := ethclient.Dial(l1RpcURL)
if err != nil {
return nil, err
}
l1ChainID, err := l1Client.ChainID(context.Background())
if err != nil {
return nil, err
}
opts, err := bind.NewKeyedTransactorWithChainID(privateKey, l1ChainID)
if err != nil {
return nil, err
}
return opts, nil
}
// findWithdrawalCall will find the call frame for the call that
// represents the user's intent.
func findWithdrawalCall(trace *callFrame, wd *crossdomain.LegacyWithdrawal, l1xdm common.Address) *callFrame {
isCall := trace.Type == "CALL"
isTarget := common.HexToAddress(trace.To) == *wd.Target
isFrom := common.HexToAddress(trace.From) == l1xdm
if isCall && isTarget && isFrom {
return trace
}
for _, subcall := range trace.Calls {
if call := findWithdrawalCall(&subcall, wd, l1xdm); call != nil {
return call
}
}
return nil
}
enc := json.NewEncoder(f) // createOutput will create the data required to send a withdrawal transaction.
enc.SetIndent("", " ") func createOutput(
return enc.Encode(input) withdrawal *crossdomain.Withdrawal,
oracle *bindings.L2OutputOracle,
blockNumber *big.Int,
clients *clients,
) (*big.Int, bindings.TypesOutputRootProof, [][]byte, error) {
// compute the storage slot that the withdrawal is stored in
slot, err := withdrawal.StorageSlot()
if err != nil {
return nil, bindings.TypesOutputRootProof{}, nil, err
}
// find the output index that the withdrawal was committed to in
l2OutputIndex, err := oracle.GetL2OutputIndexAfter(&bind.CallOpts{}, blockNumber)
if err != nil {
return nil, bindings.TypesOutputRootProof{}, nil, err
}
// fetch the output the commits to the withdrawal using the index
l2Output, err := oracle.GetL2Output(&bind.CallOpts{}, l2OutputIndex)
if err != nil {
return nil, bindings.TypesOutputRootProof{}, nil, err
}
log.Debug(
"L2 output",
"index", l2OutputIndex,
"root", common.Bytes2Hex(l2Output.OutputRoot[:]),
"l2-blocknumber", l2Output.L2BlockNumber,
"timestamp", l2Output.Timestamp,
)
// get the block header committed to in the output
header, err := clients.L2Client.HeaderByNumber(context.Background(), l2Output.L2BlockNumber)
if err != nil {
return nil, bindings.TypesOutputRootProof{}, nil, err
}
// get the storage proof for the withdrawal's storage slot
proof, err := clients.L2GethClient.GetProof(context.Background(), predeploys.L2ToL1MessagePasserAddr, []string{slot.String()}, blockNumber)
if err != nil {
return nil, bindings.TypesOutputRootProof{}, nil, err
}
if count := len(proof.StorageProof); count != 1 {
return nil, bindings.TypesOutputRootProof{}, nil, fmt.Errorf("invalid amount of storage proofs: %d", count)
}
trieNodes := make([][]byte, len(proof.StorageProof[0].Proof))
for i, s := range proof.StorageProof[0].Proof {
trieNodes[i] = common.FromHex(s)
}
// create an output root proof
outputRootProof := bindings.TypesOutputRootProof{
Version: [32]byte{},
StateRoot: header.Root,
MessagePasserStorageRoot: proof.StorageHash,
LatestBlockhash: header.Hash(),
}
// TODO(mark): import the function from `op-node` to compute the hash
// instead of doing this. Will update when testing against mainnet.
localOutputRootHash := crypto.Keccak256Hash(
outputRootProof.Version[:],
outputRootProof.StateRoot[:],
outputRootProof.MessagePasserStorageRoot[:],
outputRootProof.LatestBlockhash[:],
)
// ensure that the locally computed hash matches
if l2Output.OutputRoot != localOutputRootHash {
return nil, bindings.TypesOutputRootProof{}, nil, fmt.Errorf("mismatch in output root hashes, got 0x%x expected 0x%x", localOutputRootHash, l2Output.OutputRoot)
}
log.Info(
"output root proof",
"version", common.Hash(outputRootProof.Version),
"state-root", common.Hash(outputRootProof.StateRoot),
"storage-root", common.Hash(outputRootProof.MessagePasserStorageRoot),
"block-hash", common.Hash(outputRootProof.LatestBlockhash),
"trie-node-count", len(trieNodes),
)
return l2OutputIndex, outputRootProof, trieNodes, nil
}
// writeSuspicious will create a suspiciousWithdrawal and then append it to a
// JSONL file. Each line is its own JSON where there is a newline separating them.
func writeSuspicious(
f *os.File,
withdrawal *crossdomain.Withdrawal,
wd *crossdomain.LegacyWithdrawal,
finalizationTrace callFrame,
i int,
reason string,
) error {
bad := suspiciousWithdrawal{
Withdrawal: withdrawal,
Legacy: wd,
Trace: finalizationTrace,
Index: i,
Reason: reason,
}
data, err := json.Marshal(bad)
if err != nil {
return err
}
_, err = f.WriteString(string(data) + "\n")
return err
} }
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
) )
...@@ -17,7 +18,7 @@ import ( ...@@ -17,7 +18,7 @@ import (
type LegacyWithdrawal struct { type LegacyWithdrawal struct {
Target *common.Address `json:"target"` Target *common.Address `json:"target"`
Sender *common.Address `json:"sender"` Sender *common.Address `json:"sender"`
Data []byte `json:"data"` Data hexutil.Bytes `json:"data"`
Nonce *big.Int `json:"nonce"` Nonce *big.Int `json:"nonce"`
} }
...@@ -38,7 +39,7 @@ func NewLegacyWithdrawal(target, sender *common.Address, data []byte, nonce *big ...@@ -38,7 +39,7 @@ func NewLegacyWithdrawal(target, sender *common.Address, data []byte, nonce *big
// through the standard optimism cross domain messaging system by hashing in // through the standard optimism cross domain messaging system by hashing in
// the L2CrossDomainMessenger address. // the L2CrossDomainMessenger address.
func (w *LegacyWithdrawal) Encode() ([]byte, error) { func (w *LegacyWithdrawal) Encode() ([]byte, error) {
enc, err := EncodeCrossDomainMessageV0(w.Target, w.Sender, w.Data, w.Nonce) enc, err := EncodeCrossDomainMessageV0(w.Target, w.Sender, []byte(w.Data), w.Nonce)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot encode LegacyWithdrawal: %w", err) return nil, fmt.Errorf("cannot encode LegacyWithdrawal: %w", err)
} }
...@@ -98,7 +99,7 @@ func (w *LegacyWithdrawal) Decode(data []byte) error { ...@@ -98,7 +99,7 @@ func (w *LegacyWithdrawal) Decode(data []byte) error {
w.Target = &target w.Target = &target
w.Sender = &sender w.Sender = &sender
w.Data = msgData w.Data = hexutil.Bytes(msgData)
w.Nonce = nonce w.Nonce = nonce
return nil return nil
} }
......
...@@ -72,6 +72,12 @@ func (c *CrossDomainMessage) Hash() (common.Hash, error) { ...@@ -72,6 +72,12 @@ func (c *CrossDomainMessage) Hash() (common.Hash, error) {
} }
} }
// HashV1 forces using the V1 hash even if its a legacy hash. This is used
// for the migration process.
func (c *CrossDomainMessage) HashV1() (common.Hash, error) {
return HashCrossDomainMessageV1(c.Nonce, c.Sender, c.Target, c.Value, c.GasLimit, c.Data)
}
// ToWithdrawal will turn a CrossDomainMessage into a Withdrawal. // ToWithdrawal will turn a CrossDomainMessage into a Withdrawal.
// This only works for version 0 CrossDomainMessages as not all of // This only works for version 0 CrossDomainMessages as not all of
// the data is present for version 1 CrossDomainMessages to be turned // the data is present for version 1 CrossDomainMessages to be turned
......
...@@ -273,6 +273,6 @@ func TestGetPendingWithdrawals(t *testing.T) { ...@@ -273,6 +273,6 @@ func TestGetPendingWithdrawals(t *testing.T) {
withdrawal := withdrawals[i] withdrawal := withdrawals[i]
require.Equal(t, msg.Target, *withdrawal.Target) require.Equal(t, msg.Target, *withdrawal.Target)
require.Equal(t, msg.Message, withdrawal.Data) require.Equal(t, msg.Message, []byte(withdrawal.Data))
} }
} }
# `eof-crawler`
Simple CLI tool to scan all accounts in a geth LevelDB for contracts that begin with the EOF prefix.
## Usage
1. Pass the directory of the Geth DB into the tool
```sh
go run ./cmd/eof-crawler/main.go --db-path <db_path> [--out <out_file>]
```
2. Once the indexing has completed, an array of all EOF-prefixed contracts will be written to `eof_contracts.json` or the designated output file.
package eof
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"log"
"os"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
// Account represents an account in the state.
type Account struct {
Balance string `json:"balance"`
Nonce uint64 `json:"nonce"`
Root hexutil.Bytes `json:"root"`
CodeHash hexutil.Bytes `json:"codeHash"`
Code hexutil.Bytes `json:"code,omitempty"`
Address common.Address `json:"address,omitempty"`
SecureKey hexutil.Bytes `json:"key,omitempty"`
}
// emptyCodeHash is the known hash of an account with no code.
var emptyCodeHash = crypto.Keccak256(nil)
// IndexEOFContracts indexes all the EOF contracts in the state trie of the head block
// for the given db and writes them to a JSON file.
func IndexEOFContracts(dbPath string, out string) error {
// Open an existing Ethereum database
db, err := rawdb.NewLevelDBDatabase(dbPath, 16, 16, "", true)
if err != nil {
return fmt.Errorf("Failed to open database: %w", err)
}
stateDB := state.NewDatabase(db)
// Retrieve the head block
hash := rawdb.ReadHeadBlockHash(db)
number := rawdb.ReadHeaderNumber(db, hash)
if number == nil {
return errors.New("Failed to retrieve head block number")
}
head := rawdb.ReadBlock(db, hash, *number)
if head == nil {
return errors.New("Failed to retrieve head block")
}
// Retrieve the state belonging to the head block
st, err := trie.New(trie.StateTrieID(head.Root()), trie.NewDatabase(db))
if err != nil {
return fmt.Errorf("Failed to retrieve state trie: %w", err)
}
log.Printf("Indexing state trie at head block #%d [0x%x]", *number, hash)
// Iterate over the entire account trie to search for EOF-prefixed contracts
start := time.Now()
missingPreimages := uint64(0)
eoas := uint64(0)
nonEofContracts := uint64(0)
eofContracts := make([]Account, 0)
it := trie.NewIterator(st.NodeIterator(nil))
for it.Next() {
// Decode the state account
var data types.StateAccount
err := rlp.DecodeBytes(it.Value, &data)
if err != nil {
return fmt.Errorf("Failed to decode state account: %w", err)
}
// Check to see if the account has any code associated with it before performing
// more reads from the trie & db.
if bytes.Equal(data.CodeHash, emptyCodeHash) {
eoas++
continue
}
// Create a serializable `Account` object
account := Account{
Balance: data.Balance.String(),
Nonce: data.Nonce,
Root: data.Root[:],
CodeHash: data.CodeHash,
SecureKey: it.Key,
}
// Attempt to get the address of the account from the trie
addrBytes := st.Get(it.Key)
if addrBytes == nil {
// Preimage missing! Cannot continue.
missingPreimages++
continue
}
addr := common.BytesToAddress(addrBytes)
// Attempt to get the code of the account from the trie
code, err := stateDB.ContractCode(crypto.Keccak256Hash(addrBytes), common.BytesToHash(data.CodeHash))
if err != nil {
return fmt.Errorf("Could not load code for account %x: %w", addr, err)
}
// Check if the contract's runtime bytecode starts with the EOF prefix.
if len(code) >= 1 && code[0] == 0xEF {
// Append the account to the list of EOF contracts
account.Address = addr
account.Code = code
eofContracts = append(eofContracts, account)
} else {
nonEofContracts++
}
}
// Print finishing status
log.Printf("Indexing done in %v, found %d EOF contracts", time.Since(start), len(eofContracts))
log.Printf("Num missing preimages: %d", missingPreimages)
log.Printf("Non-EOF-prefixed contracts: %d", nonEofContracts)
log.Printf("Accounts with no code (EOAs): %d", eoas)
// Write the EOF contracts to a file
file, err := json.MarshalIndent(eofContracts, "", " ")
if err != nil {
return fmt.Errorf("Cannot marshal EOF contracts: %w", err)
}
err = os.WriteFile(out, file, 0644)
if err != nil {
return fmt.Errorf("Failed to write EOF contracts array to file: %w", err)
}
log.Printf("Wrote list of EOF contracts to `%v`", out)
return nil
}
...@@ -3,7 +3,7 @@ module github.com/ethereum-optimism/optimism/op-chain-ops ...@@ -3,7 +3,7 @@ module github.com/ethereum-optimism/optimism/op-chain-ops
go 1.18 go 1.18
require ( require (
github.com/ethereum-optimism/optimism/op-bindings v0.10.11 github.com/ethereum-optimism/optimism/op-bindings v0.10.12
github.com/ethereum-optimism/optimism/op-node v0.10.1 github.com/ethereum-optimism/optimism/op-node v0.10.1
github.com/ethereum/go-ethereum v1.10.26 github.com/ethereum/go-ethereum v1.10.26
github.com/holiman/uint256 v1.2.0 github.com/holiman/uint256 v1.2.0
...@@ -23,7 +23,8 @@ require ( ...@@ -23,7 +23,8 @@ require (
github.com/deckarep/golang-set v1.8.0 // indirect github.com/deckarep/golang-set v1.8.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/ethereum-optimism/optimism/op-service v0.10.11 // indirect github.com/ethereum-optimism/optimism/op-service v0.10.12 // indirect
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect
github.com/go-kit/kit v0.10.0 // indirect github.com/go-kit/kit v0.10.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect github.com/go-stack/stack v1.8.1 // indirect
...@@ -35,6 +36,8 @@ require ( ...@@ -35,6 +36,8 @@ require (
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e // indirect github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e // indirect
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
github.com/huin/goupnp v1.0.3 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/kr/pretty v0.3.0 // indirect github.com/kr/pretty v0.3.0 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect
...@@ -46,12 +49,16 @@ require ( ...@@ -46,12 +49,16 @@ require (
github.com/rogpeppe/go-internal v1.8.1 // indirect github.com/rogpeppe/go-internal v1.8.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.5.0 // indirect github.com/tklauser/numcpus v0.5.0 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
......
...@@ -79,21 +79,22 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m ...@@ -79,21 +79,22 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY=
github.com/ethereum-optimism/optimism/op-bindings v0.10.11 h1:RDiRyHo0G/UuxHZQdMJyqIuHtWvpionuFNfczNaWCcM= github.com/ethereum-optimism/optimism/op-bindings v0.10.12 h1:/B1gaCLwZYy9Rja3MiceV3R642bygp937kZjnYwRxA0=
github.com/ethereum-optimism/optimism/op-bindings v0.10.11/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c= github.com/ethereum-optimism/optimism/op-bindings v0.10.12/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c=
github.com/ethereum-optimism/optimism/op-node v0.10.1 h1:kVBaOEOYLV22XEHRhB7dfdmoXepO0kx/RsZQK+Bpk1Y= github.com/ethereum-optimism/optimism/op-node v0.10.1 h1:kVBaOEOYLV22XEHRhB7dfdmoXepO0kx/RsZQK+Bpk1Y=
github.com/ethereum-optimism/optimism/op-node v0.10.1/go.mod h1:pup7wiiUs9g8cZKwXeB5tEGCqwUUwFVmej9MmSIm6S8= github.com/ethereum-optimism/optimism/op-node v0.10.1/go.mod h1:pup7wiiUs9g8cZKwXeB5tEGCqwUUwFVmej9MmSIm6S8=
github.com/ethereum-optimism/optimism/op-service v0.10.11 h1:o+SazhFXlE3EM9Re5KIPEQklZ9uTI8rNkjl0h5OwRtU= github.com/ethereum-optimism/optimism/op-service v0.10.12 h1:Y7pR3/b8eeHYkmo2V5z7sj8jaraYqm2Azyph5lbiIxo=
github.com/ethereum-optimism/optimism/op-service v0.10.11/go.mod h1:wbtHqi1fv00B3agj7a2zdP3OFanEfGZ23zPgGgFCF/c= github.com/ethereum-optimism/optimism/op-service v0.10.12/go.mod h1:Ibbun+aic0rjQBV8yBf9kohqIj6mQ8nSTWbZjHv+Q7Q=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ= github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
...@@ -196,11 +197,14 @@ github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25 ...@@ -196,11 +197,14 @@ github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y=
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/ipfs/go-cid v0.3.2 h1:OGgOd+JCFM+y1DjWPmVH+2/4POtpDzwcr7VgnB7mZXc= github.com/ipfs/go-cid v0.3.2 h1:OGgOd+JCFM+y1DjWPmVH+2/4POtpDzwcr7VgnB7mZXc=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
...@@ -373,7 +377,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO ...@@ -373,7 +377,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d h1:vmirMegf1vqPJ+lDBxLQ0MAt3tz+JL57UPxu44JBOjA=
github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d/go.mod h1:97vT0Rym0wCnK4B++hNA3nCetr0Mh1KXaVxzSt1arjg=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
...@@ -400,6 +405,7 @@ github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+ ...@@ -400,6 +405,7 @@ github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+
github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo= github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw= github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw=
...@@ -473,7 +479,9 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ ...@@ -473,7 +479,9 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
......
...@@ -54,11 +54,11 @@ type Writer interface { ...@@ -54,11 +54,11 @@ type Writer interface {
type ChannelOutIface interface { type ChannelOutIface interface {
ID() derive.ChannelID ID() derive.ChannelID
Reset() error Reset() error
AddBlock(block *types.Block) error AddBlock(block *types.Block) (uint64, error)
ReadyBytes() int ReadyBytes() int
Flush() error Flush() error
Close() error Close() error
OutputFrame(w *bytes.Buffer, maxSize uint64) error OutputFrame(w *bytes.Buffer, maxSize uint64) (uint16, error)
} }
// Compile-time check for ChannelOutIface interface implementation for the ChannelOut type. // Compile-time check for ChannelOutIface interface implementation for the ChannelOut type.
...@@ -135,19 +135,19 @@ func (co *GarbageChannelOut) Reset() error { ...@@ -135,19 +135,19 @@ func (co *GarbageChannelOut) Reset() error {
// error that it returns is ErrTooManyRLPBytes. If this error // error that it returns is ErrTooManyRLPBytes. If this error
// is returned, the channel should be closed and a new one // is returned, the channel should be closed and a new one
// should be made. // should be made.
func (co *GarbageChannelOut) AddBlock(block *types.Block) error { func (co *GarbageChannelOut) AddBlock(block *types.Block) (uint64, error) {
if co.closed { if co.closed {
return errors.New("already closed") return 0, errors.New("already closed")
} }
batch, err := blockToBatch(block) batch, err := blockToBatch(block)
if err != nil { if err != nil {
return err return 0, err
} }
// We encode to a temporary buffer to determine the encoded length to // We encode to a temporary buffer to determine the encoded length to
// ensure that the total size of all RLP elements is less than or equal to MAX_RLP_BYTES_PER_CHANNEL // ensure that the total size of all RLP elements is less than or equal to MAX_RLP_BYTES_PER_CHANNEL
var buf bytes.Buffer var buf bytes.Buffer
if err := rlp.Encode(&buf, batch); err != nil { if err := rlp.Encode(&buf, batch); err != nil {
return err return 0, err
} }
if co.cfg.malformRLP { if co.cfg.malformRLP {
// Malform the RLP by incrementing the length prefix by 1. // Malform the RLP by incrementing the length prefix by 1.
...@@ -157,13 +157,13 @@ func (co *GarbageChannelOut) AddBlock(block *types.Block) error { ...@@ -157,13 +157,13 @@ func (co *GarbageChannelOut) AddBlock(block *types.Block) error {
buf.Write(bufBytes) buf.Write(bufBytes)
} }
if co.rlpLength+buf.Len() > derive.MaxRLPBytesPerChannel { if co.rlpLength+buf.Len() > derive.MaxRLPBytesPerChannel {
return fmt.Errorf("could not add %d bytes to channel of %d bytes, max is %d. err: %w", return 0, fmt.Errorf("could not add %d bytes to channel of %d bytes, max is %d. err: %w",
buf.Len(), co.rlpLength, derive.MaxRLPBytesPerChannel, derive.ErrTooManyRLPBytes) buf.Len(), co.rlpLength, derive.MaxRLPBytesPerChannel, derive.ErrTooManyRLPBytes)
} }
co.rlpLength += buf.Len() co.rlpLength += buf.Len()
_, err = io.Copy(co.compress, &buf) written, err := io.Copy(co.compress, &buf)
return err return uint64(written), err
} }
// ReadyBytes returns the number of bytes that the channel out can immediately output into a frame. // ReadyBytes returns the number of bytes that the channel out can immediately output into a frame.
...@@ -192,11 +192,12 @@ func (co *GarbageChannelOut) Close() error { ...@@ -192,11 +192,12 @@ func (co *GarbageChannelOut) Close() error {
// Returns io.EOF when the channel is closed & there are no more frames // Returns io.EOF when the channel is closed & there are no more frames
// Returns nil if there is still more buffered data. // Returns nil if there is still more buffered data.
// Returns and error if it ran into an error during processing. // Returns and error if it ran into an error during processing.
func (co *GarbageChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) error { func (co *GarbageChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) (uint16, error) {
f := derive.Frame{ f := derive.Frame{
ID: co.id, ID: co.id,
FrameNumber: uint16(co.frame), FrameNumber: uint16(co.frame),
} }
fn := f.FrameNumber
// Copy data from the local buffer into the frame data buffer // Copy data from the local buffer into the frame data buffer
// Don't go past the maxSize with the fixed frame overhead. // Don't go past the maxSize with the fixed frame overhead.
...@@ -214,18 +215,18 @@ func (co *GarbageChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) error ...@@ -214,18 +215,18 @@ func (co *GarbageChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) error
f.Data = make([]byte, maxDataSize) f.Data = make([]byte, maxDataSize)
if _, err := io.ReadFull(&co.buf, f.Data); err != nil { if _, err := io.ReadFull(&co.buf, f.Data); err != nil {
return err return fn, err
} }
if err := f.MarshalBinary(w); err != nil { if err := f.MarshalBinary(w); err != nil {
return err return fn, err
} }
co.frame += 1 co.frame += 1
if f.IsLast { if f.IsLast {
return io.EOF return fn, io.EOF
} else { } else {
return nil return fn, nil
} }
} }
......
...@@ -142,7 +142,7 @@ func (s *L2Batcher) ActL2BatchBuffer(t Testing) { ...@@ -142,7 +142,7 @@ func (s *L2Batcher) ActL2BatchBuffer(t Testing) {
s.l2BufferedBlock = syncStatus.SafeL2.ID() s.l2BufferedBlock = syncStatus.SafeL2.ID()
s.l2ChannelOut = nil s.l2ChannelOut = nil
} }
if err := s.l2ChannelOut.AddBlock(block); err != nil { // should always succeed if _, err := s.l2ChannelOut.AddBlock(block); err != nil { // should always succeed
t.Fatalf("failed to add block to channel: %v", err) t.Fatalf("failed to add block to channel: %v", err)
} }
s.l2BufferedBlock = eth.ToBlockID(block) s.l2BufferedBlock = eth.ToBlockID(block)
...@@ -168,7 +168,7 @@ func (s *L2Batcher) ActL2BatchSubmit(t Testing) { ...@@ -168,7 +168,7 @@ func (s *L2Batcher) ActL2BatchSubmit(t Testing) {
data := new(bytes.Buffer) data := new(bytes.Buffer)
data.WriteByte(derive.DerivationVersion0) data.WriteByte(derive.DerivationVersion0)
// subtract one, to account for the version byte // subtract one, to account for the version byte
if err := s.l2ChannelOut.OutputFrame(data, s.l2BatcherCfg.MaxL1TxSize-1); err == io.EOF { if _, err := s.l2ChannelOut.OutputFrame(data, s.l2BatcherCfg.MaxL1TxSize-1); err == io.EOF {
s.l2ChannelOut = nil s.l2ChannelOut = nil
s.l2Submitting = false s.l2Submitting = false
} else if err != nil { } else if err != nil {
...@@ -218,7 +218,7 @@ func (s *L2Batcher) ActL2BatchSubmitGarbage(t Testing, kind GarbageKind) { ...@@ -218,7 +218,7 @@ func (s *L2Batcher) ActL2BatchSubmitGarbage(t Testing, kind GarbageKind) {
data.WriteByte(derive.DerivationVersion0) data.WriteByte(derive.DerivationVersion0)
// subtract one, to account for the version byte // subtract one, to account for the version byte
if err := s.l2ChannelOut.OutputFrame(data, s.l2BatcherCfg.MaxL1TxSize-1); err == io.EOF { if _, err := s.l2ChannelOut.OutputFrame(data, s.l2BatcherCfg.MaxL1TxSize-1); err == io.EOF {
s.l2ChannelOut = nil s.l2ChannelOut = nil
s.l2Submitting = false s.l2Submitting = false
} else if err != nil { } else if err != nil {
......
package actions package actions
import ( import (
"context"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/metrics"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/rollup/driver" "github.com/ethereum-optimism/optimism/op-node/rollup/driver"
) )
// MockL1OriginSelector is a shim to override the origin as sequencer, so we can force it to stay on an older origin.
type MockL1OriginSelector struct {
actual *driver.L1OriginSelector
originOverride eth.L1BlockRef // override which origin gets picked
}
func (m *MockL1OriginSelector) FindL1Origin(ctx context.Context, l2Head eth.L2BlockRef) (eth.L1BlockRef, error) {
if m.originOverride != (eth.L1BlockRef{}) {
return m.originOverride, nil
}
return m.actual.FindL1Origin(ctx, l2Head)
}
// L2Sequencer is an actor that functions like a rollup node, // L2Sequencer is an actor that functions like a rollup node,
// without the full P2P/API/Node stack, but just the derivation state, and simplified driver with sequencing ability. // without the full P2P/API/Node stack, but just the derivation state, and simplified driver with sequencing ability.
type L2Sequencer struct { type L2Sequencer struct {
L2Verifier L2Verifier
sequencer *driver.Sequencer sequencer *driver.Sequencer
l1OriginSelector *driver.L1OriginSelector
seqOldOrigin bool // stay on current L1 origin when sequencing a block, unless forced to adopt the next origin
failL2GossipUnsafeBlock error // mock error failL2GossipUnsafeBlock error // mock error
mockL1OriginSelector *MockL1OriginSelector
} }
func NewL2Sequencer(t Testing, log log.Logger, l1 derive.L1Fetcher, eng L2API, cfg *rollup.Config, seqConfDepth uint64) *L2Sequencer { func NewL2Sequencer(t Testing, log log.Logger, l1 derive.L1Fetcher, eng L2API, cfg *rollup.Config, seqConfDepth uint64) *L2Sequencer {
ver := NewL2Verifier(t, log, l1, eng, cfg) ver := NewL2Verifier(t, log, l1, eng, cfg)
attrBuilder := derive.NewFetchingAttributesBuilder(cfg, l1, eng) attrBuilder := derive.NewFetchingAttributesBuilder(cfg, l1, eng)
seqConfDepthL1 := driver.NewConfDepth(seqConfDepth, ver.l1State.L1Head, l1)
l1OriginSelector := &MockL1OriginSelector{
actual: driver.NewL1OriginSelector(log, cfg, seqConfDepthL1),
}
return &L2Sequencer{ return &L2Sequencer{
L2Verifier: *ver, L2Verifier: *ver,
sequencer: driver.NewSequencer(log, cfg, eng, ver.derivation, attrBuilder, metrics.NoopMetrics), sequencer: driver.NewSequencer(log, cfg, ver.derivation, attrBuilder, l1OriginSelector),
l1OriginSelector: driver.NewL1OriginSelector(log, cfg, l1, seqConfDepth), mockL1OriginSelector: l1OriginSelector,
seqOldOrigin: false,
failL2GossipUnsafeBlock: nil, failL2GossipUnsafeBlock: nil,
} }
} }
...@@ -47,22 +63,7 @@ func (s *L2Sequencer) ActL2StartBlock(t Testing) { ...@@ -47,22 +63,7 @@ func (s *L2Sequencer) ActL2StartBlock(t Testing) {
return return
} }
parent := s.derivation.UnsafeL2Head() err := s.sequencer.StartBuildingBlock(t.Ctx())
var origin eth.L1BlockRef
if s.seqOldOrigin {
// force old origin, for testing purposes
oldOrigin, err := s.l1.L1BlockRefByHash(t.Ctx(), parent.L1Origin.Hash)
require.NoError(t, err, "failed to get current origin: %s", parent.L1Origin)
origin = oldOrigin
s.seqOldOrigin = false // don't repeat this
} else {
// select origin the real way
l1Origin, err := s.l1OriginSelector.FindL1Origin(t.Ctx(), s.l1State.L1Head(), parent)
require.NoError(t, err)
origin = l1Origin
}
err := s.sequencer.StartBuildingBlock(t.Ctx(), origin)
require.NoError(t, err, "failed to start block building") require.NoError(t, err, "failed to start block building")
s.l2Building = true s.l2Building = true
...@@ -76,24 +77,21 @@ func (s *L2Sequencer) ActL2EndBlock(t Testing) { ...@@ -76,24 +77,21 @@ func (s *L2Sequencer) ActL2EndBlock(t Testing) {
} }
s.l2Building = false s.l2Building = false
payload, err := s.sequencer.CompleteBuildingBlock(t.Ctx()) _, err := s.sequencer.CompleteBuildingBlock(t.Ctx())
// TODO: there may be legitimate temporary errors here, if we mock engine API RPC-failure. // TODO: there may be legitimate temporary errors here, if we mock engine API RPC-failure.
// For advanced tests we can catch those and print a warning instead. // For advanced tests we can catch those and print a warning instead.
require.NoError(t, err) require.NoError(t, err)
ref, err := derive.PayloadToBlockRef(payload, &s.rollupCfg.Genesis)
require.NoError(t, err, "payload must convert to block ref")
s.derivation.SetUnsafeHead(ref)
// TODO: action-test publishing of payload on p2p // TODO: action-test publishing of payload on p2p
} }
// ActL2KeepL1Origin makes the sequencer use the current L1 origin, even if the next origin is available. // ActL2KeepL1Origin makes the sequencer use the current L1 origin, even if the next origin is available.
func (s *L2Sequencer) ActL2KeepL1Origin(t Testing) { func (s *L2Sequencer) ActL2KeepL1Origin(t Testing) {
if s.seqOldOrigin { // don't do this twice parent := s.derivation.UnsafeL2Head()
t.InvalidAction("already decided to keep old L1 origin") // force old origin, for testing purposes
return oldOrigin, err := s.l1.L1BlockRefByHash(t.Ctx(), parent.L1Origin.Hash)
} require.NoError(t, err, "failed to get current origin: %s", parent.L1Origin)
s.seqOldOrigin = true s.mockL1OriginSelector.originOverride = oldOrigin
} }
// ActBuildToL1Head builds empty blocks until (incl.) the L1 head becomes the L2 origin // ActBuildToL1Head builds empty blocks until (incl.) the L1 head becomes the L2 origin
...@@ -109,7 +107,7 @@ func (s *L2Sequencer) ActBuildToL1Head(t Testing) { ...@@ -109,7 +107,7 @@ func (s *L2Sequencer) ActBuildToL1Head(t Testing) {
func (s *L2Sequencer) ActBuildToL1HeadExcl(t Testing) { func (s *L2Sequencer) ActBuildToL1HeadExcl(t Testing) {
for { for {
s.ActL2PipelineFull(t) s.ActL2PipelineFull(t)
nextOrigin, err := s.l1OriginSelector.FindL1Origin(t.Ctx(), s.l1State.L1Head(), s.derivation.UnsafeL2Head()) nextOrigin, err := s.mockL1OriginSelector.FindL1Origin(t.Ctx(), s.derivation.UnsafeL2Head())
require.NoError(t, err) require.NoError(t, err)
if nextOrigin.Number >= s.l1State.L1Head().Number { if nextOrigin.Number >= s.l1State.L1Head().Number {
break break
......
...@@ -10,12 +10,12 @@ require ( ...@@ -10,12 +10,12 @@ require (
github.com/docker/docker v20.10.21+incompatible github.com/docker/docker v20.10.21+incompatible
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3
github.com/ethereum-optimism/optimism/op-batcher v0.10.11 github.com/ethereum-optimism/optimism/op-batcher v0.10.12
github.com/ethereum-optimism/optimism/op-bindings v0.10.11 github.com/ethereum-optimism/optimism/op-bindings v0.10.12
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.11 github.com/ethereum-optimism/optimism/op-chain-ops v0.10.12
github.com/ethereum-optimism/optimism/op-node v0.10.11 github.com/ethereum-optimism/optimism/op-node v0.10.12
github.com/ethereum-optimism/optimism/op-proposer v0.10.11 github.com/ethereum-optimism/optimism/op-proposer v0.10.12
github.com/ethereum-optimism/optimism/op-service v0.10.11 github.com/ethereum-optimism/optimism/op-service v0.10.12
github.com/ethereum/go-ethereum v1.10.26 github.com/ethereum/go-ethereum v1.10.26
github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8 github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8
github.com/libp2p/go-libp2p v0.23.3 github.com/libp2p/go-libp2p v0.23.3
......
...@@ -161,18 +161,18 @@ github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z ...@@ -161,18 +161,18 @@ github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY=
github.com/ethereum-optimism/optimism/op-batcher v0.10.11 h1:aqTOE3UnTrX/rngXruT815CyRhcKi9kvZ4xJllW9l6I= github.com/ethereum-optimism/optimism/op-batcher v0.10.12 h1:Vyb2oEujqokXiybaC64l+T5e0NpEMOC6rhXqIFIN8Oo=
github.com/ethereum-optimism/optimism/op-batcher v0.10.11/go.mod h1:HIsxM0YihXGGImsUuPdI0T+L1LuS8UXgZnaZweXUGug= github.com/ethereum-optimism/optimism/op-batcher v0.10.12/go.mod h1:ai4nZjD0aOOHWhLZj7R8KFttG9+EFsdxiELDuqwzrO4=
github.com/ethereum-optimism/optimism/op-bindings v0.10.11 h1:RDiRyHo0G/UuxHZQdMJyqIuHtWvpionuFNfczNaWCcM= github.com/ethereum-optimism/optimism/op-bindings v0.10.12 h1:/B1gaCLwZYy9Rja3MiceV3R642bygp937kZjnYwRxA0=
github.com/ethereum-optimism/optimism/op-bindings v0.10.11/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c= github.com/ethereum-optimism/optimism/op-bindings v0.10.12/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c=
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.11 h1:6ihrVPJYN1HvD4KG0Fk1zIJCM4ZB109kCu9fq81jznQ= github.com/ethereum-optimism/optimism/op-chain-ops v0.10.12 h1:gRX5oIk0hboTOZ65y5MpEdM5+nmaad87QZP1donrHXo=
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.11/go.mod h1:6mub7Tx1cC4gDrfX9o9n+kA4R2qLlYvfWkG8es21EQI= github.com/ethereum-optimism/optimism/op-chain-ops v0.10.12/go.mod h1:x98mGAxXIhyYM4WsnbgziJCAk+qA4JX0wvaE+azBg48=
github.com/ethereum-optimism/optimism/op-node v0.10.11 h1:ED72b68ainzcXr5/cLOYRwv+LdE4hRDnkq3SmNRY1+Q= github.com/ethereum-optimism/optimism/op-node v0.10.12 h1:yOxMThwwz1rEDDM0xTjS+6jqJwgKRtrYM6h4Pdf0zno=
github.com/ethereum-optimism/optimism/op-node v0.10.11/go.mod h1:/CDpkMxc3mDklZ1nqz2lmxfeUyAUz7yC/OLmX8egAUw= github.com/ethereum-optimism/optimism/op-node v0.10.12/go.mod h1:z+DiFb82Vnn5zM3VEwc2OXK2V/JBg6MLg7ejTbsxye8=
github.com/ethereum-optimism/optimism/op-proposer v0.10.11 h1:uG+CXcac1LVRAnivv+3q7qJZDTgdKLv0D3mbu2o7nKI= github.com/ethereum-optimism/optimism/op-proposer v0.10.12 h1:4K61QLg1mF38zwsaTQKOJnDqc65DKKsO33X20cLrhmk=
github.com/ethereum-optimism/optimism/op-proposer v0.10.11/go.mod h1:O/BolDMRNanlblBQKwq2UGwnl7hTdKrnVlmXfijG7vw= github.com/ethereum-optimism/optimism/op-proposer v0.10.12/go.mod h1:JBB9oVfhx9VjrBgcE5KIxCdQPnr7ma/xLBLYg2EFo9I=
github.com/ethereum-optimism/optimism/op-service v0.10.11 h1:o+SazhFXlE3EM9Re5KIPEQklZ9uTI8rNkjl0h5OwRtU= github.com/ethereum-optimism/optimism/op-service v0.10.12 h1:Y7pR3/b8eeHYkmo2V5z7sj8jaraYqm2Azyph5lbiIxo=
github.com/ethereum-optimism/optimism/op-service v0.10.11/go.mod h1:wbtHqi1fv00B3agj7a2zdP3OFanEfGZ23zPgGgFCF/c= github.com/ethereum-optimism/optimism/op-service v0.10.12/go.mod h1:Ibbun+aic0rjQBV8yBf9kohqIj6mQ8nSTWbZjHv+Q7Q=
github.com/ethereum-optimism/optimism/op-signer v0.1.0 h1:wH44Deai43YQWO0pEd44pDm3BahdAtSmrOHKiPvTB8Y= github.com/ethereum-optimism/optimism/op-signer v0.1.0 h1:wH44Deai43YQWO0pEd44pDm3BahdAtSmrOHKiPvTB8Y=
github.com/ethereum-optimism/optimism/op-signer v0.1.0/go.mod h1:u8sN6X/c20pP9F1Ey7jH3fi19D08Y+T9ep3PGJfdyi8= github.com/ethereum-optimism/optimism/op-signer v0.1.0/go.mod h1:u8sN6X/c20pP9F1Ey7jH3fi19D08Y+T9ep3PGJfdyi8=
github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ= github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ=
......
...@@ -319,12 +319,14 @@ func TestMigration(t *testing.T) { ...@@ -319,12 +319,14 @@ func TestMigration(t *testing.T) {
require.NoError(t, rollupNode.Close()) require.NoError(t, rollupNode.Close())
}) })
batcher, err := bss.NewBatchSubmitter(bss.Config{ batcher, err := bss.NewBatchSubmitterFromCLIConfig(bss.CLIConfig{
L1EthRpc: forkedL1URL, L1EthRpc: forkedL1URL,
L2EthRpc: gethNode.WSEndpoint(), L2EthRpc: gethNode.WSEndpoint(),
RollupRpc: rollupNode.HTTPEndpoint(), RollupRpc: rollupNode.HTTPEndpoint(),
MinL1TxSize: 1, MaxL1TxSize: 120_000,
MaxL1TxSize: 120000, TargetL1TxSize: 1,
TargetNumFrames: 1,
ApproxComprRatio: 1.0,
ChannelTimeout: deployCfg.ChannelTimeout, ChannelTimeout: deployCfg.ChannelTimeout,
PollInterval: 50 * time.Millisecond, PollInterval: 50 * time.Millisecond,
NumConfirmations: 1, NumConfirmations: 1,
......
...@@ -522,12 +522,14 @@ func (cfg SystemConfig) Start() (*System, error) { ...@@ -522,12 +522,14 @@ func (cfg SystemConfig) Start() (*System, error) {
} }
// Batch Submitter // Batch Submitter
sys.BatchSubmitter, err = bss.NewBatchSubmitter(bss.Config{ sys.BatchSubmitter, err = bss.NewBatchSubmitterFromCLIConfig(bss.CLIConfig{
L1EthRpc: sys.Nodes["l1"].WSEndpoint(), L1EthRpc: sys.Nodes["l1"].WSEndpoint(),
L2EthRpc: sys.Nodes["sequencer"].WSEndpoint(), L2EthRpc: sys.Nodes["sequencer"].WSEndpoint(),
RollupRpc: sys.RollupNodes["sequencer"].HTTPEndpoint(), RollupRpc: sys.RollupNodes["sequencer"].HTTPEndpoint(),
MinL1TxSize: 1, MaxL1TxSize: 120_000,
MaxL1TxSize: 120000, TargetL1TxSize: 1,
TargetNumFrames: 1,
ApproxComprRatio: 1.0,
ChannelTimeout: cfg.DeployConfig.ChannelTimeout, ChannelTimeout: cfg.DeployConfig.ChannelTimeout,
PollInterval: 50 * time.Millisecond, PollInterval: 50 * time.Millisecond,
NumConfirmations: 1, NumConfirmations: 1,
......
...@@ -883,7 +883,7 @@ func TestWithdrawals(t *testing.T) { ...@@ -883,7 +883,7 @@ func TestWithdrawals(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
// Get l2BlockNumber for proof generation // Get l2BlockNumber for proof generation
ctx, cancel = context.WithTimeout(context.Background(), 20*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) ctx, cancel = context.WithTimeout(context.Background(), 30*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
defer cancel() defer cancel()
blockNumber, err := withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, receipt.BlockNumber) blockNumber, err := withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, receipt.BlockNumber)
require.Nil(t, err) require.Nil(t, err)
...@@ -933,7 +933,7 @@ func TestWithdrawals(t *testing.T) { ...@@ -933,7 +933,7 @@ func TestWithdrawals(t *testing.T) {
require.Equal(t, types.ReceiptStatusSuccessful, proveReceipt.Status) require.Equal(t, types.ReceiptStatusSuccessful, proveReceipt.Status)
// Wait for finalization and then create the Finalized Withdrawal Transaction // Wait for finalization and then create the Finalized Withdrawal Transaction
ctx, cancel = context.WithTimeout(context.Background(), 20*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) ctx, cancel = context.WithTimeout(context.Background(), 30*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
defer cancel() defer cancel()
_, err = withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, header.Number) _, err = withdrawals.WaitForFinalizationPeriod(ctx, l1Client, predeploys.DevOptimismPortalAddr, header.Number)
require.Nil(t, err) require.Nil(t, err)
...@@ -1052,10 +1052,10 @@ func TestFees(t *testing.T) { ...@@ -1052,10 +1052,10 @@ func TestFees(t *testing.T) {
err = l2Seq.SendTransaction(context.Background(), tx) err = l2Seq.SendTransaction(context.Background(), tx)
require.Nil(t, err, "Sending L2 tx to sequencer") require.Nil(t, err, "Sending L2 tx to sequencer")
_, err = waitForTransaction(tx.Hash(), l2Seq, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) _, err = waitForTransaction(tx.Hash(), l2Seq, 4*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
require.Nil(t, err, "Waiting for L2 tx on sequencer") require.Nil(t, err, "Waiting for L2 tx on sequencer")
receipt, err := waitForTransaction(tx.Hash(), l2Verif, 3*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) receipt, err := waitForTransaction(tx.Hash(), l2Verif, 4*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second)
require.Nil(t, err, "Waiting for L2 tx on verifier") require.Nil(t, err, "Waiting for L2 tx on verifier")
require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "TX should have succeeded") require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "TX should have succeeded")
......
...@@ -123,7 +123,7 @@ func RollupNodeMain(ctx *cli.Context) error { ...@@ -123,7 +123,7 @@ func RollupNodeMain(ctx *cli.Context) error {
if cfg.Heartbeat.Enabled { if cfg.Heartbeat.Enabled {
var peerID string var peerID string
if cfg.P2P == nil { if cfg.P2P.Disabled() {
peerID = "disabled" peerID = "disabled"
} else { } else {
peerID = n.P2P().Host().ID().String() peerID = n.P2P().Host().ID().String()
......
...@@ -15,7 +15,7 @@ import ( ...@@ -15,7 +15,7 @@ import (
type StorageProofEntry struct { type StorageProofEntry struct {
Key common.Hash `json:"key"` Key common.Hash `json:"key"`
Value hexutil.Bytes `json:"value"` Value hexutil.Big `json:"value"`
Proof []hexutil.Bytes `json:"proof"` Proof []hexutil.Bytes `json:"proof"`
} }
......
...@@ -6,9 +6,9 @@ require ( ...@@ -6,9 +6,9 @@ require (
github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd v0.23.3
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0
github.com/ethereum-optimism/optimism/op-bindings v0.10.11 github.com/ethereum-optimism/optimism/op-bindings v0.10.12
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.11 github.com/ethereum-optimism/optimism/op-chain-ops v0.10.12
github.com/ethereum-optimism/optimism/op-service v0.10.11 github.com/ethereum-optimism/optimism/op-service v0.10.12
github.com/ethereum/go-ethereum v1.10.26 github.com/ethereum/go-ethereum v1.10.26
github.com/golang/snappy v0.0.4 github.com/golang/snappy v0.0.4
github.com/google/go-cmp v0.5.8 github.com/google/go-cmp v0.5.8
...@@ -52,8 +52,7 @@ require ( ...@@ -52,8 +52,7 @@ require (
github.com/fjl/memsize v0.0.1 // indirect github.com/fjl/memsize v0.0.1 // indirect
github.com/flynn/noise v1.0.0 // indirect github.com/flynn/noise v1.0.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect github.com/francoispqt/gojay v1.2.13 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect github.com/go-stack/stack v1.8.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
...@@ -137,7 +136,6 @@ require ( ...@@ -137,7 +136,6 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.5.0 // indirect github.com/tklauser/numcpus v0.5.0 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa // indirect github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa // indirect
github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee // indirect github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
...@@ -151,7 +149,6 @@ require ( ...@@ -151,7 +149,6 @@ require (
golang.org/x/net v0.0.0-20220920183852-bf014ff85ad5 // indirect golang.org/x/net v0.0.0-20220920183852-bf014ff85ad5 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
golang.org/x/tools v0.1.12 // indirect golang.org/x/tools v0.1.12 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
......
...@@ -145,12 +145,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m ...@@ -145,12 +145,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY=
github.com/ethereum-optimism/optimism/op-bindings v0.10.11 h1:RDiRyHo0G/UuxHZQdMJyqIuHtWvpionuFNfczNaWCcM= github.com/ethereum-optimism/optimism/op-bindings v0.10.12 h1:/B1gaCLwZYy9Rja3MiceV3R642bygp937kZjnYwRxA0=
github.com/ethereum-optimism/optimism/op-bindings v0.10.11/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c= github.com/ethereum-optimism/optimism/op-bindings v0.10.12/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c=
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.11 h1:6ihrVPJYN1HvD4KG0Fk1zIJCM4ZB109kCu9fq81jznQ= github.com/ethereum-optimism/optimism/op-chain-ops v0.10.12 h1:gRX5oIk0hboTOZ65y5MpEdM5+nmaad87QZP1donrHXo=
github.com/ethereum-optimism/optimism/op-chain-ops v0.10.11/go.mod h1:6mub7Tx1cC4gDrfX9o9n+kA4R2qLlYvfWkG8es21EQI= github.com/ethereum-optimism/optimism/op-chain-ops v0.10.12/go.mod h1:x98mGAxXIhyYM4WsnbgziJCAk+qA4JX0wvaE+azBg48=
github.com/ethereum-optimism/optimism/op-service v0.10.11 h1:o+SazhFXlE3EM9Re5KIPEQklZ9uTI8rNkjl0h5OwRtU= github.com/ethereum-optimism/optimism/op-service v0.10.12 h1:Y7pR3/b8eeHYkmo2V5z7sj8jaraYqm2Azyph5lbiIxo=
github.com/ethereum-optimism/optimism/op-service v0.10.11/go.mod h1:wbtHqi1fv00B3agj7a2zdP3OFanEfGZ23zPgGgFCF/c= github.com/ethereum-optimism/optimism/op-service v0.10.12/go.mod h1:Ibbun+aic0rjQBV8yBf9kohqIj6mQ8nSTWbZjHv+Q7Q=
github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ= github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ=
github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
...@@ -160,10 +160,10 @@ github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJn ...@@ -160,10 +160,10 @@ github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJn
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
...@@ -587,7 +587,7 @@ github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w ...@@ -587,7 +587,7 @@ github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d h1:vmirMegf1vqPJ+lDBxLQ0MAt3tz+JL57UPxu44JBOjA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
...@@ -613,7 +613,6 @@ github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hM ...@@ -613,7 +613,6 @@ github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hM
github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+A= github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+A=
github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo= github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo=
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw= github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw=
github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
...@@ -846,6 +845,7 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc ...@@ -846,6 +845,7 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4= golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4=
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
...@@ -865,7 +865,6 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb ...@@ -865,7 +865,6 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs= golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
......
...@@ -33,6 +33,7 @@ var DefaultBootnodes = []*enode.Node{ ...@@ -33,6 +33,7 @@ var DefaultBootnodes = []*enode.Node{
// SetupP2P provides a host and discovery service for usage in the rollup node. // SetupP2P provides a host and discovery service for usage in the rollup node.
type SetupP2P interface { type SetupP2P interface {
Check() error Check() error
Disabled() bool
// Host creates a libp2p host service. Returns nil, nil if p2p is disabled. // Host creates a libp2p host service. Returns nil, nil if p2p is disabled.
Host(log log.Logger, reporter metrics.Reporter) (host.Host, error) Host(log log.Logger, reporter metrics.Reporter) (host.Host, error)
// Discovery creates a disc-v5 service. Returns nil, nil, nil if discovery is disabled. // Discovery creates a disc-v5 service. Returns nil, nil, nil if discovery is disabled.
...@@ -134,6 +135,10 @@ func (conf *Config) TargetPeers() uint { ...@@ -134,6 +135,10 @@ func (conf *Config) TargetPeers() uint {
return conf.PeersLo return conf.PeersLo
} }
func (conf *Config) Disabled() bool {
return conf.DisableP2P
}
const maxMeshParam = 1000 const maxMeshParam = 1000
func (conf *Config) Check() error { func (conf *Config) Check() error {
......
...@@ -63,3 +63,7 @@ func (p *Prepared) Discovery(log log.Logger, rollupCfg *rollup.Config, tcpPort u ...@@ -63,3 +63,7 @@ func (p *Prepared) Discovery(log log.Logger, rollupCfg *rollup.Config, tcpPort u
func (p *Prepared) ConfigureGossip(params *pubsub.GossipSubParams) []pubsub.Option { func (p *Prepared) ConfigureGossip(params *pubsub.GossipSubParams) []pubsub.Option {
return nil return nil
} }
func (p *Prepared) Disabled() bool {
return false
}
...@@ -64,39 +64,40 @@ func (co *ChannelOut) Reset() error { ...@@ -64,39 +64,40 @@ func (co *ChannelOut) Reset() error {
co.compress.Reset(&co.buf) co.compress.Reset(&co.buf)
co.closed = false co.closed = false
_, err := rand.Read(co.id[:]) _, err := rand.Read(co.id[:])
if err != nil { return err
return err
}
return nil
} }
// AddBlock adds a block to the channel. It returns an error // AddBlock adds a block to the channel. It returns the RLP encoded byte size
// if there is a problem adding the block. The only sentinel // and an error if there is a problem adding the block. The only sentinel error
// error that it returns is ErrTooManyRLPBytes. If this error // that it returns is ErrTooManyRLPBytes. If this error is returned, the channel
// is returned, the channel should be closed and a new one // should be closed and a new one should be made.
// should be made. func (co *ChannelOut) AddBlock(block *types.Block) (uint64, error) {
func (co *ChannelOut) AddBlock(block *types.Block) error {
if co.closed { if co.closed {
return errors.New("already closed") return 0, errors.New("already closed")
} }
batch, err := blockToBatch(block) batch, err := blockToBatch(block)
if err != nil { if err != nil {
return err return 0, err
} }
// We encode to a temporary buffer to determine the encoded length to // We encode to a temporary buffer to determine the encoded length to
// ensure that the total size of all RLP elements is less than or equal to MAX_RLP_BYTES_PER_CHANNEL // ensure that the total size of all RLP elements is less than or equal to MAX_RLP_BYTES_PER_CHANNEL
var buf bytes.Buffer var buf bytes.Buffer
if err := rlp.Encode(&buf, batch); err != nil { if err := rlp.Encode(&buf, batch); err != nil {
return err return 0, err
} }
if co.rlpLength+buf.Len() > MaxRLPBytesPerChannel { if co.rlpLength+buf.Len() > MaxRLPBytesPerChannel {
return fmt.Errorf("could not add %d bytes to channel of %d bytes, max is %d. err: %w", return 0, fmt.Errorf("could not add %d bytes to channel of %d bytes, max is %d. err: %w",
buf.Len(), co.rlpLength, MaxRLPBytesPerChannel, ErrTooManyRLPBytes) buf.Len(), co.rlpLength, MaxRLPBytesPerChannel, ErrTooManyRLPBytes)
} }
co.rlpLength += buf.Len() co.rlpLength += buf.Len()
_, err = io.Copy(co.compress, &buf) written, err := io.Copy(co.compress, &buf)
return err return uint64(written), err
}
// InputBytes returns the total amount of RLP-encoded input bytes.
func (co *ChannelOut) InputBytes() int {
return co.rlpLength
} }
// ReadyBytes returns the number of bytes that the channel out can immediately output into a frame. // ReadyBytes returns the number of bytes that the channel out can immediately output into a frame.
...@@ -120,12 +121,13 @@ func (co *ChannelOut) Close() error { ...@@ -120,12 +121,13 @@ func (co *ChannelOut) Close() error {
return co.compress.Close() return co.compress.Close()
} }
// OutputFrame writes a frame to w with a given max size // OutputFrame writes a frame to w with a given max size and returns the frame
// number.
// Use `ReadyBytes`, `Flush`, and `Close` to modify the ready buffer. // Use `ReadyBytes`, `Flush`, and `Close` to modify the ready buffer.
// Returns io.EOF when the channel is closed & there are no more frames // Returns io.EOF when the channel is closed & there are no more frames
// Returns nil if there is still more buffered data. // Returns nil if there is still more buffered data.
// Returns and error if it ran into an error during processing. // Returns and error if it ran into an error during processing.
func (co *ChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) error { func (co *ChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) (uint16, error) {
f := Frame{ f := Frame{
ID: co.id, ID: co.id,
FrameNumber: uint16(co.frame), FrameNumber: uint16(co.frame),
...@@ -133,9 +135,8 @@ func (co *ChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) error { ...@@ -133,9 +135,8 @@ func (co *ChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) error {
// Copy data from the local buffer into the frame data buffer // Copy data from the local buffer into the frame data buffer
// Don't go past the maxSize with the fixed frame overhead. // Don't go past the maxSize with the fixed frame overhead.
// Fixed overhead: 32 + 8 + 2 + 4 + 1 = 47 bytes. // Fixed overhead: 16 + 2 + 4 + 1 = 23 bytes.
// Add one extra byte for the version byte (for the entire L1 tx though) maxDataSize := maxSize - 23
maxDataSize := maxSize - 47 - 1
if maxDataSize > uint64(co.buf.Len()) { if maxDataSize > uint64(co.buf.Len()) {
maxDataSize = uint64(co.buf.Len()) maxDataSize = uint64(co.buf.Len())
// If we are closed & will not spill past the current frame // If we are closed & will not spill past the current frame
...@@ -147,18 +148,19 @@ func (co *ChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) error { ...@@ -147,18 +148,19 @@ func (co *ChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) error {
f.Data = make([]byte, maxDataSize) f.Data = make([]byte, maxDataSize)
if _, err := io.ReadFull(&co.buf, f.Data); err != nil { if _, err := io.ReadFull(&co.buf, f.Data); err != nil {
return err return 0, err
} }
if err := f.MarshalBinary(w); err != nil { if err := f.MarshalBinary(w); err != nil {
return err return 0, err
} }
co.frame += 1 co.frame += 1
fn := f.FrameNumber
if f.IsLast { if f.IsLast {
return io.EOF return fn, io.EOF
} else { } else {
return nil return fn, nil
} }
} }
......
...@@ -22,7 +22,7 @@ func TestChannelOutAddBlock(t *testing.T) { ...@@ -22,7 +22,7 @@ func TestChannelOutAddBlock(t *testing.T) {
}, },
nil, nil,
) )
err := cout.AddBlock(block) _, err := cout.AddBlock(block)
require.Error(t, err) require.Error(t, err)
require.Equal(t, ErrNotDepositTx, err) require.Equal(t, ErrNotDepositTx, err)
}) })
......
...@@ -33,6 +33,31 @@ type Engine interface { ...@@ -33,6 +33,31 @@ type Engine interface {
SystemConfigL2Fetcher SystemConfigL2Fetcher
} }
// EngineState provides a read-only interface of the forkchoice state properties of the L2 Engine.
type EngineState interface {
Finalized() eth.L2BlockRef
UnsafeL2Head() eth.L2BlockRef
SafeL2Head() eth.L2BlockRef
}
// EngineControl enables other components to build blocks with the Engine,
// while keeping the forkchoice state and payload-id management internal to
// avoid state inconsistencies between different users of the EngineControl.
type EngineControl interface {
EngineState
// StartPayload requests the engine to start building a block with the given attributes.
// If updateSafe, the resulting block will be marked as a safe block.
StartPayload(ctx context.Context, parent eth.L2BlockRef, attrs *eth.PayloadAttributes, updateSafe bool) (errType BlockInsertionErrType, err error)
// ConfirmPayload requests the engine to complete the current block. If no block is being built, or if it fails, an error is returned.
ConfirmPayload(ctx context.Context) (out *eth.ExecutionPayload, errTyp BlockInsertionErrType, err error)
// CancelPayload requests the engine to stop building the current block without making it canonical.
// This is optional, as the engine expires building jobs that are left uncompleted, but can still save resources.
CancelPayload(ctx context.Context, force bool) error
// BuildingPayload indicates if a payload is being built, and onto which block it is being built, and whether or not it is a safe payload.
BuildingPayload() (onto eth.L2BlockRef, id eth.PayloadID, safe bool)
}
// Max memory used for buffering unsafe payloads // Max memory used for buffering unsafe payloads
const maxUnsafePayloadsMemory = 500 * 1024 * 1024 const maxUnsafePayloadsMemory = 500 * 1024 * 1024
...@@ -68,6 +93,10 @@ type EngineQueue struct { ...@@ -68,6 +93,10 @@ type EngineQueue struct {
safeHead eth.L2BlockRef safeHead eth.L2BlockRef
unsafeHead eth.L2BlockRef unsafeHead eth.L2BlockRef
buildingOnto eth.L2BlockRef
buildingID eth.PayloadID
buildingSafe bool
// Track when the rollup node changes the forkchoice without engine action, // Track when the rollup node changes the forkchoice without engine action,
// e.g. on a reset after a reorg, or after consolidating a block. // e.g. on a reset after a reorg, or after consolidating a block.
// This update may repeat if the engine returns a temporary error. // This update may repeat if the engine returns a temporary error.
...@@ -91,6 +120,8 @@ type EngineQueue struct { ...@@ -91,6 +120,8 @@ type EngineQueue struct {
l1Fetcher L1Fetcher l1Fetcher L1Fetcher
} }
var _ EngineControl = (*EngineQueue)(nil)
// NewEngineQueue creates a new EngineQueue, which should be Reset(origin) before use. // NewEngineQueue creates a new EngineQueue, which should be Reset(origin) before use.
func NewEngineQueue(log log.Logger, cfg *rollup.Config, engine Engine, metrics Metrics, prev NextAttributesProvider, l1Fetcher L1Fetcher) *EngineQueue { func NewEngineQueue(log log.Logger, cfg *rollup.Config, engine Engine, metrics Metrics, prev NextAttributesProvider, l1Fetcher L1Fetcher) *EngineQueue {
return &EngineQueue{ return &EngineQueue{
...@@ -416,13 +447,11 @@ func (eq *EngineQueue) forceNextSafeAttributes(ctx context.Context) error { ...@@ -416,13 +447,11 @@ func (eq *EngineQueue) forceNextSafeAttributes(ctx context.Context) error {
if len(eq.safeAttributes) == 0 { if len(eq.safeAttributes) == 0 {
return nil return nil
} }
fc := eth.ForkchoiceState{
HeadBlockHash: eq.safeHead.Hash,
SafeBlockHash: eq.safeHead.Hash,
FinalizedBlockHash: eq.finalized.Hash,
}
attrs := eq.safeAttributes[0] attrs := eq.safeAttributes[0]
payload, errType, err := InsertHeadBlock(ctx, eq.log, eq.engine, fc, attrs, true) errType, err := eq.StartPayload(ctx, eq.safeHead, attrs, true)
if err == nil {
_, errType, err = eq.ConfirmPayload(ctx)
}
if err != nil { if err != nil {
switch errType { switch errType {
case BlockInsertTemporaryErr: case BlockInsertTemporaryErr:
...@@ -457,21 +486,89 @@ func (eq *EngineQueue) forceNextSafeAttributes(ctx context.Context) error { ...@@ -457,21 +486,89 @@ func (eq *EngineQueue) forceNextSafeAttributes(ctx context.Context) error {
return NewCriticalError(fmt.Errorf("unknown InsertHeadBlock error type %d: %w", errType, err)) return NewCriticalError(fmt.Errorf("unknown InsertHeadBlock error type %d: %w", errType, err))
} }
} }
eq.safeAttributes = eq.safeAttributes[1:]
eq.logSyncProgress("processed safe block derived from L1")
return nil
}
func (eq *EngineQueue) StartPayload(ctx context.Context, parent eth.L2BlockRef, attrs *eth.PayloadAttributes, updateSafe bool) (errType BlockInsertionErrType, err error) {
if eq.buildingID != (eth.PayloadID{}) {
eq.log.Warn("did not finish previous block building, starting new building now", "prev_onto", eq.buildingOnto, "prev_payload_id", eq.buildingID, "new_onto", parent)
// TODO: maybe worth it to force-cancel the old payload ID here.
}
fc := eth.ForkchoiceState{
HeadBlockHash: parent.Hash,
SafeBlockHash: eq.safeHead.Hash,
FinalizedBlockHash: eq.finalized.Hash,
}
id, errTyp, err := StartPayload(ctx, eq.engine, fc, attrs)
if err != nil {
return errTyp, err
}
eq.buildingID = id
eq.buildingSafe = updateSafe
eq.buildingOnto = parent
return BlockInsertOK, nil
}
func (eq *EngineQueue) ConfirmPayload(ctx context.Context) (out *eth.ExecutionPayload, errTyp BlockInsertionErrType, err error) {
if eq.buildingID == (eth.PayloadID{}) {
return nil, BlockInsertPrestateErr, fmt.Errorf("cannot complete payload building: not currently building a payload")
}
if eq.buildingOnto.Hash != eq.unsafeHead.Hash { // E.g. when safe-attributes consolidation fails, it will drop the existing work.
eq.log.Warn("engine is building block that reorgs previous usafe head", "onto", eq.buildingOnto, "unsafe", eq.unsafeHead)
}
fc := eth.ForkchoiceState{
HeadBlockHash: common.Hash{}, // gets overridden
SafeBlockHash: eq.safeHead.Hash,
FinalizedBlockHash: eq.finalized.Hash,
}
payload, errTyp, err := ConfirmPayload(ctx, eq.log, eq.engine, fc, eq.buildingID, eq.buildingSafe)
if err != nil {
return nil, errTyp, fmt.Errorf("failed to complete building on top of L2 chain %s, id: %s, error (%d): %w", eq.buildingOnto, eq.buildingID, errTyp, err)
}
ref, err := PayloadToBlockRef(payload, &eq.cfg.Genesis) ref, err := PayloadToBlockRef(payload, &eq.cfg.Genesis)
if err != nil { if err != nil {
return NewTemporaryError(fmt.Errorf("failed to decode L2 block ref from payload: %w", err)) return nil, BlockInsertPayloadErr, NewResetError(fmt.Errorf("failed to decode L2 block ref from payload: %w", err))
} }
eq.safeHead = ref
eq.unsafeHead = ref eq.unsafeHead = ref
eq.metrics.RecordL2Ref("l2_safe", ref)
eq.metrics.RecordL2Ref("l2_unsafe", ref) eq.metrics.RecordL2Ref("l2_unsafe", ref)
eq.safeAttributes = eq.safeAttributes[1:]
eq.postProcessSafeL2()
eq.logSyncProgress("processed safe block derived from L1")
if eq.buildingSafe {
eq.safeHead = ref
eq.postProcessSafeL2()
eq.metrics.RecordL2Ref("l2_safe", ref)
}
eq.resetBuildingState()
return payload, BlockInsertOK, nil
}
func (eq *EngineQueue) CancelPayload(ctx context.Context, force bool) error {
// the building job gets wrapped up as soon as the payload is retrieved, there's no explicit cancel in the Engine API
eq.log.Error("cancelling old block sealing job", "payload", eq.buildingID)
_, err := eq.engine.GetPayload(ctx, eq.buildingID)
if err != nil {
eq.log.Error("failed to cancel block building job", "payload", eq.buildingID, "err", err)
if !force {
return err
}
}
eq.resetBuildingState()
return nil return nil
} }
func (eq *EngineQueue) BuildingPayload() (onto eth.L2BlockRef, id eth.PayloadID, safe bool) {
return eq.buildingOnto, eq.buildingID, eq.buildingSafe
}
func (eq *EngineQueue) resetBuildingState() {
eq.buildingID = eth.PayloadID{}
eq.buildingOnto = eth.L2BlockRef{}
eq.buildingSafe = false
}
// ResetStep Walks the L2 chain backwards until it finds an L2 block whose L1 origin is canonical. // ResetStep Walks the L2 chain backwards until it finds an L2 block whose L1 origin is canonical.
// The unsafe head is set to the head of the L2 chain, unless the existing safe head is not canonical. // The unsafe head is set to the head of the L2 chain, unless the existing safe head is not canonical.
func (eq *EngineQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.SystemConfig) error { func (eq *EngineQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.SystemConfig) error {
...@@ -517,6 +614,7 @@ func (eq *EngineQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.System ...@@ -517,6 +614,7 @@ func (eq *EngineQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.System
eq.unsafeHead = unsafe eq.unsafeHead = unsafe
eq.safeHead = safe eq.safeHead = safe
eq.finalized = finalized eq.finalized = finalized
eq.resetBuildingState()
eq.needForkchoiceUpdate = true eq.needForkchoiceUpdate = true
eq.finalityData = eq.finalityData[:0] eq.finalityData = eq.finalityData[:0]
// note: we do not clear the unsafe payloads queue; if the payloads are not applicable anymore the parent hash checks will clear out the old payloads. // note: we do not clear the unsafe payloads queue; if the payloads are not applicable anymore the parent hash checks will clear out the old payloads.
......
...@@ -79,19 +79,6 @@ const ( ...@@ -79,19 +79,6 @@ const (
BlockInsertPayloadErr BlockInsertPayloadErr
) )
// InsertHeadBlock creates, executes, and inserts the specified block as the head block.
// It first uses the given FC to start the block creation process and then after the payload is executed,
// sets the FC to the same safe and finalized hashes, but updates the head hash to the new block.
// If updateSafe is true, the head block is considered to be the safe head as well as the head.
// It returns the payload, an RPC error (if the payload might still be valid), and a payload error (if the payload was not valid)
func InsertHeadBlock(ctx context.Context, log log.Logger, eng Engine, fc eth.ForkchoiceState, attrs *eth.PayloadAttributes, updateSafe bool) (out *eth.ExecutionPayload, errTyp BlockInsertionErrType, err error) {
id, errTyp, err := StartPayload(ctx, eng, fc, attrs)
if err != nil {
return nil, errTyp, err
}
return ConfirmPayload(ctx, log, eng, fc, id, updateSafe)
}
// StartPayload starts an execution payload building process in the provided Engine, with the given attributes. // StartPayload starts an execution payload building process in the provided Engine, with the given attributes.
// The severity of the error is distinguished to determine whether the same payload attributes may be re-attempted later. // The severity of the error is distinguished to determine whether the same payload attributes may be re-attempted later.
func StartPayload(ctx context.Context, eng Engine, fc eth.ForkchoiceState, attrs *eth.PayloadAttributes) (id eth.PayloadID, errType BlockInsertionErrType, err error) { func StartPayload(ctx context.Context, eng Engine, fc eth.ForkchoiceState, attrs *eth.PayloadAttributes) (id eth.PayloadID, errType BlockInsertionErrType, err error) {
......
...@@ -31,6 +31,8 @@ type ResetableStage interface { ...@@ -31,6 +31,8 @@ type ResetableStage interface {
} }
type EngineQueueStage interface { type EngineQueueStage interface {
EngineControl
FinalizedL1() eth.L1BlockRef FinalizedL1() eth.L1BlockRef
Finalized() eth.L2BlockRef Finalized() eth.L2BlockRef
UnsafeL2Head() eth.L2BlockRef UnsafeL2Head() eth.L2BlockRef
...@@ -130,8 +132,20 @@ func (dp *DerivationPipeline) UnsafeL2Head() eth.L2BlockRef { ...@@ -130,8 +132,20 @@ func (dp *DerivationPipeline) UnsafeL2Head() eth.L2BlockRef {
return dp.eng.UnsafeL2Head() return dp.eng.UnsafeL2Head()
} }
func (dp *DerivationPipeline) SetUnsafeHead(head eth.L2BlockRef) { func (dp *DerivationPipeline) StartPayload(ctx context.Context, parent eth.L2BlockRef, attrs *eth.PayloadAttributes, updateSafe bool) (errType BlockInsertionErrType, err error) {
dp.eng.SetUnsafeHead(head) return dp.eng.StartPayload(ctx, parent, attrs, updateSafe)
}
func (dp *DerivationPipeline) ConfirmPayload(ctx context.Context) (out *eth.ExecutionPayload, errTyp BlockInsertionErrType, err error) {
return dp.eng.ConfirmPayload(ctx)
}
func (dp *DerivationPipeline) CancelPayload(ctx context.Context, force bool) error {
return dp.eng.CancelPayload(ctx, force)
}
func (dp *DerivationPipeline) BuildingPayload() (onto eth.L2BlockRef, id eth.PayloadID, safe bool) {
return dp.eng.BuildingPayload()
} }
// AddUnsafePayload schedules an execution payload to be processed, ahead of deriving it from L1 // AddUnsafePayload schedules an execution payload to be processed, ahead of deriving it from L1
......
...@@ -14,7 +14,6 @@ import ( ...@@ -14,7 +14,6 @@ import (
type Metrics interface { type Metrics interface {
RecordPipelineReset() RecordPipelineReset()
RecordSequencingError()
RecordPublishingError() RecordPublishingError()
RecordDerivationError() RecordDerivationError()
...@@ -28,9 +27,8 @@ type Metrics interface { ...@@ -28,9 +27,8 @@ type Metrics interface {
SetDerivationIdle(idle bool) SetDerivationIdle(idle bool)
RecordL1ReorgDepth(d uint64) RecordL1ReorgDepth(d uint64)
CountSequencedTxs(count int)
SequencerMetrics EngineMetrics
} }
type L1Chain interface { type L1Chain interface {
...@@ -48,7 +46,6 @@ type L2Chain interface { ...@@ -48,7 +46,6 @@ type L2Chain interface {
type DerivationPipeline interface { type DerivationPipeline interface {
Reset() Reset()
Step(ctx context.Context) error Step(ctx context.Context) error
SetUnsafeHead(head eth.L2BlockRef)
AddUnsafePayload(payload *eth.ExecutionPayload) AddUnsafePayload(payload *eth.ExecutionPayload)
Finalize(ref eth.L1BlockRef) Finalize(ref eth.L1BlockRef)
FinalizedL1() eth.L1BlockRef FinalizedL1() eth.L1BlockRef
...@@ -68,14 +65,12 @@ type L1StateIface interface { ...@@ -68,14 +65,12 @@ type L1StateIface interface {
L1Finalized() eth.L1BlockRef L1Finalized() eth.L1BlockRef
} }
type L1OriginSelectorIface interface {
FindL1Origin(ctx context.Context, l1Head eth.L1BlockRef, l2Head eth.L2BlockRef) (eth.L1BlockRef, error)
}
type SequencerIface interface { type SequencerIface interface {
StartBuildingBlock(ctx context.Context, l1Origin eth.L1BlockRef) error StartBuildingBlock(ctx context.Context) error
CompleteBuildingBlock(ctx context.Context) (*eth.ExecutionPayload, error) CompleteBuildingBlock(ctx context.Context) (*eth.ExecutionPayload, error)
PlanNextSequencerAction(sequenceErr error) (delay time.Duration, seal bool, onto eth.BlockID) PlanNextSequencerAction() time.Duration
RunNextSequencerAction(ctx context.Context) *eth.ExecutionPayload
BuildingOnto() eth.L2BlockRef
} }
type Network interface { type Network interface {
...@@ -86,11 +81,15 @@ type Network interface { ...@@ -86,11 +81,15 @@ type Network interface {
// NewDriver composes an events handler that tracks L1 state, triggers L2 derivation, and optionally sequences new L2 blocks. // NewDriver composes an events handler that tracks L1 state, triggers L2 derivation, and optionally sequences new L2 blocks.
func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, network Network, log log.Logger, snapshotLog log.Logger, metrics Metrics) *Driver { func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, network Network, log log.Logger, snapshotLog log.Logger, metrics Metrics) *Driver {
l1State := NewL1State(log, metrics) l1State := NewL1State(log, metrics)
findL1Origin := NewL1OriginSelector(log, cfg, l1, driverCfg.SequencerConfDepth) sequencerConfDepth := NewConfDepth(driverCfg.SequencerConfDepth, l1State.L1Head, l1)
findL1Origin := NewL1OriginSelector(log, cfg, sequencerConfDepth)
verifConfDepth := NewConfDepth(driverCfg.VerifierConfDepth, l1State.L1Head, l1) verifConfDepth := NewConfDepth(driverCfg.VerifierConfDepth, l1State.L1Head, l1)
derivationPipeline := derive.NewDerivationPipeline(log, cfg, verifConfDepth, l2, metrics) derivationPipeline := derive.NewDerivationPipeline(log, cfg, verifConfDepth, l2, metrics)
attrBuilder := derive.NewFetchingAttributesBuilder(cfg, l1, l2) attrBuilder := derive.NewFetchingAttributesBuilder(cfg, l1, l2)
sequencer := NewSequencer(log, cfg, l2, derivationPipeline, attrBuilder, metrics) engine := derivationPipeline
meteredEngine := NewMeteredEngine(cfg, engine, metrics, log)
sequencer := NewSequencer(log, cfg, meteredEngine, attrBuilder, findL1Origin)
return &Driver{ return &Driver{
l1State: l1State, l1State: l1State,
derivation: derivationPipeline, derivation: derivationPipeline,
...@@ -106,7 +105,6 @@ func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, ne ...@@ -106,7 +105,6 @@ func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, ne
snapshotLog: snapshotLog, snapshotLog: snapshotLog,
l1: l1, l1: l1,
l2: l2, l2: l2,
l1OriginSelector: findL1Origin,
sequencer: sequencer, sequencer: sequencer,
network: network, network: network,
metrics: metrics, metrics: metrics,
......
package driver
import (
"context"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
)
type EngineMetrics interface {
RecordSequencingError()
CountSequencedTxs(count int)
RecordSequencerBuildingDiffTime(duration time.Duration)
RecordSequencerSealingTime(duration time.Duration)
}
// MeteredEngine wraps an EngineControl and adds metrics such as block building time diff and sealing time
type MeteredEngine struct {
inner derive.EngineControl
cfg *rollup.Config
metrics EngineMetrics
log log.Logger
buildingStartTime time.Time
}
// MeteredEngine implements derive.EngineControl
var _ derive.EngineControl = (*MeteredEngine)(nil)
func NewMeteredEngine(cfg *rollup.Config, inner derive.EngineControl, metrics EngineMetrics, log log.Logger) *MeteredEngine {
return &MeteredEngine{
inner: inner,
cfg: cfg,
metrics: metrics,
log: log,
}
}
func (m *MeteredEngine) Finalized() eth.L2BlockRef {
return m.inner.Finalized()
}
func (m *MeteredEngine) UnsafeL2Head() eth.L2BlockRef {
return m.inner.UnsafeL2Head()
}
func (m *MeteredEngine) SafeL2Head() eth.L2BlockRef {
return m.inner.SafeL2Head()
}
func (m *MeteredEngine) StartPayload(ctx context.Context, parent eth.L2BlockRef, attrs *eth.PayloadAttributes, updateSafe bool) (errType derive.BlockInsertionErrType, err error) {
m.buildingStartTime = time.Now()
errType, err = m.inner.StartPayload(ctx, parent, attrs, updateSafe)
if err != nil {
m.metrics.RecordSequencingError()
}
return errType, err
}
func (m *MeteredEngine) ConfirmPayload(ctx context.Context) (out *eth.ExecutionPayload, errTyp derive.BlockInsertionErrType, err error) {
sealingStart := time.Now()
// Actually execute the block and add it to the head of the chain.
payload, errType, err := m.inner.ConfirmPayload(ctx)
if err != nil {
m.metrics.RecordSequencingError()
return payload, errType, err
}
now := time.Now()
sealTime := now.Sub(sealingStart)
buildTime := now.Sub(m.buildingStartTime)
m.metrics.RecordSequencerSealingTime(sealTime)
m.metrics.RecordSequencerBuildingDiffTime(buildTime - time.Duration(m.cfg.BlockTime)*time.Second)
m.metrics.CountSequencedTxs(len(payload.Transactions))
ref := m.inner.UnsafeL2Head()
m.log.Debug("Processed new L2 block", "l2_unsafe", ref, "l1_origin", ref.L1Origin,
"txs", len(payload.Transactions), "time", ref.Time, "seal_time", sealTime, "build_time", buildTime)
return payload, errType, err
}
func (m *MeteredEngine) CancelPayload(ctx context.Context, force bool) error {
return m.inner.CancelPayload(ctx, force)
}
func (m *MeteredEngine) BuildingPayload() (onto eth.L2BlockRef, id eth.PayloadID, safe bool) {
return m.inner.BuildingPayload()
}
...@@ -2,7 +2,10 @@ package driver ...@@ -2,7 +2,10 @@ package driver
import ( import (
"context" "context"
"errors"
"fmt"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
...@@ -19,63 +22,49 @@ type L1OriginSelector struct { ...@@ -19,63 +22,49 @@ type L1OriginSelector struct {
log log.Logger log log.Logger
cfg *rollup.Config cfg *rollup.Config
l1 L1Blocks l1 L1Blocks
sequencingConfDepth uint64
} }
func NewL1OriginSelector(log log.Logger, cfg *rollup.Config, l1 L1Blocks, sequencingConfDepth uint64) *L1OriginSelector { func NewL1OriginSelector(log log.Logger, cfg *rollup.Config, l1 L1Blocks) *L1OriginSelector {
return &L1OriginSelector{ return &L1OriginSelector{
log: log, log: log,
cfg: cfg, cfg: cfg,
l1: l1, l1: l1,
sequencingConfDepth: sequencingConfDepth,
} }
} }
// FindL1Origin determines what the next L1 Origin should be. // FindL1Origin determines what the next L1 Origin should be.
// The L1 Origin is either the L2 Head's Origin, or the following L1 block // The L1 Origin is either the L2 Head's Origin, or the following L1 block
// if the next L2 block's time is greater than or equal to the L2 Head's Origin. // if the next L2 block's time is greater than or equal to the L2 Head's Origin.
func (los *L1OriginSelector) FindL1Origin(ctx context.Context, l1Head eth.L1BlockRef, l2Head eth.L2BlockRef) (eth.L1BlockRef, error) { func (los *L1OriginSelector) FindL1Origin(ctx context.Context, l2Head eth.L2BlockRef) (eth.L1BlockRef, error) {
// If we are at the head block, don't do a lookup. // Grab a reference to the current L1 origin block. This call is by hash and thus easily cached.
if l2Head.L1Origin.Hash == l1Head.Hash {
return l1Head, nil
}
// Grab a reference to the current L1 origin block.
currentOrigin, err := los.l1.L1BlockRefByHash(ctx, l2Head.L1Origin.Hash) currentOrigin, err := los.l1.L1BlockRefByHash(ctx, l2Head.L1Origin.Hash)
if err != nil { if err != nil {
return eth.L1BlockRef{}, err return eth.L1BlockRef{}, err
} }
log := los.log.New("current", currentOrigin, "current_time", currentOrigin.Time,
"l2_head", l2Head, "l2_head_time", l2Head.Time)
// If we are past the sequencer depth, we may want to advance the origin, but need to still // If we are past the sequencer depth, we may want to advance the origin, but need to still
// check the time of the next origin. // check the time of the next origin.
pastSeqDrift := l2Head.Time+los.cfg.BlockTime > currentOrigin.Time+los.cfg.MaxSequencerDrift pastSeqDrift := l2Head.Time+los.cfg.BlockTime > currentOrigin.Time+los.cfg.MaxSequencerDrift
if pastSeqDrift { if pastSeqDrift {
log.Info("Next L2 block time is past the sequencer drift + current origin time", log.Warn("Next L2 block time is past the sequencer drift + current origin time")
"current", currentOrigin, "current_time", currentOrigin.Time,
"l1_head", l1Head, "l1_head_time", l1Head.Time,
"l2_head", l2Head, "l2_head_time", l2Head.Time,
"depth", los.sequencingConfDepth)
}
if !pastSeqDrift && currentOrigin.Number+1+los.sequencingConfDepth > l1Head.Number {
// TODO: we can decide to ignore confirmation depth if we would be forced
// to make an empty block (only deposits) by staying on the current origin.
log.Info("sequencing with old origin to preserve conf depth",
"current", currentOrigin, "current_time", currentOrigin.Time,
"l1_head", l1Head, "l1_head_time", l1Head.Time,
"l2_head", l2Head, "l2_head_time", l2Head.Time,
"depth", los.sequencingConfDepth)
return currentOrigin, nil
} }
// Attempt to find the next L1 origin block, where the next origin is the immediate child of // Attempt to find the next L1 origin block, where the next origin is the immediate child of
// the current origin block. // the current origin block.
// The L1 source can be shimmed to hide new L1 blocks and enforce a sequencer confirmation distance.
nextOrigin, err := los.l1.L1BlockRefByNumber(ctx, currentOrigin.Number+1) nextOrigin, err := los.l1.L1BlockRefByNumber(ctx, currentOrigin.Number+1)
if err != nil { if err != nil {
// TODO: this could result in a bad origin being selected if we are past the seq if pastSeqDrift {
// drift & should instead advance to the next origin. return eth.L1BlockRef{}, fmt.Errorf("cannot build next L2 block past current L1 origin %s by more than sequencer time drift, and failed to find next L1 origin: %w", currentOrigin, err)
log.Error("Failed to get next origin. Falling back to current origin", "err", err) }
if errors.Is(err, ethereum.NotFound) {
log.Debug("No next L1 block found, repeating current origin")
} else {
log.Error("Failed to get next origin. Falling back to current origin", "err", err)
}
return currentOrigin, nil return currentOrigin, nil
} }
......
...@@ -27,6 +27,7 @@ func TestOriginSelectorAdvances(t *testing.T) { ...@@ -27,6 +27,7 @@ func TestOriginSelectorAdvances(t *testing.T) {
BlockTime: 2, BlockTime: 2,
} }
l1 := &testutils.MockL1Source{} l1 := &testutils.MockL1Source{}
defer l1.AssertExpectations(t)
a := eth.L1BlockRef{ a := eth.L1BlockRef{
Hash: common.Hash{'a'}, Hash: common.Hash{'a'},
Number: 10, Number: 10,
...@@ -46,9 +47,8 @@ func TestOriginSelectorAdvances(t *testing.T) { ...@@ -46,9 +47,8 @@ func TestOriginSelectorAdvances(t *testing.T) {
l1.ExpectL1BlockRefByHash(a.Hash, a, nil) l1.ExpectL1BlockRefByHash(a.Hash, a, nil)
l1.ExpectL1BlockRefByNumber(b.Number, b, nil) l1.ExpectL1BlockRefByNumber(b.Number, b, nil)
s := NewL1OriginSelector(log, cfg, l1, 0) s := NewL1OriginSelector(log, cfg, l1)
next, err := s.FindL1Origin(context.Background(), l2Head)
next, err := s.FindL1Origin(context.Background(), b, l2Head)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, b, next) require.Equal(t, b, next)
} }
...@@ -68,6 +68,7 @@ func TestOriginSelectorRespectsOriginTiming(t *testing.T) { ...@@ -68,6 +68,7 @@ func TestOriginSelectorRespectsOriginTiming(t *testing.T) {
BlockTime: 2, BlockTime: 2,
} }
l1 := &testutils.MockL1Source{} l1 := &testutils.MockL1Source{}
defer l1.AssertExpectations(t)
a := eth.L1BlockRef{ a := eth.L1BlockRef{
Hash: common.Hash{'a'}, Hash: common.Hash{'a'},
Number: 10, Number: 10,
...@@ -87,15 +88,14 @@ func TestOriginSelectorRespectsOriginTiming(t *testing.T) { ...@@ -87,15 +88,14 @@ func TestOriginSelectorRespectsOriginTiming(t *testing.T) {
l1.ExpectL1BlockRefByHash(a.Hash, a, nil) l1.ExpectL1BlockRefByHash(a.Hash, a, nil)
l1.ExpectL1BlockRefByNumber(b.Number, b, nil) l1.ExpectL1BlockRefByNumber(b.Number, b, nil)
s := NewL1OriginSelector(log, cfg, l1, 0) s := NewL1OriginSelector(log, cfg, l1)
next, err := s.FindL1Origin(context.Background(), l2Head)
next, err := s.FindL1Origin(context.Background(), b, l2Head)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, a, next) require.Equal(t, a, next)
} }
// TestOriginSelectorRespectsConfDepth ensures that the origin selector // TestOriginSelectorRespectsConfDepth ensures that the origin selector
// will respects the confirmation depth requirement // will respect the confirmation depth requirement
// //
// There are 2 L1 blocks at time 20 & 25. The L2 Head is at time 27. // There are 2 L1 blocks at time 20 & 25. The L2 Head is at time 27.
// The next L2 time is 29 which enough to normally select block `b` // The next L2 time is 29 which enough to normally select block `b`
...@@ -108,6 +108,7 @@ func TestOriginSelectorRespectsConfDepth(t *testing.T) { ...@@ -108,6 +108,7 @@ func TestOriginSelectorRespectsConfDepth(t *testing.T) {
BlockTime: 2, BlockTime: 2,
} }
l1 := &testutils.MockL1Source{} l1 := &testutils.MockL1Source{}
defer l1.AssertExpectations(t)
a := eth.L1BlockRef{ a := eth.L1BlockRef{
Hash: common.Hash{'a'}, Hash: common.Hash{'a'},
Number: 10, Number: 10,
...@@ -125,33 +126,32 @@ func TestOriginSelectorRespectsConfDepth(t *testing.T) { ...@@ -125,33 +126,32 @@ func TestOriginSelectorRespectsConfDepth(t *testing.T) {
} }
l1.ExpectL1BlockRefByHash(a.Hash, a, nil) l1.ExpectL1BlockRefByHash(a.Hash, a, nil)
l1.ExpectL1BlockRefByNumber(b.Number, b, nil) confDepthL1 := NewConfDepth(10, func() eth.L1BlockRef { return b }, l1)
s := NewL1OriginSelector(log, cfg, confDepthL1)
s := NewL1OriginSelector(log, cfg, l1, 10)
next, err := s.FindL1Origin(context.Background(), b, l2Head) next, err := s.FindL1Origin(context.Background(), l2Head)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, a, next) require.Equal(t, a, next)
} }
// TestOriginSelectorRespectsMaxSeqDrift ensures that the origin selector // TestOriginSelectorStrictConfDepth ensures that the origin selector will maintain the sequencer conf depth,
// will advance if the time delta between the current L1 origin and the next // even while the time delta between the current L1 origin and the next
// L2 block is greater than the sequencer drift. This needs to occur even // L2 block is greater than the sequencer drift.
// if conf depth needs to be ignored // It's more important to maintain safety with an empty block than to maintain liveness with poor conf depth.
// //
// There are 2 L1 blocks at time 20 & 25. The L2 Head is at time 27. // There are 2 L1 blocks at time 20 & 25. The L2 Head is at time 27.
// The next L2 time is 29. The sequencer drift is 8 so the L2 head is // The next L2 time is 29. The sequencer drift is 8 so the L2 head is
// valid with origin `a`, but the next L2 block is not valid with origin `b.` // valid with origin `a`, but the next L2 block is not valid with origin `b.`
// This is because 29 (next L2 time) > 20 (origin) + 8 (seq drift) => invalid block. // This is because 29 (next L2 time) > 20 (origin) + 8 (seq drift) => invalid block.
// Even though the LOS would normally refuse to advance because block `b` does not // We maintain confirmation distance, even though we would shift to the next origin if we could.
// have enough confirmations, it should in this instance. func TestOriginSelectorStrictConfDepth(t *testing.T) {
func TestOriginSelectorRespectsMaxSeqDrift(t *testing.T) {
log := testlog.Logger(t, log.LvlCrit) log := testlog.Logger(t, log.LvlCrit)
cfg := &rollup.Config{ cfg := &rollup.Config{
MaxSequencerDrift: 8, MaxSequencerDrift: 8,
BlockTime: 2, BlockTime: 2,
} }
l1 := &testutils.MockL1Source{} l1 := &testutils.MockL1Source{}
defer l1.AssertExpectations(t)
a := eth.L1BlockRef{ a := eth.L1BlockRef{
Hash: common.Hash{'a'}, Hash: common.Hash{'a'},
Number: 10, Number: 10,
...@@ -169,13 +169,11 @@ func TestOriginSelectorRespectsMaxSeqDrift(t *testing.T) { ...@@ -169,13 +169,11 @@ func TestOriginSelectorRespectsMaxSeqDrift(t *testing.T) {
} }
l1.ExpectL1BlockRefByHash(a.Hash, a, nil) l1.ExpectL1BlockRefByHash(a.Hash, a, nil)
l1.ExpectL1BlockRefByNumber(b.Number, b, nil) confDepthL1 := NewConfDepth(10, func() eth.L1BlockRef { return b }, l1)
s := NewL1OriginSelector(log, cfg, confDepthL1)
s := NewL1OriginSelector(log, cfg, l1, 10)
next, err := s.FindL1Origin(context.Background(), b, l2Head) _, err := s.FindL1Origin(context.Background(), l2Head)
require.Nil(t, err) require.ErrorContains(t, err, "sequencer time drift")
require.Equal(t, b, next)
} }
// TestOriginSelectorSeqDriftRespectsNextOriginTime // TestOriginSelectorSeqDriftRespectsNextOriginTime
...@@ -191,6 +189,7 @@ func TestOriginSelectorSeqDriftRespectsNextOriginTime(t *testing.T) { ...@@ -191,6 +189,7 @@ func TestOriginSelectorSeqDriftRespectsNextOriginTime(t *testing.T) {
BlockTime: 2, BlockTime: 2,
} }
l1 := &testutils.MockL1Source{} l1 := &testutils.MockL1Source{}
defer l1.AssertExpectations(t)
a := eth.L1BlockRef{ a := eth.L1BlockRef{
Hash: common.Hash{'a'}, Hash: common.Hash{'a'},
Number: 10, Number: 10,
...@@ -210,9 +209,76 @@ func TestOriginSelectorSeqDriftRespectsNextOriginTime(t *testing.T) { ...@@ -210,9 +209,76 @@ func TestOriginSelectorSeqDriftRespectsNextOriginTime(t *testing.T) {
l1.ExpectL1BlockRefByHash(a.Hash, a, nil) l1.ExpectL1BlockRefByHash(a.Hash, a, nil)
l1.ExpectL1BlockRefByNumber(b.Number, b, nil) l1.ExpectL1BlockRefByNumber(b.Number, b, nil)
s := NewL1OriginSelector(log, cfg, l1, 10) s := NewL1OriginSelector(log, cfg, l1)
next, err := s.FindL1Origin(context.Background(), l2Head)
next, err := s.FindL1Origin(context.Background(), b, l2Head)
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, a, next) require.Equal(t, a, next)
} }
// TestOriginSelectorHandlesLateL1Blocks tests the forced repeat of the previous origin,
// but with a conf depth that first prevents it from learning about the need to repeat.
//
// There are 2 L1 blocks at time 20 & 100. The L2 Head is at time 27.
// The next L2 time is 29. Even though the next L2 time is past the seq
// drift, the origin should remain on block `a` because the next origin's
// time is greater than the next L2 time.
// Due to a conf depth of 2, block `b` is not immediately visible,
// and the origin selection should fail until it is visible, by waiting for block `c`.
func TestOriginSelectorHandlesLateL1Blocks(t *testing.T) {
log := testlog.Logger(t, log.LvlCrit)
cfg := &rollup.Config{
MaxSequencerDrift: 8,
BlockTime: 2,
}
l1 := &testutils.MockL1Source{}
defer l1.AssertExpectations(t)
a := eth.L1BlockRef{
Hash: common.Hash{'a'},
Number: 10,
Time: 20,
}
b := eth.L1BlockRef{
Hash: common.Hash{'b'},
Number: 11,
Time: 100,
ParentHash: a.Hash,
}
c := eth.L1BlockRef{
Hash: common.Hash{'c'},
Number: 12,
Time: 150,
ParentHash: b.Hash,
}
d := eth.L1BlockRef{
Hash: common.Hash{'d'},
Number: 13,
Time: 200,
ParentHash: c.Hash,
}
l2Head := eth.L2BlockRef{
L1Origin: a.ID(),
Time: 27,
}
// l2 head does not change, so we start at the same origin again and again until we meet the conf depth
l1.ExpectL1BlockRefByHash(a.Hash, a, nil)
l1.ExpectL1BlockRefByHash(a.Hash, a, nil)
l1.ExpectL1BlockRefByHash(a.Hash, a, nil)
l1.ExpectL1BlockRefByNumber(b.Number, b, nil)
l1Head := b
confDepthL1 := NewConfDepth(2, func() eth.L1BlockRef { return l1Head }, l1)
s := NewL1OriginSelector(log, cfg, confDepthL1)
_, err := s.FindL1Origin(context.Background(), l2Head)
require.ErrorContains(t, err, "sequencer time drift")
l1Head = c
_, err = s.FindL1Origin(context.Background(), l2Head)
require.ErrorContains(t, err, "sequencer time drift")
l1Head = d
next, err := s.FindL1Origin(context.Background(), l2Head)
require.Nil(t, err)
require.Equal(t, a, next, "must stay on a because the L1 time may not be higher than the L2 time")
}
...@@ -2,7 +2,6 @@ package driver ...@@ -2,7 +2,6 @@ package driver
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"time" "time"
...@@ -20,16 +19,8 @@ type Downloader interface { ...@@ -20,16 +19,8 @@ type Downloader interface {
FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error) FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error)
} }
type SequencerMetrics interface { type L1OriginSelectorIface interface {
RecordSequencerBuildingDiffTime(duration time.Duration) FindL1Origin(ctx context.Context, l2Head eth.L2BlockRef) (eth.L1BlockRef, error)
RecordSequencerSealingTime(duration time.Duration)
}
type EngineState interface {
Finalized() eth.L2BlockRef
UnsafeL2Head() eth.L2BlockRef
SafeL2Head() eth.L2BlockRef
Origin() eth.L1BlockRef
} }
// Sequencer implements the sequencing interface of the driver: it starts and completes block building jobs. // Sequencer implements the sequencing interface of the driver: it starts and completes block building jobs.
...@@ -37,40 +28,44 @@ type Sequencer struct { ...@@ -37,40 +28,44 @@ type Sequencer struct {
log log.Logger log log.Logger
config *rollup.Config config *rollup.Config
l2 derive.Engine engine derive.EngineControl
engineState EngineState
attrBuilder derive.AttributesBuilder attrBuilder derive.AttributesBuilder
buildingOnto eth.L2BlockRef l1OriginSelector L1OriginSelectorIface
buildingID eth.PayloadID
buildingStartTime time.Time
metrics SequencerMetrics // timeNow enables sequencer testing to mock the time
timeNow func() time.Time
nextAction time.Time
} }
func NewSequencer(log log.Logger, cfg *rollup.Config, l2 derive.Engine, engineState EngineState, attributesBuilder derive.AttributesBuilder, metrics SequencerMetrics) *Sequencer { func NewSequencer(log log.Logger, cfg *rollup.Config, engine derive.EngineControl, attributesBuilder derive.AttributesBuilder, l1OriginSelector L1OriginSelectorIface) *Sequencer {
return &Sequencer{ return &Sequencer{
log: log, log: log,
config: cfg, config: cfg,
l2: l2, engine: engine,
metrics: metrics, timeNow: time.Now,
engineState: engineState, attrBuilder: attributesBuilder,
attrBuilder: attributesBuilder, l1OriginSelector: l1OriginSelector,
} }
} }
// StartBuildingBlock initiates a block building job on top of the given L2 head, safe and finalized blocks, and using the provided l1Origin. // StartBuildingBlock initiates a block building job on top of the given L2 head, safe and finalized blocks, and using the provided l1Origin.
func (d *Sequencer) StartBuildingBlock(ctx context.Context, l1Origin eth.L1BlockRef) error { func (d *Sequencer) StartBuildingBlock(ctx context.Context) error {
l2Head := d.engineState.UnsafeL2Head() l2Head := d.engine.UnsafeL2Head()
// Figure out which L1 origin block we're going to be building on top of.
l1Origin, err := d.l1OriginSelector.FindL1Origin(ctx, l2Head)
if err != nil {
d.log.Error("Error finding next L1 Origin", "err", err)
return err
}
if !(l2Head.L1Origin.Hash == l1Origin.ParentHash || l2Head.L1Origin.Hash == l1Origin.Hash) { if !(l2Head.L1Origin.Hash == l1Origin.ParentHash || l2Head.L1Origin.Hash == l1Origin.Hash) {
return fmt.Errorf("cannot build new L2 block with L1 origin %s (parent L1 %s) on current L2 head %s with L1 origin %s", l1Origin, l1Origin.ParentHash, l2Head, l2Head.L1Origin) return fmt.Errorf("cannot build new L2 block with L1 origin %s (parent L1 %s) on current L2 head %s with L1 origin %s", l1Origin, l1Origin.ParentHash, l2Head, l2Head.L1Origin)
} }
d.log.Info("creating new block", "parent", l2Head, "l1Origin", l1Origin) d.log.Info("creating new block", "parent", l2Head, "l1Origin", l1Origin)
if d.buildingID != (eth.PayloadID{}) { // This may happen when we decide to build a different block in response to a reorg. Or when previous block building failed.
d.log.Warn("did not finish previous block building, starting new building now", "prev_onto", d.buildingOnto, "prev_payload_id", d.buildingID, "new_onto", l2Head)
}
d.buildingStartTime = time.Now()
fetchCtx, cancel := context.WithTimeout(ctx, time.Second*20) fetchCtx, cancel := context.WithTimeout(ctx, time.Second*20)
defer cancel() defer cancel()
...@@ -90,20 +85,11 @@ func (d *Sequencer) StartBuildingBlock(ctx context.Context, l1Origin eth.L1Block ...@@ -90,20 +85,11 @@ func (d *Sequencer) StartBuildingBlock(ctx context.Context, l1Origin eth.L1Block
"num", l2Head.Number+1, "time", uint64(attrs.Timestamp), "num", l2Head.Number+1, "time", uint64(attrs.Timestamp),
"origin", l1Origin, "origin_time", l1Origin.Time, "noTxPool", attrs.NoTxPool) "origin", l1Origin, "origin_time", l1Origin.Time, "noTxPool", attrs.NoTxPool)
// And construct our fork choice state. This is our current fork choice state and will be
// updated as a result of executing the block based on the attributes described above.
fc := eth.ForkchoiceState{
HeadBlockHash: l2Head.Hash,
SafeBlockHash: d.engineState.SafeL2Head().Hash,
FinalizedBlockHash: d.engineState.Finalized().Hash,
}
// Start a payload building process. // Start a payload building process.
id, errTyp, err := derive.StartPayload(ctx, d.l2, fc, attrs) errTyp, err := d.engine.StartPayload(ctx, l2Head, attrs, false)
if err != nil { if err != nil {
return fmt.Errorf("failed to start building on top of L2 chain %s, error (%d): %w", l2Head, errTyp, err) return fmt.Errorf("failed to start building on top of L2 chain %s, error (%d): %w", l2Head, errTyp, err)
} }
d.buildingOnto = l2Head
d.buildingID = id
return nil return nil
} }
...@@ -111,75 +97,90 @@ func (d *Sequencer) StartBuildingBlock(ctx context.Context, l1Origin eth.L1Block ...@@ -111,75 +97,90 @@ func (d *Sequencer) StartBuildingBlock(ctx context.Context, l1Origin eth.L1Block
// Warning: the safe and finalized L2 blocks as viewed during the initiation of the block building are reused for completion of the block building. // Warning: the safe and finalized L2 blocks as viewed during the initiation of the block building are reused for completion of the block building.
// The Execution engine should not change the safe and finalized blocks between start and completion of block building. // The Execution engine should not change the safe and finalized blocks between start and completion of block building.
func (d *Sequencer) CompleteBuildingBlock(ctx context.Context) (*eth.ExecutionPayload, error) { func (d *Sequencer) CompleteBuildingBlock(ctx context.Context) (*eth.ExecutionPayload, error) {
if d.buildingID == (eth.PayloadID{}) { payload, errTyp, err := d.engine.ConfirmPayload(ctx)
return nil, fmt.Errorf("cannot complete payload building: not currently building a payload")
}
sealingStart := time.Now()
l2Head := d.engineState.UnsafeL2Head()
if d.buildingOnto.Hash != l2Head.Hash {
return nil, fmt.Errorf("engine reorged from %s to %s while building block", d.buildingOnto, l2Head)
}
fc := eth.ForkchoiceState{
HeadBlockHash: l2Head.Hash,
SafeBlockHash: d.engineState.SafeL2Head().Hash,
FinalizedBlockHash: d.engineState.Finalized().Hash,
}
// Actually execute the block and add it to the head of the chain.
payload, errTyp, err := derive.ConfirmPayload(ctx, d.log, d.l2, fc, d.buildingID, false)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to complete building on top of L2 chain %s, id: %s, error (%d): %w", d.buildingOnto, d.buildingID, errTyp, err) return nil, fmt.Errorf("failed to complete building block: error (%d): %w", errTyp, err)
} }
now := time.Now()
sealTime := now.Sub(sealingStart)
buildTime := now.Sub(d.buildingStartTime)
d.metrics.RecordSequencerSealingTime(sealTime)
d.metrics.RecordSequencerBuildingDiffTime(buildTime - time.Duration(d.config.BlockTime)*time.Second)
d.log.Debug("sequenced block", "seal_time", sealTime, "build_time", buildTime)
d.buildingID = eth.PayloadID{}
return payload, nil return payload, nil
} }
// PlanNextSequencerAction returns a desired delay till the next action, and if we should seal the block: // CancelBuildingBlock cancels the current open block building job.
// - true whenever we need to complete a block // This sequencer only maintains one block building job at a time.
// - false whenever we need to start a block func (d *Sequencer) CancelBuildingBlock(ctx context.Context) {
func (d *Sequencer) PlanNextSequencerAction(sequenceErr error) (delay time.Duration, seal bool, onto eth.BlockID) { // force-cancel, we can always continue block building, and any error is logged by the engine state
blockTime := time.Duration(d.config.BlockTime) * time.Second _ = d.engine.CancelPayload(ctx, true)
head := d.engineState.UnsafeL2Head() }
// based on the build error, delay and start over again // PlanNextSequencerAction returns a desired delay till the RunNextSequencerAction call.
if sequenceErr != nil { func (d *Sequencer) PlanNextSequencerAction() time.Duration {
if errors.Is(sequenceErr, UninitializedL1StateErr) { head := d.engine.UnsafeL2Head()
// temporary errors are not so bad, just retry in 500ms now := d.timeNow()
return 500 * time.Millisecond, false, head.ID()
} else { buildingOnto, buildingID, _ := d.engine.BuildingPayload()
// we just hit an unknown type of error, delay a re-attempt by as much as a block
return blockTime, false, head.ID() // We may have to wait till the next sequencing action, e.g. upon an error.
} // If the head changed we need to respond and will not delay the sequencing.
if delay := d.nextAction.Sub(now); delay > 0 && buildingOnto.Hash == head.Hash {
return delay
} }
blockTime := time.Duration(d.config.BlockTime) * time.Second
payloadTime := time.Unix(int64(head.Time+d.config.BlockTime), 0) payloadTime := time.Unix(int64(head.Time+d.config.BlockTime), 0)
remainingTime := time.Until(payloadTime) remainingTime := payloadTime.Sub(now)
// If we started building a block already, and if that work is still consistent, // If we started building a block already, and if that work is still consistent,
// then we would like to finish it by sealing the block. // then we would like to finish it by sealing the block.
if d.buildingID != (eth.PayloadID{}) && d.buildingOnto.Hash == head.Hash { if buildingID != (eth.PayloadID{}) && buildingOnto.Hash == head.Hash {
// if we started building already, then we will schedule the sealing. // if we started building already, then we will schedule the sealing.
if remainingTime < sealingDuration { if remainingTime < sealingDuration {
return 0, true, head.ID() // if there's not enough time for sealing, don't wait. return 0 // if there's not enough time for sealing, don't wait.
} else { } else {
// finish with margin of sealing duration before payloadTime // finish with margin of sealing duration before payloadTime
return remainingTime - sealingDuration, true, head.ID() return remainingTime - sealingDuration
} }
} else { } else {
// if we did not yet start building, then we will schedule the start. // if we did not yet start building, then we will schedule the start.
if remainingTime > blockTime { if remainingTime > blockTime {
// if we have too much time, then wait before starting the build // if we have too much time, then wait before starting the build
return remainingTime - blockTime, false, head.ID() return remainingTime - blockTime
} else { } else {
// otherwise start instantly // otherwise start instantly
return 0, false, head.ID() return 0
}
}
}
// BuildingOnto returns the L2 head reference that the latest block is or was being built on top of.
func (d *Sequencer) BuildingOnto() eth.L2BlockRef {
ref, _, _ := d.engine.BuildingPayload()
return ref
}
// RunNextSequencerAction starts new block building work, or seals existing work,
// and is best timed by first awaiting the delay returned by PlanNextSequencerAction.
// If a new block is successfully sealed, it will be returned for publishing, nil otherwise.
func (d *Sequencer) RunNextSequencerAction(ctx context.Context) *eth.ExecutionPayload {
if _, buildingID, _ := d.engine.BuildingPayload(); buildingID != (eth.PayloadID{}) {
payload, err := d.CompleteBuildingBlock(ctx)
if err != nil {
d.log.Error("sequencer failed to seal new block", "err", err)
d.nextAction = d.timeNow().Add(time.Second)
if buildingID != (eth.PayloadID{}) { // don't keep stale block building jobs around, try to cancel them
d.CancelBuildingBlock(ctx)
}
return nil
} else {
d.log.Info("sequencer successfully built a new block", "block", payload.ID(), "time", uint64(payload.Timestamp), "txs", len(payload.Transactions))
return payload
}
} else {
err := d.StartBuildingBlock(ctx)
if err != nil {
d.log.Error("sequencer failed to start building new block", "err", err)
d.nextAction = d.timeNow().Add(time.Second)
} else {
d.log.Info("sequencer started building new block", "payload_id", buildingID)
} }
return nil
} }
} }
package driver
import (
"context"
crand "crypto/rand"
"encoding/binary"
"errors"
"fmt"
"math/big"
"math/rand"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-node/testutils"
)
type FakeEngineControl struct {
finalized eth.L2BlockRef
safe eth.L2BlockRef
unsafe eth.L2BlockRef
buildingOnto eth.L2BlockRef
buildingID eth.PayloadID
buildingSafe bool
buildingAttrs *eth.PayloadAttributes
buildingStart time.Time
cfg *rollup.Config
timeNow func() time.Time
makePayload func(onto eth.L2BlockRef, attrs *eth.PayloadAttributes) *eth.ExecutionPayload
errTyp derive.BlockInsertionErrType
err error
totalBuildingTime time.Duration
totalBuiltBlocks int
totalTxs int
}
func (m *FakeEngineControl) avgBuildingTime() time.Duration {
return m.totalBuildingTime / time.Duration(m.totalBuiltBlocks)
}
func (m *FakeEngineControl) avgTxsPerBlock() float64 {
return float64(m.totalTxs) / float64(m.totalBuiltBlocks)
}
func (m *FakeEngineControl) StartPayload(ctx context.Context, parent eth.L2BlockRef, attrs *eth.PayloadAttributes, updateSafe bool) (errType derive.BlockInsertionErrType, err error) {
if m.err != nil {
return m.errTyp, m.err
}
m.buildingID = eth.PayloadID{}
_, _ = crand.Read(m.buildingID[:])
m.buildingOnto = parent
m.buildingSafe = updateSafe
m.buildingAttrs = attrs
m.buildingStart = m.timeNow()
return derive.BlockInsertOK, nil
}
func (m *FakeEngineControl) ConfirmPayload(ctx context.Context) (out *eth.ExecutionPayload, errTyp derive.BlockInsertionErrType, err error) {
if m.err != nil {
return nil, m.errTyp, m.err
}
buildTime := m.timeNow().Sub(m.buildingStart)
m.totalBuildingTime += buildTime
m.totalBuiltBlocks += 1
payload := m.makePayload(m.buildingOnto, m.buildingAttrs)
ref, err := derive.PayloadToBlockRef(payload, &m.cfg.Genesis)
if err != nil {
panic(err)
}
m.unsafe = ref
if m.buildingSafe {
m.safe = ref
}
m.resetBuildingState()
m.totalTxs += len(payload.Transactions)
return payload, derive.BlockInsertOK, nil
}
func (m *FakeEngineControl) CancelPayload(ctx context.Context, force bool) error {
if force {
m.resetBuildingState()
}
return m.err
}
func (m *FakeEngineControl) BuildingPayload() (onto eth.L2BlockRef, id eth.PayloadID, safe bool) {
return m.buildingOnto, m.buildingID, m.buildingSafe
}
func (m *FakeEngineControl) Finalized() eth.L2BlockRef {
return m.finalized
}
func (m *FakeEngineControl) UnsafeL2Head() eth.L2BlockRef {
return m.unsafe
}
func (m *FakeEngineControl) SafeL2Head() eth.L2BlockRef {
return m.safe
}
func (m *FakeEngineControl) resetBuildingState() {
m.buildingID = eth.PayloadID{}
m.buildingOnto = eth.L2BlockRef{}
m.buildingSafe = false
m.buildingAttrs = nil
}
var _ derive.EngineControl = (*FakeEngineControl)(nil)
type testAttrBuilderFn func(ctx context.Context, l2Parent eth.L2BlockRef, epoch eth.BlockID) (attrs *eth.PayloadAttributes, err error)
func (fn testAttrBuilderFn) PreparePayloadAttributes(ctx context.Context, l2Parent eth.L2BlockRef, epoch eth.BlockID) (attrs *eth.PayloadAttributes, err error) {
return fn(ctx, l2Parent, epoch)
}
var _ derive.AttributesBuilder = (testAttrBuilderFn)(nil)
type testOriginSelectorFn func(ctx context.Context, l2Head eth.L2BlockRef) (eth.L1BlockRef, error)
func (fn testOriginSelectorFn) FindL1Origin(ctx context.Context, l2Head eth.L2BlockRef) (eth.L1BlockRef, error) {
return fn(ctx, l2Head)
}
var _ L1OriginSelectorIface = (testOriginSelectorFn)(nil)
// TestSequencerChaosMonkey runs the sequencer in a mocked adversarial environment with
// repeated random errors in dependencies and poor clock timing.
// At the end the health of the chain is checked to show that the sequencer kept the chain in shape.
func TestSequencerChaosMonkey(t *testing.T) {
mockL1Hash := func(num uint64) (out common.Hash) {
out[31] = 1
binary.BigEndian.PutUint64(out[:], num)
return
}
mockL2Hash := func(num uint64) (out common.Hash) {
out[31] = 2
binary.BigEndian.PutUint64(out[:], num)
return
}
mockL1ID := func(num uint64) eth.BlockID {
return eth.BlockID{Hash: mockL1Hash(num), Number: num}
}
mockL2ID := func(num uint64) eth.BlockID {
return eth.BlockID{Hash: mockL2Hash(num), Number: num}
}
rng := rand.New(rand.NewSource(12345))
l1Time := uint64(100000)
// mute errors. We expect a lot of the mocked errors to cause error-logs. We check chain health at the end of the test.
log := testlog.Logger(t, log.LvlCrit)
cfg := &rollup.Config{
Genesis: rollup.Genesis{
L1: mockL1ID(100000),
L2: mockL2ID(200000),
L2Time: l1Time + 300, // L2 may start with a relative old L1 origin and will have to catch it up
SystemConfig: eth.SystemConfig{},
},
BlockTime: 2,
MaxSequencerDrift: 30,
}
// keep track of the L1 timestamps we mock because sometimes we only have the L1 hash/num handy
l1Times := map[eth.BlockID]uint64{cfg.Genesis.L1: l1Time}
genesisL2 := eth.L2BlockRef{
Hash: cfg.Genesis.L2.Hash,
Number: cfg.Genesis.L2.Number,
ParentHash: mockL2Hash(cfg.Genesis.L2.Number - 1),
Time: cfg.Genesis.L2Time,
L1Origin: cfg.Genesis.L1,
SequenceNumber: 0,
}
// initialize our engine state
engControl := &FakeEngineControl{
finalized: genesisL2,
safe: genesisL2,
unsafe: genesisL2,
cfg: cfg,
}
// start wallclock at 5 minutes after the current L2 head. The sequencer has some catching up to do!
clockTime := time.Unix(int64(engControl.unsafe.Time)+5*60, 0)
clockFn := func() time.Time {
return clockTime
}
engControl.timeNow = clockFn
// mock payload building, we don't need to process any real txs.
engControl.makePayload = func(onto eth.L2BlockRef, attrs *eth.PayloadAttributes) *eth.ExecutionPayload {
txs := make([]eth.Data, 0)
txs = append(txs, attrs.Transactions...) // include deposits
if !attrs.NoTxPool { // if we are allowed to sequence from tx pool, mock some txs
n := rng.Intn(20)
for i := 0; i < n; i++ {
txs = append(txs, []byte(fmt.Sprintf("mock sequenced tx %d", i)))
}
}
return &eth.ExecutionPayload{
ParentHash: onto.Hash,
BlockNumber: eth.Uint64Quantity(onto.Number) + 1,
Timestamp: attrs.Timestamp,
BlockHash: mockL2Hash(onto.Number),
Transactions: txs,
}
}
// We keep attribute building simple, we don't talk to a real execution engine in this test.
// Sometimes we fake an error in the attributes preparation.
var attrsErr error
attrBuilder := testAttrBuilderFn(func(ctx context.Context, l2Parent eth.L2BlockRef, epoch eth.BlockID) (attrs *eth.PayloadAttributes, err error) {
if attrsErr != nil {
return nil, attrsErr
}
seqNr := l2Parent.SequenceNumber + 1
if epoch != l2Parent.L1Origin {
seqNr = 0
}
l1Info := &testutils.MockBlockInfo{
InfoHash: epoch.Hash,
InfoParentHash: mockL1Hash(epoch.Number - 1),
InfoCoinbase: common.Address{},
InfoRoot: common.Hash{},
InfoNum: epoch.Number,
InfoTime: l1Times[epoch],
InfoMixDigest: [32]byte{},
InfoBaseFee: big.NewInt(1234),
InfoReceiptRoot: common.Hash{},
}
infoDep, err := derive.L1InfoDepositBytes(seqNr, l1Info, cfg.Genesis.SystemConfig)
require.NoError(t, err)
testGasLimit := eth.Uint64Quantity(10_000_000)
return &eth.PayloadAttributes{
Timestamp: eth.Uint64Quantity(l2Parent.Time + cfg.BlockTime),
PrevRandao: eth.Bytes32{},
SuggestedFeeRecipient: common.Address{},
Transactions: []eth.Data{infoDep},
NoTxPool: false,
GasLimit: &testGasLimit,
}, nil
})
maxL1BlockTimeGap := uint64(100)
// The origin selector just generates random L1 blocks based on RNG
var originErr error
originSelector := testOriginSelectorFn(func(ctx context.Context, l2Head eth.L2BlockRef) (eth.L1BlockRef, error) {
if originErr != nil {
return eth.L1BlockRef{}, originErr
}
origin := eth.L1BlockRef{
Hash: mockL1Hash(l2Head.L1Origin.Number),
Number: l2Head.L1Origin.Number,
ParentHash: mockL1Hash(l2Head.L1Origin.Number),
Time: l1Times[l2Head.L1Origin],
}
// randomly make a L1 origin appear, if we can even select it
nextL2Time := l2Head.Time + cfg.BlockTime
if nextL2Time <= origin.Time {
return origin, nil
}
maxTimeIncrement := nextL2Time - origin.Time
if maxTimeIncrement > maxL1BlockTimeGap {
maxTimeIncrement = maxL1BlockTimeGap
}
if rng.Intn(10) == 0 {
nextOrigin := eth.L1BlockRef{
Hash: mockL1Hash(origin.Number + 1),
Number: origin.Number + 1,
ParentHash: origin.Hash,
Time: origin.Time + 1 + uint64(rng.Int63n(int64(maxTimeIncrement))),
}
l1Times[nextOrigin.ID()] = nextOrigin.Time
return nextOrigin, nil
} else {
return origin, nil
}
})
seq := NewSequencer(log, cfg, engControl, attrBuilder, originSelector)
seq.timeNow = clockFn
// try to build 1000 blocks, with 5x as many planning attempts, to handle errors and clock problems
desiredBlocks := 1000
for i := 0; i < 5*desiredBlocks && engControl.totalBuiltBlocks < desiredBlocks; i++ {
delta := seq.PlanNextSequencerAction()
x := rng.Float32()
if x < 0.01 { // 1%: mess a lot with the clock: simulate a hang of up to 30 seconds
if i < desiredBlocks/2 { // only in first 50% of blocks to let it heal, hangs take time
delta = time.Duration(rng.Float64() * float64(time.Second*30))
}
} else if x < 0.1 { // 9%: mess with the timing, -50% to 50% off
delta = time.Duration((0.5 + rng.Float64()) * float64(delta))
} else if x < 0.5 {
// 40%: mess slightly with the timing, -10% to 10% off
delta = time.Duration((0.9 + rng.Float64()*0.2) * float64(delta))
}
clockTime = clockTime.Add(delta)
// reset errors
originErr = nil
attrsErr = nil
engControl.err = nil
engControl.errTyp = derive.BlockInsertOK
// maybe make something maybe fail, or try a new L1 origin
switch rng.Intn(10) { // 40% chance to fail sequencer action (!!!)
case 0:
originErr = errors.New("mock origin error")
case 1:
attrsErr = errors.New("mock attributes error")
case 2:
engControl.err = errors.New("mock temporary engine error")
engControl.errTyp = derive.BlockInsertTemporaryErr
case 3:
engControl.err = errors.New("mock prestate engine error")
engControl.errTyp = derive.BlockInsertPrestateErr
default:
// no error
}
payload := seq.RunNextSequencerAction(context.Background())
if payload != nil {
require.Equal(t, engControl.UnsafeL2Head().ID(), payload.ID(), "head must stay in sync with emitted payloads")
var tx types.Transaction
require.NoError(t, tx.UnmarshalBinary(payload.Transactions[0]))
info, err := derive.L1InfoDepositTxData(tx.Data())
require.NoError(t, err)
require.GreaterOrEqual(t, uint64(payload.Timestamp), info.Time, "ensure L2 time >= L1 time")
}
}
// Now, even though:
// - the start state was behind the wallclock
// - the L1 origin was far behind the L2
// - we made all components fail at random
// - messed with the clock
// the L2 chain was still built and stats are healthy on average!
l2Head := engControl.UnsafeL2Head()
t.Logf("avg build time: %s, clock timestamp: %d, L2 head time: %d, L1 origin time: %d, avg txs per block: %f", engControl.avgBuildingTime(), clockFn().Unix(), l2Head.Time, l1Times[l2Head.L1Origin], engControl.avgTxsPerBlock())
require.Equal(t, engControl.totalBuiltBlocks, desiredBlocks, "persist through random errors and build the desired blocks")
require.Equal(t, l2Head.Time, cfg.Genesis.L2Time+uint64(desiredBlocks)*cfg.BlockTime, "reached desired L2 block timestamp")
require.GreaterOrEqual(t, l2Head.Time, l1Times[l2Head.L1Origin], "the L2 time >= the L1 time")
require.Less(t, l2Head.Time-l1Times[l2Head.L1Origin], uint64(100), "The L1 origin time is close to the L2 time")
require.Less(t, clockTime.Sub(time.Unix(int64(l2Head.Time), 0)).Abs(), 2*time.Second, "L2 time is accurate, within 2 seconds of wallclock")
require.Greater(t, engControl.avgBuildingTime(), time.Second, "With 2 second block time and 1 second error backoff and healthy-on-average errors, building time should at least be a second")
require.Greater(t, engControl.avgTxsPerBlock(), 3.0, "We expect at least 1 system tx per block, but with a mocked 0-10 txs we expect an higher avg")
}
...@@ -25,8 +25,6 @@ type SyncStatus = eth.SyncStatus ...@@ -25,8 +25,6 @@ type SyncStatus = eth.SyncStatus
// sealingDuration defines the expected time it takes to seal the block // sealingDuration defines the expected time it takes to seal the block
const sealingDuration = time.Millisecond * 50 const sealingDuration = time.Millisecond * 50
var UninitializedL1StateErr = errors.New("the L1 Head in L1 State is not initialized yet")
type Driver struct { type Driver struct {
l1State L1StateIface l1State L1StateIface
...@@ -71,11 +69,10 @@ type Driver struct { ...@@ -71,11 +69,10 @@ type Driver struct {
// L2 Signals: // L2 Signals:
unsafeL2Payloads chan *eth.ExecutionPayload unsafeL2Payloads chan *eth.ExecutionPayload
l1 L1Chain l1 L1Chain
l2 L2Chain l2 L2Chain
l1OriginSelector L1OriginSelectorIface sequencer SequencerIface
sequencer SequencerIface network Network // may be nil, network for is optional
network Network // may be nil, network for is optional
metrics Metrics metrics Metrics
log log.Logger log log.Logger
...@@ -142,75 +139,6 @@ func (s *Driver) OnUnsafeL2Payload(ctx context.Context, payload *eth.ExecutionPa ...@@ -142,75 +139,6 @@ func (s *Driver) OnUnsafeL2Payload(ctx context.Context, payload *eth.ExecutionPa
} }
} }
// startNewL2Block starts sequencing a new L2 block on top of the unsafe L2 Head.
func (s *Driver) startNewL2Block(ctx context.Context) error {
l2Head := s.derivation.UnsafeL2Head()
l1Head := s.l1State.L1Head()
if l1Head == (eth.L1BlockRef{}) {
return UninitializedL1StateErr
}
// Figure out which L1 origin block we're going to be building on top of.
l1Origin, err := s.l1OriginSelector.FindL1Origin(ctx, l1Head, l2Head)
if err != nil {
s.log.Error("Error finding next L1 Origin", "err", err)
return err
}
// Rollup is configured to not start producing blocks until a specific L1 block has been
// reached. Don't produce any blocks until we're at that genesis block.
if l1Origin.Number < s.config.Genesis.L1.Number {
s.log.Info("Skipping block production because the next L1 Origin is behind the L1 genesis", "next", l1Origin.ID(), "genesis", s.config.Genesis.L1)
return fmt.Errorf("the L1 origin %s cannot be before genesis at %s", l1Origin, s.config.Genesis.L1)
}
// Should never happen. Sequencer will halt if we get into this situation somehow.
nextL2Time := l2Head.Time + s.config.BlockTime
if nextL2Time < l1Origin.Time {
s.log.Error("Cannot build L2 block for time before L1 origin",
"l2Unsafe", l2Head, "nextL2Time", nextL2Time, "l1Origin", l1Origin, "l1OriginTime", l1Origin.Time)
return fmt.Errorf("cannot build L2 block on top %s for time %d before L1 origin %s at time %d",
l2Head, nextL2Time, l1Origin, l1Origin.Time)
}
// Start creating the new block.
return s.sequencer.StartBuildingBlock(ctx, l1Origin)
}
// completeNewBlock completes a previously started L2 block sequencing job.
func (s *Driver) completeNewBlock(ctx context.Context) error {
payload, err := s.sequencer.CompleteBuildingBlock(ctx)
if err != nil {
s.metrics.RecordSequencingError()
s.log.Error("Failed to seal block as sequencer", "err", err)
return err
}
// Generate an L2 block ref from the payload.
newUnsafeL2Head, err := derive.PayloadToBlockRef(payload, &s.config.Genesis)
if err != nil {
s.metrics.RecordSequencingError()
s.log.Error("Sequenced payload cannot be transformed into valid L2 block reference", "err", err)
return fmt.Errorf("sequenced payload cannot be transformed into valid L2 block reference: %w", err)
}
// Update our L2 head block based on the new unsafe block we just generated.
s.derivation.SetUnsafeHead(newUnsafeL2Head)
s.log.Info("Sequenced new l2 block", "l2_unsafe", newUnsafeL2Head, "l1_origin", newUnsafeL2Head.L1Origin, "txs", len(payload.Transactions), "time", newUnsafeL2Head.Time)
s.metrics.CountSequencedTxs(len(payload.Transactions))
if s.network != nil {
if err := s.network.PublishL2Payload(ctx, payload); err != nil {
s.log.Warn("failed to publish newly created block", "id", payload.ID(), "err", err)
s.metrics.RecordPublishingError()
// publishing of unsafe data via p2p is optional. Errors are not severe enough to change/halt sequencing but should be logged and metered.
}
}
return nil
}
// the eventLoop responds to L1 changes and internal timers to produce L2 blocks. // the eventLoop responds to L1 changes and internal timers to produce L2 blocks.
func (s *Driver) eventLoop() { func (s *Driver) eventLoop() {
defer s.wg.Done() defer s.wg.Done()
...@@ -259,34 +187,23 @@ func (s *Driver) eventLoop() { ...@@ -259,34 +187,23 @@ func (s *Driver) eventLoop() {
// L1 chain that we need to handle. // L1 chain that we need to handle.
reqStep() reqStep()
blockTime := time.Duration(s.config.BlockTime) * time.Second
var sequenceErr error
var sequenceErrTime time.Time
sequencerTimer := time.NewTimer(0) sequencerTimer := time.NewTimer(0)
var sequencerCh <-chan time.Time var sequencerCh <-chan time.Time
var sequencingPlannedOnto eth.BlockID
var sequencerSealNext bool
planSequencerAction := func() { planSequencerAction := func() {
delay, seal, onto := s.sequencer.PlanNextSequencerAction(sequenceErr) delay := s.sequencer.PlanNextSequencerAction()
if sequenceErr != nil && time.Since(sequenceErrTime) > delay {
sequenceErr = nil
}
sequencerCh = sequencerTimer.C sequencerCh = sequencerTimer.C
if len(sequencerCh) > 0 { // empty if not already drained before resetting if len(sequencerCh) > 0 { // empty if not already drained before resetting
<-sequencerCh <-sequencerCh
} }
sequencerTimer.Reset(delay) sequencerTimer.Reset(delay)
sequencingPlannedOnto = onto
sequencerSealNext = seal
} }
for { for {
// If we are sequencing, update the trigger for the next sequencer action. // If we are sequencing, and the L1 state is ready, update the trigger for the next sequencer action.
// This may adjust at any time based on fork-choice changes or previous errors. // This may adjust at any time based on fork-choice changes or previous errors.
if s.driverConfig.SequencerEnabled && !s.driverConfig.SequencerStopped { if s.driverConfig.SequencerEnabled && !s.driverConfig.SequencerStopped && s.l1State.L1Head() != (eth.L1BlockRef{}) {
// update sequencer time if the head changed // update sequencer time if the head changed
if sequencingPlannedOnto != s.derivation.UnsafeL2Head().ID() { if s.sequencer.BuildingOnto().ID() != s.derivation.UnsafeL2Head().ID() {
planSequencerAction() planSequencerAction()
} }
} else { } else {
...@@ -295,22 +212,14 @@ func (s *Driver) eventLoop() { ...@@ -295,22 +212,14 @@ func (s *Driver) eventLoop() {
select { select {
case <-sequencerCh: case <-sequencerCh:
s.log.Info("sequencing now!", "seal", sequencerSealNext, "idle_derivation", s.idleDerivation) payload := s.sequencer.RunNextSequencerAction(ctx)
if sequencerSealNext { if s.network != nil && payload != nil {
// try to seal the current block task, and allow it to take up to 3 block times. // Publishing of unsafe data via p2p is optional.
// If this fails we will simply start a new block building job. // Errors are not severe enough to change/halt sequencing but should be logged and metered.
ctx, cancel := context.WithTimeout(ctx, 3*blockTime) if err := s.network.PublishL2Payload(ctx, payload); err != nil {
sequenceErr = s.completeNewBlock(ctx) s.log.Warn("failed to publish newly created block", "id", payload.ID(), "err", err)
cancel() s.metrics.RecordPublishingError()
} else { }
// Start the block building, don't allow the starting of sequencing to get stuck for more the time of 1 block.
ctx, cancel := context.WithTimeout(ctx, blockTime)
sequenceErr = s.startNewL2Block(ctx)
cancel()
}
if sequenceErr != nil {
s.log.Error("sequencing error", "err", sequenceErr)
sequenceErrTime = time.Now()
} }
planSequencerAction() // schedule the next sequencer action to keep the sequencing looping planSequencerAction() // schedule the next sequencer action to keep the sequencing looping
case payload := <-s.unsafeL2Payloads: case payload := <-s.unsafeL2Payloads:
...@@ -386,8 +295,8 @@ func (s *Driver) eventLoop() { ...@@ -386,8 +295,8 @@ func (s *Driver) eventLoop() {
} else { } else {
s.log.Info("Sequencer has been started") s.log.Info("Sequencer has been started")
s.driverConfig.SequencerStopped = false s.driverConfig.SequencerStopped = false
sequencingPlannedOnto = eth.BlockID{}
close(resp.err) close(resp.err)
planSequencerAction() // resume sequencing
} }
case respCh := <-s.stopSequencer: case respCh := <-s.stopSequencer:
if s.driverConfig.SequencerStopped { if s.driverConfig.SequencerStopped {
......
// On develop
package driver
import (
"context"
"errors"
"math/big"
"math/rand"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/metrics"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/testutils"
)
type TestDummyOutputImpl struct {
willError bool
cfg *rollup.Config
l1Origin eth.L1BlockRef
l2Head eth.L2BlockRef
}
func (d *TestDummyOutputImpl) PlanNextSequencerAction(sequenceErr error) (delay time.Duration, seal bool, onto eth.BlockID) {
return 0, d.l1Origin != (eth.L1BlockRef{}), d.l2Head.ParentID()
}
func (d *TestDummyOutputImpl) StartBuildingBlock(ctx context.Context, l1Origin eth.L1BlockRef) error {
d.l1Origin = l1Origin
return nil
}
func (d *TestDummyOutputImpl) CompleteBuildingBlock(ctx context.Context) (*eth.ExecutionPayload, error) {
// If we're meant to error, return one
if d.willError {
return nil, errors.New("the TestDummyOutputImpl.createNewBlock operation failed")
}
info := &testutils.MockBlockInfo{
InfoHash: d.l1Origin.Hash,
InfoParentHash: d.l1Origin.ParentHash,
InfoCoinbase: common.Address{},
InfoRoot: common.Hash{},
InfoNum: d.l1Origin.Number,
InfoTime: d.l1Origin.Time,
InfoMixDigest: [32]byte{},
InfoBaseFee: big.NewInt(123),
InfoReceiptRoot: common.Hash{},
}
infoTx, err := derive.L1InfoDepositBytes(d.l2Head.SequenceNumber, info, eth.SystemConfig{})
if err != nil {
panic(err)
}
payload := eth.ExecutionPayload{
ParentHash: d.l2Head.Hash,
FeeRecipient: common.Address{},
StateRoot: eth.Bytes32{},
ReceiptsRoot: eth.Bytes32{},
LogsBloom: eth.Bytes256{},
PrevRandao: eth.Bytes32{},
BlockNumber: eth.Uint64Quantity(d.l2Head.Number + 1),
GasLimit: 0,
GasUsed: 0,
Timestamp: eth.Uint64Quantity(d.l2Head.Time + d.cfg.BlockTime),
ExtraData: nil,
BaseFeePerGas: eth.Uint256Quantity{},
BlockHash: common.Hash{123},
Transactions: []eth.Data{infoTx},
}
return &payload, nil
}
var _ SequencerIface = (*TestDummyOutputImpl)(nil)
type TestDummyDerivationPipeline struct {
DerivationPipeline
l2Head eth.L2BlockRef
l2SafeHead eth.L2BlockRef
l2Finalized eth.L2BlockRef
}
func (d TestDummyDerivationPipeline) Reset() {}
func (d TestDummyDerivationPipeline) Step(ctx context.Context) error { return nil }
func (d TestDummyDerivationPipeline) SetUnsafeHead(head eth.L2BlockRef) {}
func (d TestDummyDerivationPipeline) AddUnsafePayload(payload *eth.ExecutionPayload) {}
func (d TestDummyDerivationPipeline) Finalized() eth.L2BlockRef { return d.l2Head }
func (d TestDummyDerivationPipeline) SafeL2Head() eth.L2BlockRef { return d.l2SafeHead }
func (d TestDummyDerivationPipeline) UnsafeL2Head() eth.L2BlockRef { return d.l2Finalized }
type TestDummyL1OriginSelector struct {
retval eth.L1BlockRef
}
func (l TestDummyL1OriginSelector) FindL1Origin(ctx context.Context, l1Head eth.L1BlockRef, l2Head eth.L2BlockRef) (eth.L1BlockRef, error) {
return l.retval, nil
}
// TestRejectCreateBlockBadTimestamp tests that a block creation with invalid timestamps will be caught.
// This does not test:
// - The findL1Origin call (it is hardcoded to be the head)
// - The outputInterface used to create a new block from a given payload.
// - The DerivationPipeline setting unsafe head (a mock provider is used to pretend to set it)
// - Metrics (only mocked enough to let the method proceed)
// - Publishing (network is set to nil so publishing won't occur)
func TestRejectCreateBlockBadTimestamp(t *testing.T) {
// Create our random provider
rng := rand.New(rand.NewSource(rand.Int63()))
// Create our context for methods to execute under
ctx := context.Background()
// Create our fake L1/L2 heads and link them accordingly
l1HeadRef := testutils.RandomBlockRef(rng)
l2HeadRef := testutils.RandomL2BlockRef(rng)
l2l1OriginBlock := l1HeadRef
l2HeadRef.L1Origin = l2l1OriginBlock.ID()
// Create a rollup config
cfg := rollup.Config{
BlockTime: uint64(60),
Genesis: rollup.Genesis{
L1: l1HeadRef.ID(),
L2: l2HeadRef.ID(),
L2Time: 0x7000, // dummy value
},
}
// Patch our timestamp so we fail
l2HeadRef.Time = l2l1OriginBlock.Time - (cfg.BlockTime * 2)
// Create our outputter
outputProvider := &TestDummyOutputImpl{cfg: &cfg, l2Head: l2HeadRef, willError: false}
// Create our state
s := Driver{
l1State: &L1State{
l1Head: l1HeadRef,
log: log.New(),
metrics: metrics.NoopMetrics,
},
log: log.New(),
l1OriginSelector: TestDummyL1OriginSelector{retval: l1HeadRef},
config: &cfg,
sequencer: outputProvider,
derivation: TestDummyDerivationPipeline{},
metrics: metrics.NoopMetrics,
}
// Create a new block
// - L2Head's L1Origin, its timestamp should be greater than L1 genesis.
// - L2Head timestamp + BlockTime should be greater than or equal to the L1 Time.
err := s.startNewL2Block(ctx)
if err == nil {
err = s.completeNewBlock(ctx)
}
// Verify the L1Origin's block number is greater than L1 genesis in our config.
if l2l1OriginBlock.Number < s.config.Genesis.L1.Number {
require.NoError(t, err, "L1Origin block number should be greater than the L1 genesis block number")
}
// Verify the new L2 block to create will have a time stamp equal or newer than our L1 origin block we derive from.
if l2HeadRef.Time+cfg.BlockTime < l2l1OriginBlock.Time {
// If not, we expect a specific error.
// TODO: This isn't the cleanest, we should construct + compare the whole error message.
require.NotNil(t, err)
require.Contains(t, err.Error(), "cannot build L2 block on top")
require.Contains(t, err.Error(), "for time")
require.Contains(t, err.Error(), "before L1 origin")
return
}
// If we expected the outputter to error, capture that here
if outputProvider.willError {
require.NotNil(t, err, "outputInterface failed to createNewBlock, so createNewL2Block should also have failed")
return
}
// Otherwise we should have no error.
require.NoError(t, err, "error raised in TestRejectCreateBlockBadTimestamp")
}
// FuzzRejectCreateBlockBadTimestamp is a property test derived from the TestRejectCreateBlockBadTimestamp unit test.
// It fuzzes timestamps and block times to find a configuration to violate error checking.
func FuzzRejectCreateBlockBadTimestamp(f *testing.F) {
f.Fuzz(func(t *testing.T, randSeed int64, l2Time uint64, blockTime uint64, forceOutputFail bool, currentL2HeadTime uint64) {
// Create our random provider
rng := rand.New(rand.NewSource(randSeed))
// Create our context for methods to execute under
ctx := context.Background()
// Create our fake L1/L2 heads and link them accordingly
l1HeadRef := testutils.RandomBlockRef(rng)
l2HeadRef := testutils.RandomL2BlockRef(rng)
l2l1OriginBlock := l1HeadRef
l2HeadRef.L1Origin = l2l1OriginBlock.ID()
// TODO: Cap our block time so it doesn't overflow
if blockTime > 0x100000 {
blockTime = 0x100000
}
// Create a rollup config
cfg := rollup.Config{
BlockTime: blockTime,
Genesis: rollup.Genesis{
L1: l1HeadRef.ID(),
L2: l2HeadRef.ID(),
L2Time: l2Time, // dummy value
},
}
// Patch our timestamp so we fail
l2HeadRef.Time = currentL2HeadTime
// Create our outputter
outputProvider := &TestDummyOutputImpl{cfg: &cfg, l2Head: l2HeadRef, willError: forceOutputFail}
// Create our state
s := Driver{
l1State: &L1State{
l1Head: l1HeadRef,
log: log.New(),
metrics: metrics.NoopMetrics,
},
log: log.New(),
l1OriginSelector: TestDummyL1OriginSelector{retval: l1HeadRef},
config: &cfg,
sequencer: outputProvider,
derivation: TestDummyDerivationPipeline{},
metrics: metrics.NoopMetrics,
}
// Create a new block
// - L2Head's L1Origin, its timestamp should be greater than L1 genesis.
// - L2Head timestamp + BlockTime should be greater than or equal to the L1 Time.
err := s.startNewL2Block(ctx)
if err == nil {
err = s.completeNewBlock(ctx)
}
// Verify the L1Origin's timestamp is greater than L1 genesis in our config.
if l2l1OriginBlock.Number < s.config.Genesis.L1.Number {
require.NoError(t, err)
return
}
// Verify the new L2 block to create will have a time stamp equal or newer than our L1 origin block we derive from.
if l2HeadRef.Time+cfg.BlockTime < l2l1OriginBlock.Time {
// If not, we expect a specific error.
// TODO: This isn't the cleanest, we should construct + compare the whole error message.
require.NotNil(t, err)
require.Contains(t, err.Error(), "cannot build L2 block on top")
require.Contains(t, err.Error(), "for time")
require.Contains(t, err.Error(), "before L1 origin")
return
}
// Otherwise we should have no error.
require.Nil(t, err)
// If we expected the outputter to error, capture that here
if outputProvider.willError {
require.NotNil(t, err, "outputInterface failed to createNewBlock, so createNewL2Block should also have failed")
return
}
// Otherwise we should have no error.
require.NoError(t, err, "L1Origin block number should be greater than the L1 genesis block number")
})
}
...@@ -359,7 +359,8 @@ func (s *EthClient) ReadStorageAt(ctx context.Context, address common.Address, s ...@@ -359,7 +359,8 @@ func (s *EthClient) ReadStorageAt(ctx context.Context, address common.Address, s
if err := result.Verify(block.Root()); err != nil { if err := result.Verify(block.Root()); err != nil {
return common.Hash{}, fmt.Errorf("failed to verify retrieved proof against state root: %w", err) return common.Hash{}, fmt.Errorf("failed to verify retrieved proof against state root: %w", err)
} }
return common.BytesToHash(result.StorageProof[0].Value), nil value := result.StorageProof[0].Value.ToInt()
return common.BytesToHash(value.Bytes()), nil
} }
func (s *EthClient) Close() { func (s *EthClient) Close() {
......
package version package version
var ( var (
Version = "v0.10.11" Version = "v0.10.12"
Meta = "dev" Meta = "dev"
) )
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
) )
var ( var (
Version = "v0.10.11" Version = "v0.10.12"
GitCommit = "" GitCommit = ""
GitDate = "" GitDate = ""
) )
......
...@@ -3,9 +3,9 @@ module github.com/ethereum-optimism/optimism/op-proposer ...@@ -3,9 +3,9 @@ module github.com/ethereum-optimism/optimism/op-proposer
go 1.18 go 1.18
require ( require (
github.com/ethereum-optimism/optimism/op-bindings v0.10.11 github.com/ethereum-optimism/optimism/op-bindings v0.10.12
github.com/ethereum-optimism/optimism/op-node v0.10.11 github.com/ethereum-optimism/optimism/op-node v0.10.12
github.com/ethereum-optimism/optimism/op-service v0.10.11 github.com/ethereum-optimism/optimism/op-service v0.10.12
github.com/ethereum-optimism/optimism/op-signer v0.1.0 github.com/ethereum-optimism/optimism/op-signer v0.1.0
github.com/ethereum/go-ethereum v1.10.26 github.com/ethereum/go-ethereum v1.10.26
github.com/urfave/cli v1.22.9 github.com/urfave/cli v1.22.9
...@@ -15,12 +15,16 @@ require ( ...@@ -15,12 +15,16 @@ require (
github.com/VictoriaMetrics/fastcache v1.10.0 // indirect github.com/VictoriaMetrics/fastcache v1.10.0 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect github.com/benbjohnson/clock v1.3.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd v0.23.3 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/btcsuite/btcd/btcutil v1.1.0 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect github.com/deckarep/golang-set v1.8.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/dyson/certman v0.3.0 // indirect github.com/dyson/certman v0.3.0 // indirect
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 // indirect
github.com/fjl/memsize v0.0.1 // indirect github.com/fjl/memsize v0.0.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
...@@ -80,6 +84,7 @@ require ( ...@@ -80,6 +84,7 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.5.0 // indirect github.com/tklauser/numcpus v0.5.0 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa // indirect github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
......
...@@ -36,6 +36,7 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym ...@@ -36,6 +36,7 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/VictoriaMetrics/fastcache v1.10.0 h1:5hDJnLsKLpnUEToub7ETuRu8RCkb40woBZAUiKonXzY= github.com/VictoriaMetrics/fastcache v1.10.0 h1:5hDJnLsKLpnUEToub7ETuRu8RCkb40woBZAUiKonXzY=
github.com/VictoriaMetrics/fastcache v1.10.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8= github.com/VictoriaMetrics/fastcache v1.10.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
...@@ -49,10 +50,27 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 ...@@ -49,10 +50,27 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
github.com/btcsuite/btcd v0.23.3 h1:4KH/JKy9WiCd+iUS9Mu0Zp7Dnj17TGdKrg9xc/FGj24= github.com/btcsuite/btcd v0.23.3 h1:4KH/JKy9WiCd+iUS9Mu0Zp7Dnj17TGdKrg9xc/FGj24=
github.com/btcsuite/btcd v0.23.3/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
github.com/btcsuite/btcd/btcutil v1.1.0 h1:MO4klnGY+EWJdoWF12Wkuf4AWDBPMpZNeN/jRLrklUU=
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
...@@ -67,14 +85,18 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX ...@@ -67,14 +85,18 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dyson/certman v0.3.0 h1:S7WCUim5faT/OiBhiY3u5cMaiC9MNKiA+8PJDXLaIYQ= github.com/dyson/certman v0.3.0 h1:S7WCUim5faT/OiBhiY3u5cMaiC9MNKiA+8PJDXLaIYQ=
github.com/dyson/certman v0.3.0/go.mod h1:RMWlyA9op6D9SxOBRRX3sxnParehv9gf52WWUJPd1JA= github.com/dyson/certman v0.3.0/go.mod h1:RMWlyA9op6D9SxOBRRX3sxnParehv9gf52WWUJPd1JA=
...@@ -83,14 +105,16 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF ...@@ -83,14 +105,16 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M=
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY=
github.com/ethereum-optimism/optimism/op-bindings v0.10.11 h1:RDiRyHo0G/UuxHZQdMJyqIuHtWvpionuFNfczNaWCcM= github.com/ethereum-optimism/optimism/op-bindings v0.10.12 h1:/B1gaCLwZYy9Rja3MiceV3R642bygp937kZjnYwRxA0=
github.com/ethereum-optimism/optimism/op-bindings v0.10.11/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c= github.com/ethereum-optimism/optimism/op-bindings v0.10.12/go.mod h1:9ZSUq/rjlzp3uYyBN4sZmhTc3oZgDVqJ4wrUja7vj6c=
github.com/ethereum-optimism/optimism/op-node v0.10.11 h1:ED72b68ainzcXr5/cLOYRwv+LdE4hRDnkq3SmNRY1+Q= github.com/ethereum-optimism/optimism/op-node v0.10.12 h1:yOxMThwwz1rEDDM0xTjS+6jqJwgKRtrYM6h4Pdf0zno=
github.com/ethereum-optimism/optimism/op-node v0.10.11/go.mod h1:/CDpkMxc3mDklZ1nqz2lmxfeUyAUz7yC/OLmX8egAUw= github.com/ethereum-optimism/optimism/op-node v0.10.12/go.mod h1:z+DiFb82Vnn5zM3VEwc2OXK2V/JBg6MLg7ejTbsxye8=
github.com/ethereum-optimism/optimism/op-service v0.10.11 h1:o+SazhFXlE3EM9Re5KIPEQklZ9uTI8rNkjl0h5OwRtU= github.com/ethereum-optimism/optimism/op-service v0.10.12 h1:Y7pR3/b8eeHYkmo2V5z7sj8jaraYqm2Azyph5lbiIxo=
github.com/ethereum-optimism/optimism/op-service v0.10.11/go.mod h1:wbtHqi1fv00B3agj7a2zdP3OFanEfGZ23zPgGgFCF/c= github.com/ethereum-optimism/optimism/op-service v0.10.12/go.mod h1:Ibbun+aic0rjQBV8yBf9kohqIj6mQ8nSTWbZjHv+Q7Q=
github.com/ethereum-optimism/optimism/op-signer v0.1.0 h1:wH44Deai43YQWO0pEd44pDm3BahdAtSmrOHKiPvTB8Y= github.com/ethereum-optimism/optimism/op-signer v0.1.0 h1:wH44Deai43YQWO0pEd44pDm3BahdAtSmrOHKiPvTB8Y=
github.com/ethereum-optimism/optimism/op-signer v0.1.0/go.mod h1:u8sN6X/c20pP9F1Ey7jH3fi19D08Y+T9ep3PGJfdyi8= github.com/ethereum-optimism/optimism/op-signer v0.1.0/go.mod h1:u8sN6X/c20pP9F1Ey7jH3fi19D08Y+T9ep3PGJfdyi8=
github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ= github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ=
...@@ -214,7 +238,10 @@ github.com/ipfs/go-cid v0.3.2 h1:OGgOd+JCFM+y1DjWPmVH+2/4POtpDzwcr7VgnB7mZXc= ...@@ -214,7 +238,10 @@ github.com/ipfs/go-cid v0.3.2 h1:OGgOd+JCFM+y1DjWPmVH+2/4POtpDzwcr7VgnB7mZXc=
github.com/ipfs/go-cid v0.3.2/go.mod h1:gQ8pKqT/sUxGY+tIwy1RPpAojYu7jAyCp5Tz1svoupw= github.com/ipfs/go-cid v0.3.2/go.mod h1:gQ8pKqT/sUxGY+tIwy1RPpAojYu7jAyCp5Tz1svoupw=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
...@@ -225,6 +252,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V ...@@ -225,6 +252,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0= github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0=
...@@ -294,11 +322,15 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn ...@@ -294,11 +322,15 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
...@@ -359,7 +391,7 @@ github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w ...@@ -359,7 +391,7 @@ github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d h1:vmirMegf1vqPJ+lDBxLQ0MAt3tz+JL57UPxu44JBOjA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
...@@ -378,6 +410,7 @@ github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hM ...@@ -378,6 +410,7 @@ github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hM
github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+A= github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+A=
github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo= github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo=
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw= github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw=
github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q= github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q=
...@@ -395,6 +428,7 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= ...@@ -395,6 +428,7 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
...@@ -433,6 +467,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB ...@@ -433,6 +467,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
...@@ -462,6 +497,7 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ ...@@ -462,6 +497,7 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
...@@ -523,10 +559,12 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w ...@@ -523,10 +559,12 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
......
...@@ -119,8 +119,10 @@ services: ...@@ -119,8 +119,10 @@ services:
OP_BATCHER_L1_ETH_RPC: http://l1:8545 OP_BATCHER_L1_ETH_RPC: http://l1:8545
OP_BATCHER_L2_ETH_RPC: http://l2:8545 OP_BATCHER_L2_ETH_RPC: http://l2:8545
OP_BATCHER_ROLLUP_RPC: http://op-node:8545 OP_BATCHER_ROLLUP_RPC: http://op-node:8545
OP_BATCHER_MIN_L1_TX_SIZE_BYTES: 1
OP_BATCHER_MAX_L1_TX_SIZE_BYTES: 120000 OP_BATCHER_MAX_L1_TX_SIZE_BYTES: 120000
OP_BATCHER_TARGET_L1_TX_SIZE_BYTES: 624
OP_BATCHER_TARGET_NUM_FRAMES: 1
OP_BATCHER_APPROX_COMPR_RATIO: 1.0
OP_BATCHER_CHANNEL_TIMEOUT: 40 OP_BATCHER_CHANNEL_TIMEOUT: 40
OP_BATCHER_POLL_INTERVAL: 1s OP_BATCHER_POLL_INTERVAL: 1s
OP_BATCHER_NUM_CONFIRMATIONS: 1 OP_BATCHER_NUM_CONFIRMATIONS: 1
......
...@@ -46,7 +46,7 @@ COPY packages/hardhat-deploy-config/package.json ./packages/hardhat-deploy-confi ...@@ -46,7 +46,7 @@ COPY packages/hardhat-deploy-config/package.json ./packages/hardhat-deploy-confi
COPY packages/message-relayer/package.json ./packages/message-relayer/package.json COPY packages/message-relayer/package.json ./packages/message-relayer/package.json
COPY packages/fault-detector/package.json ./packages/fault-detector/package.json COPY packages/fault-detector/package.json ./packages/fault-detector/package.json
COPY packages/replica-healthcheck/package.json ./packages/replica-healthcheck/package.json COPY packages/replica-healthcheck/package.json ./packages/replica-healthcheck/package.json
COPY packages/drippie-mon/package.json ./packages/drippie-mon/package.json COPY packages/chain-mon/package.json ./packages/chain-mon/package.json
COPY packages/balance-monitor/package.json ./packages/balance-monitor/package.json COPY packages/balance-monitor/package.json ./packages/balance-monitor/package.json
COPY packages/two-step-monitor/package.json ./packages/two-step-monitor/package.json COPY packages/two-step-monitor/package.json ./packages/two-step-monitor/package.json
COPY integration-tests/package.json ./integration-tests/package.json COPY integration-tests/package.json ./integration-tests/package.json
...@@ -106,8 +106,8 @@ WORKDIR /opt/optimism/packages/replica-healthcheck ...@@ -106,8 +106,8 @@ WORKDIR /opt/optimism/packages/replica-healthcheck
ENTRYPOINT ["npm", "run", "start"] ENTRYPOINT ["npm", "run", "start"]
FROM base as drippie-mon FROM base as drippie-mon
WORKDIR /opt/optimism/packages/drippie-mon WORKDIR /opt/optimism/packages/chain-mon
ENTRYPOINT ["npm", "run", "start"] ENTRYPOINT ["npm", "run", "start:drippie-mon"]
FROM base as balance-monitor FROM base as balance-monitor
WORKDIR /opt/optimism/packages/balance-monitor WORKDIR /opt/optimism/packages/balance-monitor
......
###############################################################################
# ↓ drippie-mon ↓ #
###############################################################################
# RPC pointing to network where Drippie is deployed # RPC pointing to network where Drippie is deployed
DRIPPIE_MON__RPC= DRIPPIE_MON__RPC=
......
# @eth-optimism/drippie-mon # @eth-optimism/chain-mon
[![codecov](https://codecov.io/gh/ethereum-optimism/optimism/branch/develop/graph/badge.svg?token=0VTG7PG7YR&flag=drippie-mon-tests)](https://codecov.io/gh/ethereum-optimism/optimism) [![codecov](https://codecov.io/gh/ethereum-optimism/optimism/branch/develop/graph/badge.svg?token=0VTG7PG7YR&flag=chain-mon-tests)](https://codecov.io/gh/ethereum-optimism/optimism)
`drippie-mon` is a simple service for monitoring Drippie contracts. `chain-mon` is a collection of chain monitoring services.
## Installation ## Installation
...@@ -14,11 +14,17 @@ yarn install ...@@ -14,11 +14,17 @@ yarn install
yarn build yarn build
``` ```
## Running the service ## Running a service
Copy `.env.example` into a new file named `.env`, then set the environment variables listed there. Copy `.env.example` into a new file named `.env`, then set the environment variables listed there depending on the service you want to run.
Once your environment variables have been set, run via: Once your environment variables have been set, run via:
``` ```
yarn start yarn start:<service name>
```
For example, to run `drippie-mon`, execute:
```
yarn start:drippie-mon
``` ```
{ {
"private": true, "private": true,
"name": "@eth-optimism/drippie-mon", "name": "@eth-optimism/chain-mon",
"version": "0.4.3", "version": "0.1.0",
"description": "[Optimism] Service for monitoring Drippie instances", "description": "[Optimism] Chain monitoring services",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
"files": [ "files": [
"dist/*" "dist/*"
], ],
"scripts": { "scripts": {
"start": "ts-node ./src/service.ts", "start:drippie-mon": "ts-node ./src/drippie-mon/service.ts",
"test:coverage": "echo 'No tests defined.'", "test:coverage": "echo 'No tests defined.'",
"build": "tsc -p ./tsconfig.json", "build": "tsc -p ./tsconfig.json",
"clean": "rimraf dist/ ./tsconfig.tsbuildinfo", "clean": "rimraf dist/ ./tsconfig.tsbuildinfo",
...@@ -21,10 +21,9 @@ ...@@ -21,10 +21,9 @@
"keywords": [ "keywords": [
"optimism", "optimism",
"ethereum", "ethereum",
"drippie",
"monitoring" "monitoring"
], ],
"homepage": "https://github.com/ethereum-optimism/optimism/tree/develop/packages/drippie-mon#readme", "homepage": "https://github.com/ethereum-optimism/optimism/tree/develop/packages/chain-mon#readme",
"license": "MIT", "license": "MIT",
"author": "Optimism PBC", "author": "Optimism PBC",
"repository": { "repository": {
......
...@@ -9,7 +9,7 @@ import { Provider } from '@ethersproject/abstract-provider' ...@@ -9,7 +9,7 @@ import { Provider } from '@ethersproject/abstract-provider'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import * as DrippieArtifact from '@eth-optimism/contracts-periphery/artifacts/contracts/universal/drippie/Drippie.sol/Drippie.json' import * as DrippieArtifact from '@eth-optimism/contracts-periphery/artifacts/contracts/universal/drippie/Drippie.sol/Drippie.json'
import { version } from '../package.json' import { version } from '../../package.json'
type DrippieMonOptions = { type DrippieMonOptions = {
rpc: Provider rpc: Provider
......
export * from './drippie-mon/service'
...@@ -124,6 +124,7 @@ export abstract class BaseServiceV2< ...@@ -124,6 +124,7 @@ export abstract class BaseServiceV2<
metricsSpec: MetricsSpec<TMetrics> metricsSpec: MetricsSpec<TMetrics>
options?: Partial<TOptions & StandardOptions> options?: Partial<TOptions & StandardOptions>
loop?: boolean loop?: boolean
bodyParserParams?: bodyParser.OptionsJson
} }
) { ) {
this.loop = params.loop !== undefined ? params.loop : true this.loop = params.loop !== undefined ? params.loop : true
...@@ -330,6 +331,7 @@ export abstract class BaseServiceV2< ...@@ -330,6 +331,7 @@ export abstract class BaseServiceV2<
verify: (req, res, buf, encoding) => { verify: (req, res, buf, encoding) => {
;(req as any).rawBody = buf?.toString(encoding || 'utf8') || '' ;(req as any).rawBody = buf?.toString(encoding || 'utf8') || ''
}, },
...(this.params.bodyParserParams ?? {}),
}) })
) )
......
...@@ -9,13 +9,13 @@ GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 88513) ...@@ -9,13 +9,13 @@ GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 88513)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 74998) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 74998)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 36156) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 36156)
GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 167187) GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 167187)
Bytes_Test:test_slice_acrossMultipleWords_works() (gas: 9391) Bytes_slice_Test:test_slice_acrossMultipleWords_works() (gas: 9357)
Bytes_Test:test_slice_acrossWords_works() (gas: 1397) Bytes_slice_Test:test_slice_acrossWords_works() (gas: 1396)
Bytes_Test:test_slice_fromNonZeroIdx_works() (gas: 17218) Bytes_slice_Test:test_slice_fromNonZeroIdx_works() (gas: 17154)
Bytes_Test:test_slice_fromZeroIdx_works() (gas: 20826) Bytes_slice_Test:test_slice_fromZeroIdx_works() (gas: 20671)
Bytes_Test:test_toNibbles_expectedResult128Bytes_works() (gas: 129885) Bytes_toNibbles_Test:test_toNibbles_expectedResult128Bytes_works() (gas: 129830)
Bytes_Test:test_toNibbles_expectedResult5Bytes_works() (gas: 6132) Bytes_toNibbles_Test:test_toNibbles_expectedResult5Bytes_works() (gas: 6088)
Bytes_Test:test_toNibbles_zeroLengthInput_works() (gas: 966) Bytes_toNibbles_Test:test_toNibbles_zeroLengthInput_works() (gas: 944)
CrossDomainMessenger_BaseGas_Test:test_baseGas_succeeds() (gas: 20120) CrossDomainMessenger_BaseGas_Test:test_baseGas_succeeds() (gas: 20120)
CrossDomainOwnableThroughPortal_Test:test_depositTransaction_crossDomainOwner_succeeds() (gas: 61882) CrossDomainOwnableThroughPortal_Test:test_depositTransaction_crossDomainOwner_succeeds() (gas: 61882)
CrossDomainOwnable_Test:test_onlyOwner_notOwner_reverts() (gas: 10530) CrossDomainOwnable_Test:test_onlyOwner_notOwner_reverts() (gas: 10530)
...@@ -46,7 +46,7 @@ GovernanceToken_Test:test_mint_fromNotOwner_reverts() (gas: 17052) ...@@ -46,7 +46,7 @@ GovernanceToken_Test:test_mint_fromNotOwner_reverts() (gas: 17052)
GovernanceToken_Test:test_mint_fromOwner_succeeds() (gas: 108547) GovernanceToken_Test:test_mint_fromOwner_succeeds() (gas: 108547)
GovernanceToken_Test:test_transferFrom_succeeds() (gas: 146295) GovernanceToken_Test:test_transferFrom_succeeds() (gas: 146295)
GovernanceToken_Test:test_transfer_succeeds() (gas: 138085) GovernanceToken_Test:test_transfer_succeeds() (gas: 138085)
Hashing_Test:test_hashDepositSource_succeeds() (gas: 650) Hashing_hashDepositSource_Test:test_hashDepositSource_succeeds() (gas: 566)
L1BlockTest:test_basefee_succeeds() (gas: 7553) L1BlockTest:test_basefee_succeeds() (gas: 7553)
L1BlockTest:test_hash_succeeds() (gas: 7510) L1BlockTest:test_hash_succeeds() (gas: 7510)
L1BlockTest:test_number_succeeds() (gas: 7584) L1BlockTest:test_number_succeeds() (gas: 7584)
...@@ -193,36 +193,36 @@ LegacyERC20ETH_Test:test_mint_doesNotExist_reverts() (gas: 10649) ...@@ -193,36 +193,36 @@ LegacyERC20ETH_Test:test_mint_doesNotExist_reverts() (gas: 10649)
LegacyERC20ETH_Test:test_transferFrom_doesNotExist_reverts() (gas: 12890) LegacyERC20ETH_Test:test_transferFrom_doesNotExist_reverts() (gas: 12890)
LegacyERC20ETH_Test:test_transfer_doesNotExist_reverts() (gas: 10733) LegacyERC20ETH_Test:test_transfer_doesNotExist_reverts() (gas: 10733)
LegacyMessagePasser_Test:test_passMessageToL1_succeeds() (gas: 34518) LegacyMessagePasser_Test:test_passMessageToL1_succeeds() (gas: 34518)
MerkleTrie_Test:test_get_corruptedProof_reverts() (gas: 5713) MerkleTrie_get_Test:test_get_corruptedProof_reverts() (gas: 5713)
MerkleTrie_Test:test_get_extraProofElements_reverts() (gas: 60653) MerkleTrie_get_Test:test_get_extraProofElements_reverts() (gas: 60653)
MerkleTrie_Test:test_get_invalidDataRemainder_reverts() (gas: 35852) MerkleTrie_get_Test:test_get_invalidDataRemainder_reverts() (gas: 35852)
MerkleTrie_Test:test_get_invalidInternalNodeHash_reverts() (gas: 50810) MerkleTrie_get_Test:test_get_invalidInternalNodeHash_reverts() (gas: 50810)
MerkleTrie_Test:test_get_nonexistentKey1_reverts() (gas: 59671) MerkleTrie_get_Test:test_get_nonexistentKey1_reverts() (gas: 59671)
MerkleTrie_Test:test_get_nonexistentKey2_reverts() (gas: 23385) MerkleTrie_get_Test:test_get_nonexistentKey2_reverts() (gas: 23385)
MerkleTrie_Test:test_get_smallerPathThanKey1_reverts() (gas: 53547) MerkleTrie_get_Test:test_get_smallerPathThanKey1_reverts() (gas: 53547)
MerkleTrie_Test:test_get_smallerPathThanKey2_reverts() (gas: 55006) MerkleTrie_get_Test:test_get_smallerPathThanKey2_reverts() (gas: 55006)
MerkleTrie_Test:test_get_validProof10_succeeds() (gas: 50593) MerkleTrie_get_Test:test_get_validProof10_succeeds() (gas: 50593)
MerkleTrie_Test:test_get_validProof1_succeeds() (gas: 61666) MerkleTrie_get_Test:test_get_validProof1_succeeds() (gas: 61666)
MerkleTrie_Test:test_get_validProof2_succeeds() (gas: 71601) MerkleTrie_get_Test:test_get_validProof2_succeeds() (gas: 71601)
MerkleTrie_Test:test_get_validProof3_succeeds() (gas: 32827) MerkleTrie_get_Test:test_get_validProof3_succeeds() (gas: 32827)
MerkleTrie_Test:test_get_validProof4_succeeds() (gas: 23623) MerkleTrie_get_Test:test_get_validProof4_succeeds() (gas: 23623)
MerkleTrie_Test:test_get_validProof5_succeeds() (gas: 84262) MerkleTrie_get_Test:test_get_validProof5_succeeds() (gas: 84262)
MerkleTrie_Test:test_get_validProof6_succeeds() (gas: 72998) MerkleTrie_get_Test:test_get_validProof6_succeeds() (gas: 72998)
MerkleTrie_Test:test_get_validProof7_succeeds() (gas: 79653) MerkleTrie_get_Test:test_get_validProof7_succeeds() (gas: 79653)
MerkleTrie_Test:test_get_validProof8_succeeds() (gas: 50550) MerkleTrie_get_Test:test_get_validProof8_succeeds() (gas: 50550)
MerkleTrie_Test:test_get_validProof9_succeeds() (gas: 50593) MerkleTrie_get_Test:test_get_validProof9_succeeds() (gas: 50593)
MerkleTrie_Test:test_get_wrongKeyProof_reverts() (gas: 53848) MerkleTrie_get_Test:test_get_wrongKeyProof_reverts() (gas: 53848)
MerkleTrie_Test:test_get_zeroBranchValueLength_reverts() (gas: 43270) MerkleTrie_get_Test:test_get_zeroBranchValueLength_reverts() (gas: 43270)
MerkleTrie_Test:test_get_zeroLengthKey_reverts() (gas: 3632) MerkleTrie_get_Test:test_get_zeroLengthKey_reverts() (gas: 3632)
MintManager_Test:test_constructor_succeeds() (gas: 10535) MintManager_constructor_Test:test_constructor_succeeds() (gas: 10512)
MintManager_Test:test_mint_afterPeriodElapsed_succeeds() (gas: 148117) MintManager_mint_Test:test_mint_afterPeriodElapsed_succeeds() (gas: 148139)
MintManager_Test:test_mint_beforePeriodElapsed_reverts() (gas: 140455) MintManager_mint_Test:test_mint_beforePeriodElapsed_reverts() (gas: 140433)
MintManager_Test:test_mint_fromNotOwner_reverts() (gas: 10987) MintManager_mint_Test:test_mint_fromNotOwner_reverts() (gas: 10943)
MintManager_Test:test_mint_fromOwner_succeeds() (gas: 137241) MintManager_mint_Test:test_mint_fromOwner_succeeds() (gas: 137219)
MintManager_Test:test_mint_moreThanCap_reverts() (gas: 142523) MintManager_mint_Test:test_mint_moreThanCap_reverts() (gas: 142478)
MintManager_Test:test_upgrade_fromNotOwner_reverts() (gas: 10974) MintManager_upgrade_Test:test_upgrade_fromNotOwner_reverts() (gas: 10929)
MintManager_Test:test_upgrade_fromOwner_succeeds() (gas: 23463) MintManager_upgrade_Test:test_upgrade_fromOwner_succeeds() (gas: 23411)
MintManager_Test:test_upgrade_toZeroAddress_reverts() (gas: 11003) MintManager_upgrade_Test:test_upgrade_toZeroAddress_reverts() (gas: 10958)
OptimismMintableERC20_Test:test_bridge_succeeds() (gas: 7643) OptimismMintableERC20_Test:test_bridge_succeeds() (gas: 7643)
OptimismMintableERC20_Test:test_burn_notBridge_reverts() (gas: 11165) OptimismMintableERC20_Test:test_burn_notBridge_reverts() (gas: 11165)
OptimismMintableERC20_Test:test_burn_succeeds() (gas: 51013) OptimismMintableERC20_Test:test_burn_succeeds() (gas: 51013)
...@@ -314,67 +314,67 @@ ProxyAdmin_Test:test_setAddressManager_notOwner_reverts() (gas: 10600) ...@@ -314,67 +314,67 @@ ProxyAdmin_Test:test_setAddressManager_notOwner_reverts() (gas: 10600)
ProxyAdmin_Test:test_setImplementationName_notOwner_reverts() (gas: 11156) ProxyAdmin_Test:test_setImplementationName_notOwner_reverts() (gas: 11156)
ProxyAdmin_Test:test_setImplementationName_succeeds() (gas: 38945) ProxyAdmin_Test:test_setImplementationName_succeeds() (gas: 38945)
ProxyAdmin_Test:test_setProxyType_notOwner_reverts() (gas: 10814) ProxyAdmin_Test:test_setProxyType_notOwner_reverts() (gas: 10814)
RLPReader_Test:test_readBytes_bytestring00_succeeds() (gas: 1857) RLPReader_readBytes_Test:test_readBytes_bytestring00_succeeds() (gas: 1834)
RLPReader_Test:test_readBytes_bytestring01_succeeds() (gas: 1900) RLPReader_readBytes_Test:test_readBytes_bytestring01_succeeds() (gas: 1833)
RLPReader_Test:test_readBytes_bytestring7f_succeeds() (gas: 1898) RLPReader_readBytes_Test:test_readBytes_bytestring7f_succeeds() (gas: 1854)
RLPReader_Test:test_readBytes_invalidListLength_reverts() (gas: 3925) RLPReader_readBytes_Test:test_readBytes_invalidListLength_reverts() (gas: 3924)
RLPReader_Test:test_readBytes_invalidPrefix_reverts() (gas: 3983) RLPReader_readBytes_Test:test_readBytes_invalidPrefix_reverts() (gas: 3939)
RLPReader_Test:test_readBytes_invalidRemainder_reverts() (gas: 4177) RLPReader_readBytes_Test:test_readBytes_invalidRemainder_reverts() (gas: 4133)
RLPReader_Test:test_readBytes_invalidStringLength_reverts() (gas: 3880) RLPReader_readBytes_Test:test_readBytes_invalidStringLength_reverts() (gas: 3857)
RLPReader_Test:test_readBytes_revertListItem_reverts() (gas: 4043) RLPReader_readBytes_Test:test_readBytes_revertListItem_reverts() (gas: 3976)
RLPReader_Test:test_readList_dictTest1_succeeds() (gas: 23202) RLPReader_readList_Test:test_readList_dictTest1_succeeds() (gas: 23179)
RLPReader_Test:test_readList_empty_succeeds() (gas: 4613) RLPReader_readList_Test:test_readList_empty_succeeds() (gas: 4612)
RLPReader_Test:test_readList_incorrectLengthInArray_reverts() (gas: 3954) RLPReader_readList_Test:test_readList_incorrectLengthInArray_reverts() (gas: 3976)
RLPReader_Test:test_readList_int32Overflow2_reverts() (gas: 4117) RLPReader_readList_Test:test_readList_int32Overflow2_reverts() (gas: 4094)
RLPReader_Test:test_readList_int32Overflow_reverts() (gas: 4116) RLPReader_readList_Test:test_readList_int32Overflow_reverts() (gas: 4094)
RLPReader_Test:test_readList_invalidRemainder_reverts() (gas: 4158) RLPReader_readList_Test:test_readList_invalidRemainder_reverts() (gas: 4158)
RLPReader_Test:test_readList_invalidShortList_reverts() (gas: 3967) RLPReader_readList_Test:test_readList_invalidShortList_reverts() (gas: 3967)
RLPReader_Test:test_readList_invalidValue_reverts() (gas: 3879) RLPReader_readList_Test:test_readList_invalidValue_reverts() (gas: 3878)
RLPReader_Test:test_readList_leadingZerosInLongLengthArray1_reverts() (gas: 3982) RLPReader_readList_Test:test_readList_leadingZerosInLongLengthArray1_reverts() (gas: 3982)
RLPReader_Test:test_readList_leadingZerosInLongLengthArray2_reverts() (gas: 3968) RLPReader_readList_Test:test_readList_leadingZerosInLongLengthArray2_reverts() (gas: 3945)
RLPReader_Test:test_readList_leadingZerosInLongLengthList1_reverts() (gas: 4007) RLPReader_readList_Test:test_readList_leadingZerosInLongLengthList1_reverts() (gas: 3984)
RLPReader_Test:test_readList_listLongerThan32Elements_reverts() (gas: 38571) RLPReader_readList_Test:test_readList_listLongerThan32Elements_reverts() (gas: 38571)
RLPReader_Test:test_readList_listOfLists2_succeeds() (gas: 12169) RLPReader_readList_Test:test_readList_listOfLists2_succeeds() (gas: 12124)
RLPReader_Test:test_readList_listOfLists_succeeds() (gas: 9482) RLPReader_readList_Test:test_readList_listOfLists_succeeds() (gas: 9504)
RLPReader_Test:test_readList_longList1_succeeds() (gas: 28417) RLPReader_readList_Test:test_readList_longList1_succeeds() (gas: 28372)
RLPReader_Test:test_readList_longList2_succeeds() (gas: 196855) RLPReader_readList_Test:test_readList_longList2_succeeds() (gas: 196855)
RLPReader_Test:test_readList_longListLessThan56Bytes_reverts() (gas: 4046) RLPReader_readList_Test:test_readList_longListLessThan56Bytes_reverts() (gas: 4023)
RLPReader_Test:test_readList_longStringLength_reverts() (gas: 3924) RLPReader_readList_Test:test_readList_longStringLength_reverts() (gas: 3901)
RLPReader_Test:test_readList_longStringLessThan56Bytes_reverts() (gas: 3998) RLPReader_readList_Test:test_readList_longStringLessThan56Bytes_reverts() (gas: 4009)
RLPReader_Test:test_readList_multiList_succeeds() (gas: 11784) RLPReader_readList_Test:test_readList_multiList_succeeds() (gas: 11719)
RLPReader_Test:test_readList_nonOptimalLongLengthArray1_reverts() (gas: 3999) RLPReader_readList_Test:test_readList_nonOptimalLongLengthArray1_reverts() (gas: 3999)
RLPReader_Test:test_readList_nonOptimalLongLengthArray2_reverts() (gas: 4000) RLPReader_readList_Test:test_readList_nonOptimalLongLengthArray2_reverts() (gas: 4022)
RLPReader_Test:test_readList_notEnoughContentForList1_reverts() (gas: 4138) RLPReader_readList_Test:test_readList_notEnoughContentForList1_reverts() (gas: 4115)
RLPReader_Test:test_readList_notEnoughContentForList2_reverts() (gas: 4139) RLPReader_readList_Test:test_readList_notEnoughContentForList2_reverts() (gas: 4161)
RLPReader_Test:test_readList_notEnoughContentForString1_reverts() (gas: 4094) RLPReader_readList_Test:test_readList_notEnoughContentForString1_reverts() (gas: 4072)
RLPReader_Test:test_readList_notEnoughContentForString2_reverts() (gas: 4138) RLPReader_readList_Test:test_readList_notEnoughContentForString2_reverts() (gas: 4116)
RLPReader_Test:test_readList_notLongEnough_reverts() (gas: 3933) RLPReader_readList_Test:test_readList_notLongEnough_reverts() (gas: 3955)
RLPReader_Test:test_readList_shortListMax1_succeeds() (gas: 39747) RLPReader_readList_Test:test_readList_shortListMax1_succeeds() (gas: 39724)
RLPWriter_Test:test_writeList_dictTest1_succeeds() (gas: 37112) RLPWriter_writeList_Test:test_writeList_dictTest1_succeeds() (gas: 37112)
RLPWriter_Test:test_writeList_empty_succeeds() (gas: 1743) RLPWriter_writeList_Test:test_writeList_empty_succeeds() (gas: 1676)
RLPWriter_Test:test_writeList_listoflists2_succeeds() (gas: 16656) RLPWriter_writeList_Test:test_writeList_listoflists2_succeeds() (gas: 16633)
RLPWriter_Test:test_writeList_listoflists_succeeds() (gas: 10857) RLPWriter_writeList_Test:test_writeList_listoflists_succeeds() (gas: 10879)
RLPWriter_Test:test_writeList_longlist1_succeeds() (gas: 40467) RLPWriter_writeList_Test:test_writeList_longlist1_succeeds() (gas: 40467)
RLPWriter_Test:test_writeList_longlist2_succeeds() (gas: 281281) RLPWriter_writeList_Test:test_writeList_longlist2_succeeds() (gas: 281258)
RLPWriter_Test:test_writeList_multiList_succeeds() (gas: 22566) RLPWriter_writeList_Test:test_writeList_multiList_succeeds() (gas: 22546)
RLPWriter_Test:test_writeList_shortListMax1_succeeds() (gas: 36918) RLPWriter_writeList_Test:test_writeList_shortListMax1_succeeds() (gas: 36896)
RLPWriter_Test:test_writeList_stringList_succeeds() (gas: 10742) RLPWriter_writeList_Test:test_writeList_stringList_succeeds() (gas: 10720)
RLPWriter_Test:test_writeString_bytestring00_succeeds() (gas: 977) RLPWriter_writeString_Test:test_writeString_bytestring00_succeeds() (gas: 954)
RLPWriter_Test:test_writeString_bytestring01_succeeds() (gas: 998) RLPWriter_writeString_Test:test_writeString_bytestring01_succeeds() (gas: 975)
RLPWriter_Test:test_writeString_bytestring7f_succeeds() (gas: 1019) RLPWriter_writeString_Test:test_writeString_bytestring7f_succeeds() (gas: 953)
RLPWriter_Test:test_writeString_empty_succeeds() (gas: 1644) RLPWriter_writeString_Test:test_writeString_empty_succeeds() (gas: 1621)
RLPWriter_Test:test_writeString_longstring2_succeeds() (gas: 258779) RLPWriter_writeString_Test:test_writeString_longstring2_succeeds() (gas: 258734)
RLPWriter_Test:test_writeString_longstring_succeeds() (gas: 16994) RLPWriter_writeString_Test:test_writeString_longstring_succeeds() (gas: 16950)
RLPWriter_Test:test_writeString_shortstring2_succeeds() (gas: 15409) RLPWriter_writeString_Test:test_writeString_shortstring2_succeeds() (gas: 15364)
RLPWriter_Test:test_writeString_shortstring_succeeds() (gas: 2480) RLPWriter_writeString_Test:test_writeString_shortstring_succeeds() (gas: 2502)
RLPWriter_Test:test_writeUint_mediumint2_succeeds() (gas: 8702) RLPWriter_writeUint_Test:test_writeUint_mediumint2_succeeds() (gas: 8714)
RLPWriter_Test:test_writeUint_mediumint3_succeeds() (gas: 9123) RLPWriter_writeUint_Test:test_writeUint_mediumint3_succeeds() (gas: 9091)
RLPWriter_Test:test_writeUint_mediumint_succeeds() (gas: 8405) RLPWriter_writeUint_Test:test_writeUint_mediumint_succeeds() (gas: 8372)
RLPWriter_Test:test_writeUint_smallint2_succeeds() (gas: 7290) RLPWriter_writeUint_Test:test_writeUint_smallint2_succeeds() (gas: 7279)
RLPWriter_Test:test_writeUint_smallint3_succeeds() (gas: 7311) RLPWriter_writeUint_Test:test_writeUint_smallint3_succeeds() (gas: 7256)
RLPWriter_Test:test_writeUint_smallint4_succeeds() (gas: 7312) RLPWriter_writeUint_Test:test_writeUint_smallint4_succeeds() (gas: 7280)
RLPWriter_Test:test_writeUint_smallint_succeeds() (gas: 7290) RLPWriter_writeUint_Test:test_writeUint_smallint_succeeds() (gas: 7258)
RLPWriter_Test:test_writeUint_zero_succeeds() (gas: 7802) RLPWriter_writeUint_Test:test_writeUint_zero_succeeds() (gas: 7726)
ResourceMetering_Test:test_meter_initialResourceParams_succeeds() (gas: 8983) ResourceMetering_Test:test_meter_initialResourceParams_succeeds() (gas: 8983)
ResourceMetering_Test:test_meter_updateNoGasDelta_succeeds() (gas: 2008119) ResourceMetering_Test:test_meter_updateNoGasDelta_succeeds() (gas: 2008119)
ResourceMetering_Test:test_meter_updateOneEmptyBlock_succeeds() (gas: 18148) ResourceMetering_Test:test_meter_updateOneEmptyBlock_succeeds() (gas: 18148)
......
...@@ -4,7 +4,10 @@ pragma solidity 0.8.15; ...@@ -4,7 +4,10 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol"; import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol";
contract AddressAliasHelper_Test is Test { contract AddressAliasHelper_applyAndUndo_Test is Test {
/**
* @notice Tests that applying and then undoing an alias results in the original address.
*/
function testFuzz_applyAndUndo_succeeds(address _address) external { function testFuzz_applyAndUndo_succeeds(address _address) external {
address aliased = AddressAliasHelper.applyL1ToL2Alias(_address); address aliased = AddressAliasHelper.applyL1ToL2Alias(_address);
address unaliased = AddressAliasHelper.undoL1ToL2Alias(aliased); address unaliased = AddressAliasHelper.undoL1ToL2Alias(aliased);
......
...@@ -3,10 +3,10 @@ pragma solidity 0.8.15; ...@@ -3,10 +3,10 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { Bytes } from "../libraries/Bytes.sol"; import { Bytes } from "../libraries/Bytes.sol";
/// @title BytesTest contract Bytes_slice_Test is Test {
contract Bytes_Test is Test { /**
/// @dev Tests that the `slice` function works as expected when starting from * @notice Tests that the `slice` function works as expected when starting from index 0.
/// index 0. */
function test_slice_fromZeroIdx_works() public { function test_slice_fromZeroIdx_works() public {
bytes memory input = hex"11223344556677889900"; bytes memory input = hex"11223344556677889900";
...@@ -24,8 +24,10 @@ contract Bytes_Test is Test { ...@@ -24,8 +24,10 @@ contract Bytes_Test is Test {
assertEq(Bytes.slice(input, 0, 10), hex"11223344556677889900"); assertEq(Bytes.slice(input, 0, 10), hex"11223344556677889900");
} }
/// @dev Tests that the `slice` function works as expected when starting from /**
/// indexes [1, 9] with lengths [1, 9], in reverse order. * @notice Tests that the `slice` function works as expected when starting from indices [1, 9]
* with lengths [1, 9], in reverse order.
*/
function test_slice_fromNonZeroIdx_works() public { function test_slice_fromNonZeroIdx_works() public {
bytes memory input = hex"11223344556677889900"; bytes memory input = hex"11223344556677889900";
...@@ -42,10 +44,11 @@ contract Bytes_Test is Test { ...@@ -42,10 +44,11 @@ contract Bytes_Test is Test {
assertEq(Bytes.slice(input, 1, 9), hex"223344556677889900"); assertEq(Bytes.slice(input, 1, 9), hex"223344556677889900");
} }
/// @dev Tests that the `slice` function works as expected when slicing between /**
/// multiple words in memory. In this case, we test that a 2 byte slice between * @notice Tests that the `slice` function works as expected when slicing between multiple words
/// the 32nd byte of the first word and the 1st byte of the second word is * in memory. In this case, we test that a 2 byte slice between the 32nd byte of the
/// correct. * first word and the 1st byte of the second word is correct.
*/
function test_slice_acrossWords_works() public { function test_slice_acrossWords_works() public {
bytes bytes
memory input = hex"00000000000000000000000000000000000000000000000000000000000000112200000000000000000000000000000000000000000000000000000000000000"; memory input = hex"00000000000000000000000000000000000000000000000000000000000000112200000000000000000000000000000000000000000000000000000000000000";
...@@ -53,9 +56,11 @@ contract Bytes_Test is Test { ...@@ -53,9 +56,11 @@ contract Bytes_Test is Test {
assertEq(Bytes.slice(input, 31, 2), hex"1122"); assertEq(Bytes.slice(input, 31, 2), hex"1122");
} }
/// @dev Tests that the `slice` function works as expected when slicing between /**
/// multiple words in memory. In this case, we test that a 34 byte slice between * @notice Tests that the `slice` function works as expected when slicing between multiple
/// 3 separate words returns the correct result. * words in memory. In this case, we test that a 34 byte slice between 3 separate words
* returns the correct result.
*/
function test_slice_acrossMultipleWords_works() public { function test_slice_acrossMultipleWords_works() public {
bytes bytes
memory input = hex"000000000000000000000000000000000000000000000000000000000000001122FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1100000000000000000000000000000000000000000000000000000000000000"; memory input = hex"000000000000000000000000000000000000000000000000000000000000001122FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1100000000000000000000000000000000000000000000000000000000000000";
...@@ -65,8 +70,10 @@ contract Bytes_Test is Test { ...@@ -65,8 +70,10 @@ contract Bytes_Test is Test {
assertEq(Bytes.slice(input, 31, 34), expected); assertEq(Bytes.slice(input, 31, 34), expected);
} }
/// @dev Tests that, when given an input bytes array of length `n`, /**
/// the `slice` function will always revert if `_start + _length > n`. * @notice Tests that, when given an input bytes array of length `n`, the `slice` function will
* always revert if `_start + _length > n`.
*/
function testFuzz_slice_outOfBounds_reverts( function testFuzz_slice_outOfBounds_reverts(
bytes memory _input, bytes memory _input,
uint256 _start, uint256 _start,
...@@ -81,8 +88,10 @@ contract Bytes_Test is Test { ...@@ -81,8 +88,10 @@ contract Bytes_Test is Test {
Bytes.slice(_input, _start, _length); Bytes.slice(_input, _start, _length);
} }
/// @dev Tests that, when given a length `n` that is greater than `type(uint256).max - 31`, /**
/// the `slice` function reverts. * @notice Tests that, when given a length `n` that is greater than `type(uint256).max - 31`,
* the `slice` function reverts.
*/
function testFuzz_slice_lengthOverflows_reverts( function testFuzz_slice_lengthOverflows_reverts(
bytes memory _input, bytes memory _input,
uint256 _start, uint256 _start,
...@@ -95,8 +104,10 @@ contract Bytes_Test is Test { ...@@ -95,8 +104,10 @@ contract Bytes_Test is Test {
Bytes.slice(_input, _start, _length); Bytes.slice(_input, _start, _length);
} }
/// @dev Tests that, when given a length `n` that is greater than `type(uint256).max - 31`, /**
/// the `slice` function reverts. * @notice Tests that, when given a start index `n` that is greater than
* `type(uint256).max - n`, the `slice` function reverts.
*/
function testFuzz_slice_rangeOverflows_reverts( function testFuzz_slice_rangeOverflows_reverts(
bytes memory _input, bytes memory _input,
uint256 _start, uint256 _start,
...@@ -111,9 +122,56 @@ contract Bytes_Test is Test { ...@@ -111,9 +122,56 @@ contract Bytes_Test is Test {
vm.expectRevert("slice_overflow"); vm.expectRevert("slice_overflow");
Bytes.slice(_input, _start, _length); Bytes.slice(_input, _start, _length);
} }
}
contract Bytes_toNibbles_Test is Test {
/**
* @notice Diffs the test Solidity version of `toNibbles` against the Yul version.
*
* @param _bytes The `bytes` array to convert to nibbles.
*
* @return Yul version of `toNibbles` applied to `_bytes`.
*/
function _toNibblesYul(bytes memory _bytes) internal pure returns (bytes memory) {
// Allocate memory for the `nibbles` array.
bytes memory nibbles = new bytes(_bytes.length << 1);
assembly {
// Load the length of the passed bytes array from memory
let bytesLength := mload(_bytes)
// Store the memory offset of the _bytes array's contents on the stack
let bytesStart := add(_bytes, 0x20)
/// @dev Tests that, given an input of 5 bytes, the `toNibbles` function returns // Store the memory offset of the nibbles array's contents on the stack
/// an array of 10 nibbles corresponding to the input data. let nibblesStart := add(nibbles, 0x20)
// Loop through each byte in the input array
for {
let i := 0x00
} lt(i, bytesLength) {
i := add(i, 0x01)
} {
// Get the starting offset of the next 2 bytes in the nibbles array
let offset := add(nibblesStart, shl(0x01, i))
// Load the byte at the current index within the `_bytes` array
let b := byte(0x00, mload(add(bytesStart, i)))
// Pull out the first nibble and store it in the new array
mstore8(offset, shr(0x04, b))
// Pull out the second nibble and store it in the new array
mstore8(add(offset, 0x01), and(b, 0x0F))
}
}
return nibbles;
}
/**
* @notice Tests that, given an input of 5 bytes, the `toNibbles` function returns an array of
* 10 nibbles corresponding to the input data.
*/
function test_toNibbles_expectedResult5Bytes_works() public { function test_toNibbles_expectedResult5Bytes_works() public {
bytes memory input = hex"1234567890"; bytes memory input = hex"1234567890";
bytes memory expected = hex"01020304050607080900"; bytes memory expected = hex"01020304050607080900";
...@@ -124,10 +182,11 @@ contract Bytes_Test is Test { ...@@ -124,10 +182,11 @@ contract Bytes_Test is Test {
assertEq(actual, expected); assertEq(actual, expected);
} }
/// @dev Tests that, given an input of 128 bytes, the `toNibbles` function returns /**
/// an array of 256 nibbles corresponding to the input data. * @notice Tests that, given an input of 128 bytes, the `toNibbles` function returns an array
/// This test exists to ensure that, given a large input, the `toNibbles` function * of 256 nibbles corresponding to the input data. This test exists to ensure that,
/// works as expected. * given a large input, the `toNibbles` function works as expected.
*/
function test_toNibbles_expectedResult128Bytes_works() public { function test_toNibbles_expectedResult128Bytes_works() public {
bytes bytes
memory input = hex"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"; memory input = hex"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f";
...@@ -140,8 +199,10 @@ contract Bytes_Test is Test { ...@@ -140,8 +199,10 @@ contract Bytes_Test is Test {
assertEq(actual, expected); assertEq(actual, expected);
} }
/// @dev Tests that, given an input of 0 bytes, the `toNibbles` function returns /**
/// a zero length array. * @notice Tests that, given an input of 0 bytes, the `toNibbles` function returns a zero
* length array.
*/
function test_toNibbles_zeroLengthInput_works() public { function test_toNibbles_zeroLengthInput_works() public {
bytes memory input = hex""; bytes memory input = hex"";
bytes memory expected = hex""; bytes memory expected = hex"";
...@@ -153,30 +214,24 @@ contract Bytes_Test is Test { ...@@ -153,30 +214,24 @@ contract Bytes_Test is Test {
assertEq(actual, expected); assertEq(actual, expected);
} }
/// @dev Test that the `toNibbles` function in the `Bytes` library is equivalent to the /**
/// Yul implementation. * @notice Test that the `toNibbles` function in the `Bytes` library is equivalent to the Yul
* implementation.
*/
function testDiff_toNibbles_succeeds(bytes memory _input) public { function testDiff_toNibbles_succeeds(bytes memory _input) public {
assertEq(Bytes.toNibbles(_input), toNibblesYul(_input)); assertEq(Bytes.toNibbles(_input), _toNibblesYul(_input));
}
/// @dev Test that the `equal` function in the `Bytes` library returns `false` if given
/// two non-equal byte arrays.
function testFuzz_equal_notEqual_works(bytes memory _a, bytes memory _b) public {
vm.assume(!manualEq(_a, _b));
assertFalse(Bytes.equal(_a, _b));
} }
}
/// @dev Test whether or not the `equal` function in the `Bytes` library is equivalent contract Bytes_equal_Test is Test {
/// to manually checking equality of the two dynamic `bytes` arrays in memory. /**
function testDiff_equal_works(bytes memory _a, bytes memory _b) public { * @notice Manually checks equality of two dynamic `bytes` arrays in memory.
assertEq(Bytes.equal(_a, _b), manualEq(_a, _b)); *
} * @param _a The first `bytes` array to compare.
* @param _b The second `bytes` array to compare.
//////////////////////////////////////////////////////////////// *
// HELPERS // * @return True if the two `bytes` arrays are equal in memory.
//////////////////////////////////////////////////////////////// */
/// @dev Utility function to manually check equality of two dynamic `bytes` arrays in memory.
function manualEq(bytes memory _a, bytes memory _b) internal pure returns (bool) { function manualEq(bytes memory _a, bytes memory _b) internal pure returns (bool) {
bool _eq; bool _eq;
assembly { assembly {
...@@ -191,40 +246,20 @@ contract Bytes_Test is Test { ...@@ -191,40 +246,20 @@ contract Bytes_Test is Test {
return _eq; return _eq;
} }
/// @dev Utility function to diff test Solidity version of `toNibbles` /**
function toNibblesYul(bytes memory _bytes) internal pure returns (bytes memory) { * @notice Tests that the `equal` function in the `Bytes` library returns `false` if given two
// Allocate memory for the `nibbles` array. * non-equal byte arrays.
bytes memory nibbles = new bytes(_bytes.length << 1); */
function testFuzz_equal_notEqual_works(bytes memory _a, bytes memory _b) public {
assembly { vm.assume(!manualEq(_a, _b));
// Load the length of the passed bytes array from memory assertFalse(Bytes.equal(_a, _b));
let bytesLength := mload(_bytes) }
// Store the memory offset of the _bytes array's contents on the stack
let bytesStart := add(_bytes, 0x20)
// Store the memory offset of the nibbles array's contents on the stack
let nibblesStart := add(nibbles, 0x20)
// Loop through each byte in the input array
for {
let i := 0x00
} lt(i, bytesLength) {
i := add(i, 0x01)
} {
// Get the starting offset of the next 2 bytes in the nibbles array
let offset := add(nibblesStart, shl(0x01, i))
// Load the byte at the current index within the `_bytes` array
let b := byte(0x00, mload(add(bytesStart, i)))
// Pull out the first nibble and store it in the new array
mstore8(offset, shr(0x04, b))
// Pull out the second nibble and store it in the new array
mstore8(add(offset, 0x01), and(b, 0x0F))
}
}
return nibbles; /**
* @notice Test whether or not the `equal` function in the `Bytes` library is equivalent to
* manually checking equality of the two dynamic `bytes` arrays in memory.
*/
function testDiff_equal_works(bytes memory _a, bytes memory _b) public {
assertEq(Bytes.equal(_a, _b), manualEq(_a, _b));
} }
} }
...@@ -6,20 +6,33 @@ import { Types } from "../libraries/Types.sol"; ...@@ -6,20 +6,33 @@ import { Types } from "../libraries/Types.sol";
import { Hashing } from "../libraries/Hashing.sol"; import { Hashing } from "../libraries/Hashing.sol";
import { Encoding } from "../libraries/Encoding.sol"; import { Encoding } from "../libraries/Encoding.sol";
contract Hashing_Test is CommonTest { contract Hashing_hashDepositSource_Test is CommonTest {
function setUp() external { function setUp() external {
_setUp(); _setUp();
} }
/**
* @notice Tests that hashDepositSource returns the correct hash in a simple case.
*/
function test_hashDepositSource_succeeds() external { function test_hashDepositSource_succeeds() external {
bytes32 sourceHash = Hashing.hashDepositSource( assertEq(
0xd25df7858efc1778118fb133ac561b138845361626dfb976699c5287ed0f4959, Hashing.hashDepositSource(
0x1 0xd25df7858efc1778118fb133ac561b138845361626dfb976699c5287ed0f4959,
0x1
),
0xf923fb07134d7d287cb52c770cc619e17e82606c21a875c92f4c63b65280a5cc
); );
}
}
assertEq(sourceHash, 0xf923fb07134d7d287cb52c770cc619e17e82606c21a875c92f4c63b65280a5cc); contract Hashing_hashCrossDomainMessage_Test is CommonTest {
function setUp() external {
_setUp();
} }
/**
* @notice Tests that hashCrossDomainMessage returns the correct hash in a simple case.
*/
function testDiff_hashCrossDomainMessage_succeeds( function testDiff_hashCrossDomainMessage_succeeds(
uint240 _nonce, uint240 _nonce,
uint16 _version, uint16 _version,
...@@ -29,31 +42,25 @@ contract Hashing_Test is CommonTest { ...@@ -29,31 +42,25 @@ contract Hashing_Test is CommonTest {
uint256 _gasLimit, uint256 _gasLimit,
bytes memory _data bytes memory _data
) external { ) external {
// Ensure the version is valid // Ensure the version is valid.
uint16 version = uint16(bound(uint256(_version), 0, 1)); uint16 version = uint16(bound(uint256(_version), 0, 1));
uint256 nonce = Encoding.encodeVersionedNonce(_nonce, version); uint256 nonce = Encoding.encodeVersionedNonce(_nonce, version);
bytes32 _hash = ffi.hashCrossDomainMessage( assertEq(
nonce, Hashing.hashCrossDomainMessage(nonce, _sender, _target, _value, _gasLimit, _data),
_sender, ffi.hashCrossDomainMessage(nonce, _sender, _target, _value, _gasLimit, _data)
_target,
_value,
_gasLimit,
_data
);
bytes32 hash = Hashing.hashCrossDomainMessage(
nonce,
_sender,
_target,
_value,
_gasLimit,
_data
); );
}
}
assertEq(hash, _hash); contract Hashing_hashWithdrawal_Test is CommonTest {
function setUp() external {
_setUp();
} }
/**
* @notice Tests that hashWithdrawal returns the correct hash in a simple case.
*/
function testDiff_hashWithdrawal_succeeds( function testDiff_hashWithdrawal_succeeds(
uint256 _nonce, uint256 _nonce,
address _sender, address _sender,
...@@ -62,42 +69,56 @@ contract Hashing_Test is CommonTest { ...@@ -62,42 +69,56 @@ contract Hashing_Test is CommonTest {
uint256 _gasLimit, uint256 _gasLimit,
bytes memory _data bytes memory _data
) external { ) external {
bytes32 hash = Hashing.hashWithdrawal( assertEq(
Types.WithdrawalTransaction(_nonce, _sender, _target, _value, _gasLimit, _data) Hashing.hashWithdrawal(
Types.WithdrawalTransaction(_nonce, _sender, _target, _value, _gasLimit, _data)
),
ffi.hashWithdrawal(_nonce, _sender, _target, _value, _gasLimit, _data)
); );
}
}
bytes32 _hash = ffi.hashWithdrawal(_nonce, _sender, _target, _value, _gasLimit, _data); contract Hashing_hashOutputRootProof_Test is CommonTest {
function setUp() external {
assertEq(hash, _hash); _setUp();
} }
/**
* @notice Tests that hashOutputRootProof returns the correct hash in a simple case.
*/
function testDiff_hashOutputRootProof_succeeds( function testDiff_hashOutputRootProof_succeeds(
bytes32 _version, bytes32 _version,
bytes32 _stateRoot, bytes32 _stateRoot,
bytes32 _messagePasserStorageRoot, bytes32 _messagePasserStorageRoot,
bytes32 _latestBlockhash bytes32 _latestBlockhash
) external { ) external {
Types.OutputRootProof memory proof = Types.OutputRootProof({ assertEq(
version: _version, Hashing.hashOutputRootProof(
stateRoot: _stateRoot, Types.OutputRootProof({
messagePasserStorageRoot: _messagePasserStorageRoot, version: _version,
latestBlockhash: _latestBlockhash stateRoot: _stateRoot,
}); messagePasserStorageRoot: _messagePasserStorageRoot,
latestBlockhash: _latestBlockhash
bytes32 hash = Hashing.hashOutputRootProof(proof); })
),
bytes32 _hash = ffi.hashOutputRootProof( ffi.hashOutputRootProof(
_version, _version,
_stateRoot, _stateRoot,
_messagePasserStorageRoot, _messagePasserStorageRoot,
_latestBlockhash _latestBlockhash
)
); );
}
}
assertEq(hash, _hash); contract Hashing_hashDepositTransaction_Test is CommonTest {
function setUp() external {
_setUp();
} }
// TODO(tynes): foundry bug cannot serialize /**
// bytes32 as strings with vm.toString * @notice Tests that hashDepositTransaction returns the correct hash in a simple case.
*/
function testDiff_hashDepositTransaction_succeeds( function testDiff_hashDepositTransaction_succeeds(
address _from, address _from,
address _to, address _to,
...@@ -107,30 +128,21 @@ contract Hashing_Test is CommonTest { ...@@ -107,30 +128,21 @@ contract Hashing_Test is CommonTest {
bytes memory _data, bytes memory _data,
uint256 _logIndex uint256 _logIndex
) external { ) external {
bytes32 hash = Hashing.hashDepositTransaction( assertEq(
Types.UserDepositTransaction( Hashing.hashDepositTransaction(
_from, Types.UserDepositTransaction(
_to, _from,
false, // isCreate _to,
_value, false, // isCreate
_mint, _value,
_gas, _mint,
_data, _gas,
bytes32(uint256(0)), _data,
_logIndex bytes32(uint256(0)),
) _logIndex
)
),
ffi.hashDepositTransaction(_from, _to, _mint, _value, _gas, _data, _logIndex)
); );
bytes32 _hash = ffi.hashDepositTransaction(
_from,
_to,
_mint,
_value,
_gas,
_data,
_logIndex
);
assertEq(hash, _hash);
} }
} }
...@@ -4,7 +4,7 @@ pragma solidity 0.8.15; ...@@ -4,7 +4,7 @@ pragma solidity 0.8.15;
import { CommonTest } from "./CommonTest.t.sol"; import { CommonTest } from "./CommonTest.t.sol";
import { MerkleTrie } from "../libraries/trie/MerkleTrie.sol"; import { MerkleTrie } from "../libraries/trie/MerkleTrie.sol";
contract MerkleTrie_Test is CommonTest { contract MerkleTrie_get_Test is CommonTest {
function setUp() public { function setUp() public {
_setUp(); _setUp();
} }
......
...@@ -5,7 +5,7 @@ import { CommonTest } from "./CommonTest.t.sol"; ...@@ -5,7 +5,7 @@ import { CommonTest } from "./CommonTest.t.sol";
import { MintManager } from "../governance/MintManager.sol"; import { MintManager } from "../governance/MintManager.sol";
import { GovernanceToken } from "../governance/GovernanceToken.sol"; import { GovernanceToken } from "../governance/GovernanceToken.sol";
contract MintManager_Test is CommonTest { contract MintManager_Initializer is CommonTest {
address constant owner = address(0x1234); address constant owner = address(0x1234);
address constant rando = address(0x5678); address constant rando = address(0x5678);
GovernanceToken internal gov; GovernanceToken internal gov;
...@@ -21,12 +21,22 @@ contract MintManager_Test is CommonTest { ...@@ -21,12 +21,22 @@ contract MintManager_Test is CommonTest {
vm.prank(owner); vm.prank(owner);
gov.transferOwnership(address(manager)); gov.transferOwnership(address(manager));
} }
}
contract MintManager_constructor_Test is MintManager_Initializer {
/**
* @notice Tests that the constructor properly configures the contract.
*/
function test_constructor_succeeds() external { function test_constructor_succeeds() external {
assertEq(manager.owner(), owner); assertEq(manager.owner(), owner);
assertEq(address(manager.governanceToken()), address(gov)); assertEq(address(manager.governanceToken()), address(gov));
} }
}
contract MintManager_mint_Test is MintManager_Initializer {
/**
* @notice Tests that the mint function properly mints tokens when called by the owner.
*/
function test_mint_fromOwner_succeeds() external { function test_mint_fromOwner_succeeds() external {
// Mint once. // Mint once.
vm.prank(owner); vm.prank(owner);
...@@ -36,6 +46,9 @@ contract MintManager_Test is CommonTest { ...@@ -36,6 +46,9 @@ contract MintManager_Test is CommonTest {
assertEq(gov.balanceOf(owner), 100); assertEq(gov.balanceOf(owner), 100);
} }
/**
* @notice Tests that the mint function reverts when called by a non-owner.
*/
function test_mint_fromNotOwner_reverts() external { function test_mint_fromNotOwner_reverts() external {
// Mint from rando fails. // Mint from rando fails.
vm.prank(rando); vm.prank(rando);
...@@ -43,6 +56,10 @@ contract MintManager_Test is CommonTest { ...@@ -43,6 +56,10 @@ contract MintManager_Test is CommonTest {
manager.mint(owner, 100); manager.mint(owner, 100);
} }
/**
* @notice Tests that the mint function properly mints tokens when called by the owner a second
* time after the mint period has elapsed.
*/
function test_mint_afterPeriodElapsed_succeeds() external { function test_mint_afterPeriodElapsed_succeeds() external {
// Mint once. // Mint once.
vm.prank(owner); vm.prank(owner);
...@@ -60,6 +77,10 @@ contract MintManager_Test is CommonTest { ...@@ -60,6 +77,10 @@ contract MintManager_Test is CommonTest {
assertEq(gov.balanceOf(owner), 102); assertEq(gov.balanceOf(owner), 102);
} }
/**
* @notice Tests that the mint function always reverts when called before the mint period has
* elapsed, even if the caller is the owner.
*/
function test_mint_beforePeriodElapsed_reverts() external { function test_mint_beforePeriodElapsed_reverts() external {
// Mint once. // Mint once.
vm.prank(owner); vm.prank(owner);
...@@ -77,6 +98,9 @@ contract MintManager_Test is CommonTest { ...@@ -77,6 +98,9 @@ contract MintManager_Test is CommonTest {
assertEq(gov.balanceOf(owner), 100); assertEq(gov.balanceOf(owner), 100);
} }
/**
* @notice Tests that the owner cannot mint more than the mint cap.
*/
function test_mint_moreThanCap_reverts() external { function test_mint_moreThanCap_reverts() external {
// Mint once. // Mint once.
vm.prank(owner); vm.prank(owner);
...@@ -94,7 +118,12 @@ contract MintManager_Test is CommonTest { ...@@ -94,7 +118,12 @@ contract MintManager_Test is CommonTest {
// Token balance does not increase. // Token balance does not increase.
assertEq(gov.balanceOf(owner), 100); assertEq(gov.balanceOf(owner), 100);
} }
}
contract MintManager_upgrade_Test is MintManager_Initializer {
/**
* @notice Tests that the owner can upgrade the mint manager.
*/
function test_upgrade_fromOwner_succeeds() external { function test_upgrade_fromOwner_succeeds() external {
// Upgrade to new manager. // Upgrade to new manager.
vm.prank(owner); vm.prank(owner);
...@@ -104,6 +133,9 @@ contract MintManager_Test is CommonTest { ...@@ -104,6 +133,9 @@ contract MintManager_Test is CommonTest {
assertEq(gov.owner(), rando); assertEq(gov.owner(), rando);
} }
/**
* @notice Tests that the upgrade function reverts when called by a non-owner.
*/
function test_upgrade_fromNotOwner_reverts() external { function test_upgrade_fromNotOwner_reverts() external {
// Upgrade from rando fails. // Upgrade from rando fails.
vm.prank(rando); vm.prank(rando);
...@@ -111,6 +143,10 @@ contract MintManager_Test is CommonTest { ...@@ -111,6 +143,10 @@ contract MintManager_Test is CommonTest {
manager.upgrade(rando); manager.upgrade(rando);
} }
/**
* @notice Tests that the upgrade function reverts when attempting to update to the zero
* address, even if the caller is the owner.
*/
function test_upgrade_toZeroAddress_reverts() external { function test_upgrade_toZeroAddress_reverts() external {
// Upgrade to zero address fails. // Upgrade to zero address fails.
vm.prank(owner); vm.prank(owner);
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { RLPReader } from "../libraries/rlp/RLPReader.sol";
import { CommonTest } from "./CommonTest.t.sol";
import { stdError } from "forge-std/Test.sol"; import { stdError } from "forge-std/Test.sol";
import { CommonTest } from "./CommonTest.t.sol";
import { RLPReader } from "../libraries/rlp/RLPReader.sol";
contract RLPReader_Test is CommonTest { contract RLPReader_readBytes_Test is CommonTest {
function test_readBytes_bytestring00_succeeds() external { function test_readBytes_bytestring00_succeeds() external {
assertEq(RLPReader.readBytes(hex"00"), hex"00"); assertEq(RLPReader.readBytes(hex"00"), hex"00");
} }
...@@ -48,7 +48,9 @@ contract RLPReader_Test is CommonTest { ...@@ -48,7 +48,9 @@ contract RLPReader_Test is CommonTest {
); );
RLPReader.readBytes(hex"810a"); RLPReader.readBytes(hex"810a");
} }
}
contract RLPReader_readList_Test is CommonTest {
function test_readList_empty_succeeds() external { function test_readList_empty_succeeds() external {
RLPReader.RLPItem[] memory list = RLPReader.readList(hex"c0"); RLPReader.RLPItem[] memory list = RLPReader.readList(hex"c0");
assertEq(list.length, 0); assertEq(list.length, 0);
......
...@@ -4,7 +4,7 @@ pragma solidity 0.8.15; ...@@ -4,7 +4,7 @@ pragma solidity 0.8.15;
import { RLPWriter } from "../libraries/rlp/RLPWriter.sol"; import { RLPWriter } from "../libraries/rlp/RLPWriter.sol";
import { CommonTest } from "./CommonTest.t.sol"; import { CommonTest } from "./CommonTest.t.sol";
contract RLPWriter_Test is CommonTest { contract RLPWriter_writeString_Test is CommonTest {
function test_writeString_empty_succeeds() external { function test_writeString_empty_succeeds() external {
assertEq(RLPWriter.writeString(""), hex"80"); assertEq(RLPWriter.writeString(""), hex"80");
} }
...@@ -47,7 +47,9 @@ contract RLPWriter_Test is CommonTest { ...@@ -47,7 +47,9 @@ contract RLPWriter_Test is CommonTest {
hex"b904004c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20437572616269747572206d6175726973206d61676e612c20737573636970697420736564207665686963756c61206e6f6e2c20696163756c697320666175636962757320746f72746f722e2050726f696e20737573636970697420756c74726963696573206d616c6573756164612e204475697320746f72746f7220656c69742c2064696374756d2071756973207472697374697175652065752c20756c7472696365732061742072697375732e204d6f72626920612065737420696d70657264696574206d6920756c6c616d636f7270657220616c6971756574207375736369706974206e6563206c6f72656d2e2041656e65616e2071756973206c656f206d6f6c6c69732c2076756c70757461746520656c6974207661726975732c20636f6e73657175617420656e696d2e204e756c6c6120756c74726963657320747572706973206a7573746f2c20657420706f73756572652075726e6120636f6e7365637465747572206e65632e2050726f696e206e6f6e20636f6e76616c6c6973206d657475732e20446f6e65632074656d706f7220697073756d20696e206d617572697320636f6e67756520736f6c6c696369747564696e2e20566573746962756c756d20616e746520697073756d207072696d697320696e206661756369627573206f726369206c756374757320657420756c74726963657320706f737565726520637562696c69612043757261653b2053757370656e646973736520636f6e76616c6c69732073656d2076656c206d617373612066617563696275732c2065676574206c6163696e6961206c616375732074656d706f722e204e756c6c61207175697320756c747269636965732070757275732e2050726f696e20617563746f722072686f6e637573206e69626820636f6e64696d656e74756d206d6f6c6c69732e20416c697175616d20636f6e73657175617420656e696d206174206d65747573206c75637475732c206120656c656966656e6420707572757320656765737461732e20437572616269747572206174206e696268206d657475732e204e616d20626962656e64756d2c206e6571756520617420617563746f72207472697374697175652c206c6f72656d206c696265726f20616c697175657420617263752c206e6f6e20696e74657264756d2074656c6c7573206c65637475732073697420616d65742065726f732e20437261732072686f6e6375732c206d65747573206163206f726e617265206375727375732c20646f6c6f72206a7573746f20756c747269636573206d657475732c20617420756c6c616d636f7270657220766f6c7574706174" hex"b904004c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742e20437572616269747572206d6175726973206d61676e612c20737573636970697420736564207665686963756c61206e6f6e2c20696163756c697320666175636962757320746f72746f722e2050726f696e20737573636970697420756c74726963696573206d616c6573756164612e204475697320746f72746f7220656c69742c2064696374756d2071756973207472697374697175652065752c20756c7472696365732061742072697375732e204d6f72626920612065737420696d70657264696574206d6920756c6c616d636f7270657220616c6971756574207375736369706974206e6563206c6f72656d2e2041656e65616e2071756973206c656f206d6f6c6c69732c2076756c70757461746520656c6974207661726975732c20636f6e73657175617420656e696d2e204e756c6c6120756c74726963657320747572706973206a7573746f2c20657420706f73756572652075726e6120636f6e7365637465747572206e65632e2050726f696e206e6f6e20636f6e76616c6c6973206d657475732e20446f6e65632074656d706f7220697073756d20696e206d617572697320636f6e67756520736f6c6c696369747564696e2e20566573746962756c756d20616e746520697073756d207072696d697320696e206661756369627573206f726369206c756374757320657420756c74726963657320706f737565726520637562696c69612043757261653b2053757370656e646973736520636f6e76616c6c69732073656d2076656c206d617373612066617563696275732c2065676574206c6163696e6961206c616375732074656d706f722e204e756c6c61207175697320756c747269636965732070757275732e2050726f696e20617563746f722072686f6e637573206e69626820636f6e64696d656e74756d206d6f6c6c69732e20416c697175616d20636f6e73657175617420656e696d206174206d65747573206c75637475732c206120656c656966656e6420707572757320656765737461732e20437572616269747572206174206e696268206d657475732e204e616d20626962656e64756d2c206e6571756520617420617563746f72207472697374697175652c206c6f72656d206c696265726f20616c697175657420617263752c206e6f6e20696e74657264756d2074656c6c7573206c65637475732073697420616d65742065726f732e20437261732072686f6e6375732c206d65747573206163206f726e617265206375727375732c20646f6c6f72206a7573746f20756c747269636573206d657475732c20617420756c6c616d636f7270657220766f6c7574706174"
); );
} }
}
contract RLPWriter_writeUint_Test is CommonTest {
function test_writeUint_zero_succeeds() external { function test_writeUint_zero_succeeds() external {
assertEq(RLPWriter.writeUint(0x0), hex"80"); assertEq(RLPWriter.writeUint(0x0), hex"80");
} }
...@@ -79,7 +81,9 @@ contract RLPWriter_Test is CommonTest { ...@@ -79,7 +81,9 @@ contract RLPWriter_Test is CommonTest {
function test_writeUint_mediumint3_succeeds() external { function test_writeUint_mediumint3_succeeds() external {
assertEq(RLPWriter.writeUint(100000), hex"830186a0"); assertEq(RLPWriter.writeUint(100000), hex"830186a0");
} }
}
contract RLPWriter_writeList_Test is CommonTest {
function test_writeList_empty_succeeds() external { function test_writeList_empty_succeeds() external {
assertEq(RLPWriter.writeList(new bytes[](0)), hex"c0"); assertEq(RLPWriter.writeList(new bytes[](0)), hex"c0");
} }
......
...@@ -4,8 +4,8 @@ pragma solidity 0.8.15; ...@@ -4,8 +4,8 @@ pragma solidity 0.8.15;
import { CommonTest } from "./CommonTest.t.sol"; import { CommonTest } from "./CommonTest.t.sol";
import { SafeCall } from "../libraries/SafeCall.sol"; import { SafeCall } from "../libraries/SafeCall.sol";
contract SafeCall_Test is CommonTest { contract SafeCall_call_Test is CommonTest {
function testFuzz_safeCall_succeeds( function testFuzz_call_succeeds(
address from, address from,
address to, address to,
uint256 gas, uint256 gas,
......
...@@ -4,20 +4,48 @@ AssetReceiverTest:testFail_withdrawERC721() (gas: 55908) ...@@ -4,20 +4,48 @@ AssetReceiverTest:testFail_withdrawERC721() (gas: 55908)
AssetReceiverTest:testFail_withdrawETH() (gas: 10457) AssetReceiverTest:testFail_withdrawETH() (gas: 10457)
AssetReceiverTest:testFail_withdrawETHwithAmount() (gas: 10594) AssetReceiverTest:testFail_withdrawETHwithAmount() (gas: 10594)
AssetReceiverTest:test_constructor() (gas: 9794) AssetReceiverTest:test_constructor() (gas: 9794)
AssetReceiverTest:test_receive() (gas: 18860) AssetReceiverTest:test_receive() (gas: 21010)
AssetReceiverTest:test_withdrawERC20() (gas: 183064) AssetReceiverTest:test_withdrawERC20() (gas: 185529)
AssetReceiverTest:test_withdrawERC20withAmount() (gas: 182146) AssetReceiverTest:test_withdrawERC20withAmount() (gas: 184609)
AssetReceiverTest:test_withdrawERC721() (gas: 49097) AssetReceiverTest:test_withdrawERC721() (gas: 51565)
AssetReceiverTest:test_withdrawETH() (gas: 26179) AssetReceiverTest:test_withdrawETH() (gas: 28774)
AssetReceiverTest:test_withdrawETHwithAmount() (gas: 26108) AssetReceiverTest:test_withdrawETHwithAmount() (gas: 28703)
AssetReceiverTest:test_attest_bulk() (gas: 611440) AssetReceiverTest:test_attest_bulk() (gas: 611417)
AssetReceiverTest:test_attest_individual() (gas: 538514) AssetReceiverTest:test_attest_individual() (gas: 538536)
AssetReceiverTest:test_attest_single() (gas: 558962) AssetReceiverTest:test_attest_single() (gas: 558939)
CheckBalanceHighTest:testFuzz_check_fails(address,uint256) (runs: 256, μ: 12352, ~: 12382)
CheckBalanceHighTest:testFuzz_check_succeeds(address,uint256) (runs: 256, μ: 10284, ~: 10284)
CheckBalanceLowTest:testFuzz_check_fails(address,uint256) (runs: 256, μ: 10262, ~: 10262)
CheckBalanceLowTest:testFuzz_check_succeeds(address,uint256) (runs: 256, μ: 12374, ~: 12404)
CheckGelatoLowTest:testFuzz_check_fails(uint256,address) (runs: 256, μ: 34938, ~: 35871)
CheckGelatoLowTest:testFuzz_check_succeeds(uint256,address) (runs: 256, μ: 18800, ~: 18800)
CheckTrueTest:testFuzz_always_true_succeeds(bytes) (runs: 256, μ: 6539, ~: 6486)
Drippie_Test:testFuzz_fails_unauthorized(address) (runs: 256, μ: 17073, ~: 17073)
Drippie_Test:test_create_fails_twice() (gas: 169499)
Drippie_Test:test_create_success() (gas: 184013)
Drippie_Test:test_drip_amount() (gas: 286156)
Drippie_Test:test_drip_not_exist_fails() (gas: 15136)
Drippie_Test:test_name_not_exist_fails() (gas: 16157)
Drippie_Test:test_non_reentrant_zero_interval_fails() (gas: 19090)
Drippie_Test:test_not_active_fails() (gas: 171861)
Drippie_Test:test_reentrant_fails() (gas: 19129)
Drippie_Test:test_reentrant_succeeds() (gas: 180769)
Drippie_Test:test_set_status_none_fails() (gas: 169439)
Drippie_Test:test_set_status_same_fails() (gas: 169939)
Drippie_Test:test_set_status_success() (gas: 199270)
Drippie_Test:test_should_archive_if_paused_success() (gas: 177922)
Drippie_Test:test_should_not_allow_active_if_archived_fails() (gas: 175362)
Drippie_Test:test_should_not_allow_paused_if_archived_fails() (gas: 175383)
Drippie_Test:test_should_not_archive_if_active_fails() (gas: 176512)
Drippie_Test:test_status_unauthorized_fails() (gas: 167971)
Drippie_Test:test_trigger_one_function() (gas: 339137)
Drippie_Test:test_trigger_two_functions() (gas: 493184)
Drippie_Test:test_twice_in_one_interval_fails() (gas: 305202)
OptimistTest:test_optimist_baseURI() (gas: 116809) OptimistTest:test_optimist_baseURI() (gas: 116809)
OptimistTest:test_optimist_burn() (gas: 77526) OptimistTest:test_optimist_burn() (gas: 77526)
OptimistTest:test_optimist_initialize() (gas: 23095) OptimistTest:test_optimist_initialize() (gas: 23095)
OptimistTest:test_optimist_is_on_allow_list() (gas: 52616) OptimistTest:test_optimist_is_on_allow_list() (gas: 52616)
OptimistTest:test_optimist_mint_already_minted() (gas: 98911) OptimistTest:test_optimist_mint_already_minted() (gas: 98823)
OptimistTest:test_optimist_mint_happy_path() (gas: 99175) OptimistTest:test_optimist_mint_happy_path() (gas: 99175)
OptimistTest:test_optimist_mint_no_attestation() (gas: 15897) OptimistTest:test_optimist_mint_no_attestation() (gas: 15897)
OptimistTest:test_optimist_mint_secondary_minter() (gas: 100576) OptimistTest:test_optimist_mint_secondary_minter() (gas: 100576)
...@@ -26,9 +54,9 @@ OptimistTest:test_optimist_sbt_transfer() (gas: 102331) ...@@ -26,9 +54,9 @@ OptimistTest:test_optimist_sbt_transfer() (gas: 102331)
OptimistTest:test_optimist_set_approval_for_all() (gas: 100907) OptimistTest:test_optimist_set_approval_for_all() (gas: 100907)
OptimistTest:test_optimist_supports_interface() (gas: 5797) OptimistTest:test_optimist_supports_interface() (gas: 5797)
OptimistTest:test_optimist_token_id_of_owner() (gas: 95045) OptimistTest:test_optimist_token_id_of_owner() (gas: 95045)
OptimistTest:test_optimist_token_uri() (gas: 213950) OptimistTest:test_optimist_token_uri() (gas: 213972)
TransactorTest:testFail_CALL() (gas: 15658) TransactorTest:testFail_CALL() (gas: 15636)
TransactorTest:testFail_DELEGATECALLL() (gas: 15632) TransactorTest:testFail_DELEGATECALLL() (gas: 15632)
TransactorTest:test_CALL() (gas: 26977) TransactorTest:test_CALL() (gas: 26969)
TransactorTest:test_DELEGATECALL() (gas: 21122) TransactorTest:test_DELEGATECALL() (gas: 21189)
TransactorTest:test_constructor() (gas: 9782) TransactorTest:test_constructor() (gas: 9772)
import { DeployConfig } from '../../src'
const config: DeployConfig = {
ddd: '0x9C6373dE60c2D3297b18A8f964618ac46E011B58',
l2ProxyOwnerAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
optimistName: 'Optimist',
optimistSymbol: 'OPTIMIST',
attestorAddress: '0x60c5C9c98bcBd0b0F2fD89B24c16e533BaA8CdA3',
}
export default config
import { ethers } from 'ethers'
import { DrippieConfig, Time } from '../../src'
const config: DrippieConfig = {
BatcherBalance: {
interval: 1 * Time.DAY,
dripcheck: 'CheckBalanceLow',
checkparams: {
target: '0x6887246668a3b87f54deb3b94ba47a6f63f32985',
threshold: ethers.utils.parseEther('75'),
},
actions: [
{
target: '0x6887246668a3b87f54deb3b94ba47a6f63f32985',
value: ethers.utils.parseEther('125'),
},
],
},
ProposerBalance: {
interval: 1 * Time.DAY,
dripcheck: 'CheckBalanceLow',
checkparams: {
target: '0x473300df21d047806a082244b417f96b32f13a33',
threshold: ethers.utils.parseEther('50'),
},
actions: [
{
target: '0x473300df21d047806a082244b417f96b32f13a33',
value: ethers.utils.parseEther('100'),
},
],
},
GelatoBalance: {
interval: 1 * Time.DAY,
dripcheck: 'CheckGelatoLow',
checkparams: {
treasury: '0x2807B4aE232b624023f87d0e237A3B1bf200Fd99',
recipient: '0xc37f6a6c4AB335E20d10F034B90386E2fb70bbF5',
threshold: ethers.utils.parseEther('0.1'),
},
actions: [
{
target: '0x2807B4aE232b624023f87d0e237A3B1bf200Fd99',
value: ethers.utils.parseEther('1'),
data: {
fn: 'depositFunds',
args: [
// receiver
'0xc37f6a6c4AB335E20d10F034B90386E2fb70bbF5',
// token
'0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
// amount
ethers.utils.parseEther('1'),
],
},
},
],
},
}
export default config
...@@ -17,7 +17,22 @@ contract AssetReceiver_Initializer is Test { ...@@ -17,7 +17,22 @@ contract AssetReceiver_Initializer is Test {
TestERC721 testERC721; TestERC721 testERC721;
AssetReceiver assetReceiver; AssetReceiver assetReceiver;
function _setUp() public { event ReceivedETH(address indexed from, uint256 amount);
event WithdrewETH(address indexed withdrawer, address indexed recipient, uint256 amount);
event WithdrewERC20(
address indexed withdrawer,
address indexed recipient,
address indexed asset,
uint256 amount
);
event WithdrewERC721(
address indexed withdrawer,
address indexed recipient,
address indexed asset,
uint256 id
);
function setUp() public {
// Deploy ERC20 and ERC721 tokens // Deploy ERC20 and ERC721 tokens
testERC20 = new TestERC20(); testERC20 = new TestERC20();
testERC721 = new TestERC721(); testERC721 = new TestERC721();
...@@ -38,10 +53,6 @@ contract AssetReceiver_Initializer is Test { ...@@ -38,10 +53,6 @@ contract AssetReceiver_Initializer is Test {
} }
contract AssetReceiverTest is AssetReceiver_Initializer { contract AssetReceiverTest is AssetReceiver_Initializer {
function setUp() public {
super._setUp();
}
// Tests if the owner was set correctly during deploy // Tests if the owner was set correctly during deploy
function test_constructor() external { function test_constructor() external {
assertEq(address(alice), assetReceiver.owner()); assertEq(address(alice), assetReceiver.owner());
...@@ -52,6 +63,8 @@ contract AssetReceiverTest is AssetReceiver_Initializer { ...@@ -52,6 +63,8 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
// Check that contract balance is 0 initially // Check that contract balance is 0 initially
assertEq(address(assetReceiver).balance, 0); assertEq(address(assetReceiver).balance, 0);
vm.expectEmit(true, true, true, true, address(assetReceiver));
emit ReceivedETH(alice, 100);
// Send funds // Send funds
vm.prank(alice); vm.prank(alice);
(bool success, ) = address(assetReceiver).call{ value: 100 }(hex""); (bool success, ) = address(assetReceiver).call{ value: 100 }(hex"");
...@@ -71,6 +84,9 @@ contract AssetReceiverTest is AssetReceiver_Initializer { ...@@ -71,6 +84,9 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(address(alice).balance, 1 ether); assertEq(address(alice).balance, 1 ether);
vm.expectEmit(true, true, true, true, address(assetReceiver));
emit WithdrewETH(alice, alice, 1 ether);
// call withdrawETH // call withdrawETH
vm.prank(alice); vm.prank(alice);
assetReceiver.withdrawETH(payable(alice)); assetReceiver.withdrawETH(payable(alice));
...@@ -96,6 +112,9 @@ contract AssetReceiverTest is AssetReceiver_Initializer { ...@@ -96,6 +112,9 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(address(alice).balance, 1 ether); assertEq(address(alice).balance, 1 ether);
vm.expectEmit(true, true, true, true, address(assetReceiver));
emit WithdrewETH(alice, alice, 0.5 ether);
// call withdrawETH // call withdrawETH
vm.prank(alice); vm.prank(alice);
assetReceiver.withdrawETH(payable(alice), 0.5 ether); assetReceiver.withdrawETH(payable(alice), 0.5 ether);
...@@ -121,6 +140,9 @@ contract AssetReceiverTest is AssetReceiver_Initializer { ...@@ -121,6 +140,9 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(testERC20.balanceOf(address(assetReceiver)), 100_000); assertEq(testERC20.balanceOf(address(assetReceiver)), 100_000);
assertEq(testERC20.balanceOf(alice), 0); assertEq(testERC20.balanceOf(alice), 0);
vm.expectEmit(true, true, true, true, address(assetReceiver));
emit WithdrewERC20(alice, alice, address(testERC20), 100_000);
// call withdrawERC20 // call withdrawERC20
vm.prank(alice); vm.prank(alice);
assetReceiver.withdrawERC20(testERC20, alice); assetReceiver.withdrawERC20(testERC20, alice);
...@@ -146,6 +168,9 @@ contract AssetReceiverTest is AssetReceiver_Initializer { ...@@ -146,6 +168,9 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(testERC20.balanceOf(address(assetReceiver)), 100_000); assertEq(testERC20.balanceOf(address(assetReceiver)), 100_000);
assertEq(testERC20.balanceOf(alice), 0); assertEq(testERC20.balanceOf(alice), 0);
vm.expectEmit(true, true, true, true, address(assetReceiver));
emit WithdrewERC20(alice, alice, address(testERC20), 50_000);
// call withdrawERC20 // call withdrawERC20
vm.prank(alice); vm.prank(alice);
assetReceiver.withdrawERC20(testERC20, alice, 50_000); assetReceiver.withdrawERC20(testERC20, alice, 50_000);
...@@ -172,6 +197,9 @@ contract AssetReceiverTest is AssetReceiver_Initializer { ...@@ -172,6 +197,9 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
testERC721.transferFrom(alice, address(assetReceiver), DEFAULT_TOKEN_ID); testERC721.transferFrom(alice, address(assetReceiver), DEFAULT_TOKEN_ID);
assertEq(testERC721.ownerOf(DEFAULT_TOKEN_ID), address(assetReceiver)); assertEq(testERC721.ownerOf(DEFAULT_TOKEN_ID), address(assetReceiver));
vm.expectEmit(true, true, true, true, address(assetReceiver));
emit WithdrewERC721(alice, alice, address(testERC721), DEFAULT_TOKEN_ID);
// Call withdrawERC721 // Call withdrawERC721
vm.prank(alice); vm.prank(alice);
assetReceiver.withdrawERC721(testERC721, alice, DEFAULT_TOKEN_ID); assetReceiver.withdrawERC721(testERC721, alice, DEFAULT_TOKEN_ID);
......
...@@ -11,7 +11,7 @@ contract AssetReceiver_Initializer is Test { ...@@ -11,7 +11,7 @@ contract AssetReceiver_Initializer is Test {
address bob = address(256); address bob = address(256);
address sally = address(512); address sally = address(512);
function _setUp() public { function setUp() public {
// Give alice and bob some ETH // Give alice and bob some ETH
vm.deal(alice_attestor, 1 ether); vm.deal(alice_attestor, 1 ether);
...@@ -22,10 +22,6 @@ contract AssetReceiver_Initializer is Test { ...@@ -22,10 +22,6 @@ contract AssetReceiver_Initializer is Test {
} }
contract AssetReceiverTest is AssetReceiver_Initializer { contract AssetReceiverTest is AssetReceiver_Initializer {
function setUp() public {
super._setUp();
}
event AttestationCreated( event AttestationCreated(
address indexed creator, address indexed creator,
address indexed about, address indexed about,
......
//SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import { Test } from "forge-std/Test.sol";
import { CheckBalanceHigh } from "../universal/drippie/dripchecks/CheckBalanceHigh.sol";
/**
* @title CheckBalanceHighTest
* @notice Tests the CheckBalanceHigh contract via fuzzing both the success case
* and the failure case.
*/
contract CheckBalanceHighTest is Test {
/**
* @notice An instance of the CheckBalanceHigh contract.
*/
CheckBalanceHigh c;
/**
* @notice Deploy the `CheckTrue` contract.
*/
function setUp() external {
c = new CheckBalanceHigh();
}
/**
* @notice Fuzz the `check` function and assert that it always returns true
* when the target's balance is larger than the threshold.
*/
function testFuzz_check_succeeds(address _target, uint256 _threshold) external {
CheckBalanceHigh.Params memory p = CheckBalanceHigh.Params({
target: _target,
threshold: _threshold
});
// prevent overflows
vm.assume(_threshold != type(uint256).max);
vm.deal(_target, _threshold + 1);
assertEq(c.check(abi.encode(p)), true);
}
/**
* @notice Fuzz the `check` function and assert that it always returns false
* when the target's balance is smaller than the threshold.
*/
function testFuzz_check_fails(address _target, uint256 _threshold) external {
CheckBalanceHigh.Params memory p = CheckBalanceHigh.Params({
target: _target,
threshold: _threshold
});
vm.assume(_target.balance < _threshold);
assertEq(c.check(abi.encode(p)), false);
}
}
//SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import { Test } from "forge-std/Test.sol";
import { CheckBalanceLow } from "../universal/drippie/dripchecks/CheckBalanceLow.sol";
/**
* @title CheckBalanceLowTest
* @notice Tests the CheckBalanceLow contract via fuzzing both the success case
* and the failure case.
*/
contract CheckBalanceLowTest is Test {
/**
* @notice An instance of the CheckBalanceLow contract.
*/
CheckBalanceLow c;
/**
* @notice Deploy the `CheckBalanceLow` contract.
*/
function setUp() external {
c = new CheckBalanceLow();
}
/**
* @notice Fuzz the `check` function and assert that it always returns true
* when the target's balance is smaller than the threshold.
*/
function testFuzz_check_succeeds(address _target, uint256 _threshold) external {
CheckBalanceLow.Params memory p = CheckBalanceLow.Params({
target: _target,
threshold: _threshold
});
vm.assume(_target.balance < _threshold);
assertEq(c.check(abi.encode(p)), true);
}
/**
* @notice Fuzz the `check` function and assert that it always returns false
* when the target's balance is larger than the threshold.
*/
function testFuzz_check_fails(address _target, uint256 _threshold) external {
CheckBalanceLow.Params memory p = CheckBalanceLow.Params({
target: _target,
threshold: _threshold
});
// prevent overflows
vm.assume(_threshold != type(uint256).max);
vm.deal(_target, _threshold + 1);
assertEq(c.check(abi.encode(p)), false);
}
}
//SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import { Test } from "forge-std/Test.sol";
import {
CheckGelatoLow,
IGelatoTreasury
} from "../universal/drippie/dripchecks/CheckGelatoLow.sol";
/**
* @title MockGelatoTreasury
* @notice Mocks the Gelato treasury for testing purposes. Allows arbitrary
* setting of user balances.
*/
contract MockGelatoTreasury is IGelatoTreasury {
mapping(address => mapping(address => uint256)) private tokenBalances;
function setTokenBalance(
address _user,
address _token,
uint256 _balance
) external {
tokenBalances[_token][_user] = _balance;
}
function userTokenBalance(address _user, address _token) external view returns (uint256) {
return tokenBalances[_token][_user];
}
}
/**
* @title CheckGelatoLowTest
* @notice Tests the CheckBalanceHigh contract via fuzzing both the success case
* and the failure case.
*/
contract CheckGelatoLowTest is Test {
/**
* @notice An instance of the CheckGelatoLow contract.
*/
CheckGelatoLow c;
/**
* @notice An instance of the MockGelatoTreasury contract.
*/
MockGelatoTreasury gelato;
/**
* @notice The account Gelato uses to represent ether
*/
address internal constant eth = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/**
* @notice Deploy the `CheckGelatoLow` and `MockGelatoTreasury` contracts.
*/
function setUp() external {
c = new CheckGelatoLow();
gelato = new MockGelatoTreasury();
}
/**
* @notice Fuzz the `check` function and assert that it always returns true
* when the user's balance in the treasury is less than the threshold.
*/
function testFuzz_check_succeeds(uint256 _threshold, address _recipient) external {
CheckGelatoLow.Params memory p = CheckGelatoLow.Params({
treasury: address(gelato),
threshold: _threshold,
recipient: _recipient
});
vm.assume(gelato.userTokenBalance(_recipient, eth) < _threshold);
assertEq(c.check(abi.encode(p)), true);
}
/**
* @notice Fuzz the `check` function and assert that it always returns false
* when the user's balance in the treasury is greater than or equal
* to the threshold.
*/
function testFuzz_check_fails(uint256 _threshold, address _recipient) external {
CheckGelatoLow.Params memory p = CheckGelatoLow.Params({
treasury: address(gelato),
threshold: _threshold,
recipient: _recipient
});
gelato.setTokenBalance(_recipient, eth, _threshold);
assertEq(c.check(abi.encode(p)), false);
}
}
//SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import { Test } from "forge-std/Test.sol";
import { CheckTrue } from "../universal/drippie/dripchecks/CheckTrue.sol";
/**
* @title CheckTrueTest
* @notice Ensures that the CheckTrue DripCheck contract always returns true.
*/
contract CheckTrueTest is Test {
/**
* @notice An instance of the CheckTrue contract.
*/
CheckTrue c;
/**
* @notice Deploy the `CheckTrue` contract.
*/
function setUp() external {
c = new CheckTrue();
}
/**
* @notice Fuzz the `check` function and assert that it always returns true.
*/
function testFuzz_always_true_succeeds(bytes memory input) external {
assertEq(c.check(input), true);
}
}
//SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import { Test } from "forge-std/Test.sol";
import { Drippie } from "../universal/drippie/Drippie.sol";
import { IDripCheck } from "../universal/drippie/IDripCheck.sol";
import { CheckTrue } from "../universal/drippie/dripchecks/CheckTrue.sol";
import { SimpleStorage } from "../testing/helpers/SimpleStorage.sol";
/**
* @title TestDrippie
* @notice This is a wrapper contract around Drippie used for testing.
* Returning an entire `Drippie.DripState` causes stack too
* deep errors without `--vir-ir` which causes the compile time
* to go up by ~4x. Each of the methods is a simple getter around
* parts of the `DripState`.
*/
contract TestDrippie is Drippie {
constructor(address owner) Drippie(owner) {}
function dripStatus(string memory name) external view returns (Drippie.DripStatus) {
return drips[name].status;
}
function dripStateLast(string memory name) external view returns (uint256) {
return drips[name].last;
}
function dripConfig(string memory name) external view returns (Drippie.DripConfig memory) {
return drips[name].config;
}
function dripConfigActions(string memory name)
external
view
returns (Drippie.DripAction[] memory)
{
return drips[name].config.actions;
}
function dripConfigCheckParams(string memory name) external view returns (bytes memory) {
return drips[name].config.checkparams;
}
function dripConfigCheckAddress(string memory name) external view returns (address) {
return address(drips[name].config.dripcheck);
}
}
/**
* @title Drippie_Test
* @notice Test coverage of the Drippie contract.
*/
contract Drippie_Test is Test {
/**
* @notice Emitted when a drip is executed.
*/
event DripExecuted(string indexed nameref, string name, address executor, uint256 timestamp);
/**
* @notice Emitted when a drip's status is updated.
*/
event DripStatusUpdated(string indexed nameref, string name, Drippie.DripStatus status);
/**
* @notice Emitted when a drip is created.
*/
event DripCreated(string indexed nameref, string name, Drippie.DripConfig config);
/**
* @notice Address of the test DripCheck. CheckTrue is deployed
* here so it always returns true.
*/
IDripCheck check;
/**
* @notice Address of a SimpleStorage contract. Used to test that
* calls are actually made by Drippie.
*/
SimpleStorage simpleStorage;
/**
* @notice Address of the Drippie contract.
*/
TestDrippie drippie;
/**
* @notice Address of the Drippie owner
*/
address constant alice = address(0x42);
/**
* @notice The name of the default drip
*/
string constant dripName = "foo";
/**
* @notice Set up the test suite by deploying a CheckTrue DripCheck.
* This is a mock that always returns true. Alice is the owner
* and also give drippie 1 ether so that it can balance transfer.
*/
function setUp() external {
check = IDripCheck(new CheckTrue());
simpleStorage = new SimpleStorage();
drippie = new TestDrippie(alice);
vm.deal(address(drippie), 1 ether);
}
/**
* @notice Builds a default Drippie.DripConfig. Uses CheckTrue as the
* dripcheck so it will always be able to do its DripActions.
* Gives a dummy DripAction that does a simple transfer to
* a dummy address.
*/
function _defaultConfig() internal view returns (Drippie.DripConfig memory) {
Drippie.DripAction[] memory actions = new Drippie.DripAction[](1);
actions[0] = Drippie.DripAction({ target: payable(address(0x44)), data: hex"", value: 1 });
return
Drippie.DripConfig({
interval: 100,
dripcheck: check,
reentrant: false,
checkparams: hex"",
actions: actions
});
}
/**
* @notice Creates a default drip using the default drip config.
*/
function _createDefaultDrip(string memory name) internal {
address owner = drippie.owner();
Drippie.DripConfig memory cfg = _defaultConfig();
vm.prank(owner);
drippie.create(name, cfg);
}
/**
* @notice Moves the block's timestamp forward so that it is
* possible to execute a drip.
*/
function _warpToExecutable(string memory name) internal {
Drippie.DripConfig memory config = drippie.dripConfig(name);
vm.warp(config.interval + drippie.dripStateLast(name));
}
/**
* @notice Creates a drip and asserts that it was configured as expected.
*/
function test_create_success() external {
Drippie.DripConfig memory cfg = _defaultConfig();
vm.expectEmit(true, true, true, true);
emit DripCreated(dripName, dripName, cfg);
if (cfg.reentrant) {
assertEq(cfg.interval, 0);
} else {
assertTrue(cfg.interval > 0);
}
vm.prank(drippie.owner());
drippie.create(dripName, cfg);
Drippie.DripStatus status = drippie.dripStatus(dripName);
Drippie.DripConfig memory config = drippie.dripConfig(dripName);
assertEq(uint256(status), uint256(Drippie.DripStatus.PAUSED));
assertEq(config.interval, cfg.interval);
assertEq(config.reentrant, cfg.reentrant);
assertEq(address(config.dripcheck), address(cfg.dripcheck));
assertEq(config.checkparams, cfg.checkparams);
assertEq(config.actions.length, cfg.actions.length);
for (uint256 i; i < config.actions.length; i++) {
Drippie.DripAction memory a = config.actions[i];
Drippie.DripAction memory b = cfg.actions[i];
assertEq(a.target, b.target);
assertEq(a.data, b.data);
assertEq(a.value, b.value);
}
}
/**
* @notice Ensures that the same drip cannot be created two times.
*/
function test_create_fails_twice() external {
vm.startPrank(drippie.owner());
Drippie.DripConfig memory cfg = _defaultConfig();
drippie.create(dripName, cfg);
vm.expectRevert("Drippie: drip with that name already exists");
drippie.create(dripName, cfg);
vm.stopPrank();
}
/**
* @notice Ensures that only the owner of Drippie can create a drip.
*/
function testFuzz_fails_unauthorized(address caller) external {
vm.assume(caller != drippie.owner());
vm.prank(caller);
vm.expectRevert("UNAUTHORIZED");
drippie.create(dripName, _defaultConfig());
}
/**
* @notice The owner should be able to set the status of the drip.
*/
function test_set_status_success() external {
vm.expectEmit(true, true, true, true);
emit DripCreated(dripName, dripName, _defaultConfig());
_createDefaultDrip(dripName);
address owner = drippie.owner();
{
Drippie.DripStatus status = drippie.dripStatus(dripName);
assertEq(uint256(status), uint256(Drippie.DripStatus.PAUSED));
}
vm.prank(owner);
vm.expectEmit(true, true, true, true);
emit DripStatusUpdated({
nameref: dripName,
name: dripName,
status: Drippie.DripStatus.ACTIVE
});
drippie.status(dripName, Drippie.DripStatus.ACTIVE);
{
Drippie.DripStatus status = drippie.dripStatus(dripName);
assertEq(uint256(status), uint256(Drippie.DripStatus.ACTIVE));
}
vm.prank(owner);
vm.expectEmit(true, true, true, true);
emit DripStatusUpdated({
nameref: dripName,
name: dripName,
status: Drippie.DripStatus.PAUSED
});
drippie.status(dripName, Drippie.DripStatus.PAUSED);
{
Drippie.DripStatus status = drippie.dripStatus(dripName);
assertEq(uint256(status), uint256(Drippie.DripStatus.PAUSED));
}
}
/**
* @notice The drip status cannot be set back to NONE after it is created.
*/
function test_set_status_none_fails() external {
_createDefaultDrip(dripName);
vm.prank(drippie.owner());
vm.expectRevert("Drippie: drip status can never be set back to NONE after creation");
drippie.status(dripName, Drippie.DripStatus.NONE);
}
/**
* @notice The owner cannot set the status of the drip to the status that
* it is already set as.
*/
function test_set_status_same_fails() external {
_createDefaultDrip(dripName);
vm.prank(drippie.owner());
vm.expectRevert("Drippie: cannot set drip status to the same status as its current status");
drippie.status(dripName, Drippie.DripStatus.PAUSED);
}
/**
* @notice The owner should be able to archive the drip if it is in the
* paused state.
*/
function test_should_archive_if_paused_success() external {
_createDefaultDrip(dripName);
address owner = drippie.owner();
vm.prank(owner);
vm.expectEmit(true, true, true, true);
emit DripStatusUpdated({
nameref: dripName,
name: dripName,
status: Drippie.DripStatus.ARCHIVED
});
drippie.status(dripName, Drippie.DripStatus.ARCHIVED);
Drippie.DripStatus status = drippie.dripStatus(dripName);
assertEq(uint256(status), uint256(Drippie.DripStatus.ARCHIVED));
}
/**
* @notice The owner should not be able to archive the drip if it is in the
* active state.
*/
function test_should_not_archive_if_active_fails() external {
_createDefaultDrip(dripName);
vm.prank(drippie.owner());
drippie.status(dripName, Drippie.DripStatus.ACTIVE);
vm.prank(drippie.owner());
vm.expectRevert("Drippie: drip must first be paused before being archived");
drippie.status(dripName, Drippie.DripStatus.ARCHIVED);
}
/**
* @notice The owner should not be allowed to pause the drip if it
* has already been archived.
*/
function test_should_not_allow_paused_if_archived_fails() external {
_createDefaultDrip(dripName);
_notAllowFromArchive(dripName, Drippie.DripStatus.PAUSED);
}
/**
* @notice The owner should not be allowed to make the drip active again if
* it has already been archived.
*/
function test_should_not_allow_active_if_archived_fails() external {
_createDefaultDrip(dripName);
_notAllowFromArchive(dripName, Drippie.DripStatus.ACTIVE);
}
/**
* @notice Archive the drip and then attempt to set the status to the passed
* in status.
*/
function _notAllowFromArchive(string memory name, Drippie.DripStatus status) internal {
address owner = drippie.owner();
vm.prank(owner);
drippie.status(name, Drippie.DripStatus.ARCHIVED);
vm.expectRevert("Drippie: drip with that name has been archived and cannot be updated");
vm.prank(owner);
drippie.status(name, status);
}
/**
* @notice Attempt to update a drip that does not exist.
*/
function test_name_not_exist_fails() external {
string memory otherName = "bar";
vm.prank(drippie.owner());
vm.expectRevert("Drippie: drip with that name does not exist and cannot be updated");
assertFalse(keccak256(abi.encode(dripName)) == keccak256(abi.encode(otherName)));
drippie.status(otherName, Drippie.DripStatus.ARCHIVED);
}
/**
* @notice Expect a revert when attempting to set the status when not the owner.
*/
function test_status_unauthorized_fails() external {
_createDefaultDrip(dripName);
vm.expectRevert("UNAUTHORIZED");
drippie.status(dripName, Drippie.DripStatus.ACTIVE);
}
/**
* @notice The drip should execute and be able to transfer value.
*/
function test_drip_amount() external {
_createDefaultDrip(dripName);
vm.prank(drippie.owner());
drippie.status(dripName, Drippie.DripStatus.ACTIVE);
_warpToExecutable(dripName);
vm.expectEmit(true, true, true, true);
emit DripExecuted({
nameref: dripName,
name: dripName,
executor: address(this),
timestamp: block.timestamp
});
Drippie.DripAction[] memory actions = drippie.dripConfigActions(dripName);
assertEq(actions.length, 1);
vm.expectCall(
drippie.dripConfigCheckAddress(dripName),
drippie.dripConfigCheckParams(dripName)
);
vm.expectCall(actions[0].target, actions[0].value, actions[0].data);
drippie.drip(dripName);
}
/**
* @notice A single DripAction should be able to make a state modifying call.
*/
function test_trigger_one_function() external {
Drippie.DripConfig memory cfg = _defaultConfig();
bytes32 key = bytes32(uint256(2));
bytes32 value = bytes32(uint256(3));
// Add in an action
cfg.actions[0] = Drippie.DripAction({
target: payable(address(simpleStorage)),
data: abi.encodeWithSelector(SimpleStorage.set.selector, key, value),
value: 0
});
vm.prank(drippie.owner());
drippie.create(dripName, cfg);
_warpToExecutable(dripName);
vm.prank(drippie.owner());
drippie.status(dripName, Drippie.DripStatus.ACTIVE);
vm.expectCall(
address(simpleStorage),
0,
abi.encodeWithSelector(SimpleStorage.set.selector, key, value)
);
vm.expectEmit(true, true, true, true, address(drippie));
emit DripExecuted(dripName, dripName, address(this), block.timestamp);
drippie.drip(dripName);
assertEq(simpleStorage.get(key), value);
}
/**
* @notice Multiple drip actions should be able to be triggered with the same check.
*/
function test_trigger_two_functions() external {
Drippie.DripConfig memory cfg = _defaultConfig();
Drippie.DripAction[] memory actions = new Drippie.DripAction[](2);
bytes32 keyOne = bytes32(uint256(2));
bytes32 valueOne = bytes32(uint256(3));
actions[0] = Drippie.DripAction({
target: payable(address(simpleStorage)),
data: abi.encodeWithSelector(simpleStorage.set.selector, keyOne, valueOne),
value: 0
});
bytes32 keyTwo = bytes32(uint256(4));
bytes32 valueTwo = bytes32(uint256(5));
actions[1] = Drippie.DripAction({
target: payable(address(simpleStorage)),
data: abi.encodeWithSelector(simpleStorage.set.selector, keyTwo, valueTwo),
value: 0
});
cfg.actions = actions;
vm.prank(drippie.owner());
drippie.create(dripName, cfg);
_warpToExecutable(dripName);
vm.prank(drippie.owner());
drippie.status(dripName, Drippie.DripStatus.ACTIVE);
vm.expectCall(
drippie.dripConfigCheckAddress(dripName),
drippie.dripConfigCheckParams(dripName)
);
vm.expectCall(
address(simpleStorage),
0,
abi.encodeWithSelector(SimpleStorage.set.selector, keyOne, valueOne)
);
vm.expectCall(
address(simpleStorage),
0,
abi.encodeWithSelector(SimpleStorage.set.selector, keyTwo, valueTwo)
);
vm.expectEmit(true, true, true, true, address(drippie));
emit DripExecuted(dripName, dripName, address(this), block.timestamp);
drippie.drip(dripName);
assertEq(simpleStorage.get(keyOne), valueOne);
assertEq(simpleStorage.get(keyTwo), valueTwo);
}
/**
* @notice The drips can only be triggered once per interval. Attempt to
* trigger the same drip multiple times in the same interval. Then
* move forward to the next interval and it should trigger.
*/
function test_twice_in_one_interval_fails() external {
_createDefaultDrip(dripName);
vm.prank(drippie.owner());
drippie.status(dripName, Drippie.DripStatus.ACTIVE);
_warpToExecutable(dripName);
vm.prank(drippie.owner());
drippie.drip(dripName);
vm.prank(drippie.owner());
vm.expectRevert("Drippie: drip interval has not elapsed since last drip");
drippie.drip(dripName);
_warpToExecutable(dripName);
vm.expectEmit(true, true, true, true, address(drippie));
emit DripExecuted({
nameref: dripName,
name: dripName,
executor: address(this),
timestamp: block.timestamp
});
drippie.drip(dripName);
}
/**
* @notice It should revert if attempting to trigger a drip that does not exist.
* Note that the drip was never created at the beginning of the test.
*/
function test_drip_not_exist_fails() external {
vm.prank(drippie.owner());
vm.expectRevert("Drippie: selected drip does not exist or is not currently active");
drippie.drip(dripName);
}
/**
* @notice The owner cannot trigger the drip when it is paused.
*/
function test_not_active_fails() external {
_createDefaultDrip(dripName);
Drippie.DripStatus status = drippie.dripStatus(dripName);
assertEq(uint256(status), uint256(Drippie.DripStatus.PAUSED));
vm.prank(drippie.owner());
vm.expectRevert("Drippie: selected drip does not exist or is not currently active");
drippie.drip(dripName);
}
/**
* @notice The interval must be 0 if reentrant is set on the config.
*/
function test_reentrant_succeeds() external {
address owner = drippie.owner();
Drippie.DripConfig memory cfg = _defaultConfig();
cfg.reentrant = true;
cfg.interval = 0;
vm.prank(owner);
vm.expectEmit(true, true, true, true, address(drippie));
emit DripCreated(dripName, dripName, cfg);
drippie.create(dripName, cfg);
Drippie.DripConfig memory _cfg = drippie.dripConfig(dripName);
assertEq(_cfg.reentrant, true);
assertEq(_cfg.interval, 0);
}
/**
* @notice A non zero interval when reentrant is true will cause a revert
* when creating a drip.
*/
function test_reentrant_fails() external {
address owner = drippie.owner();
Drippie.DripConfig memory cfg = _defaultConfig();
cfg.reentrant = true;
cfg.interval = 1;
vm.prank(owner);
vm.expectRevert("Drippie: if allowing reentrant drip, must set interval to zero");
drippie.create(dripName, cfg);
}
/**
* @notice If reentrant is false and the interval is 0 then it should
* revert when the drip is created.
*/
function test_non_reentrant_zero_interval_fails() external {
address owner = drippie.owner();
Drippie.DripConfig memory cfg = _defaultConfig();
cfg.reentrant = false;
cfg.interval = 0;
vm.prank(owner);
vm.expectRevert("Drippie: interval must be greater than zero if drip is not reentrant");
drippie.create(dripName, cfg);
}
}
...@@ -47,7 +47,7 @@ contract Optimist_Initializer is Test { ...@@ -47,7 +47,7 @@ contract Optimist_Initializer is Test {
attestationStation.attest(attestationData); attestationStation.attest(attestationData);
} }
function _setUp() public { function setUp() public {
// Give alice and bob and sally some ETH // Give alice and bob and sally some ETH
vm.deal(alice_admin, 1 ether); vm.deal(alice_admin, 1 ether);
vm.deal(bob, 1 ether); vm.deal(bob, 1 ether);
...@@ -68,11 +68,6 @@ contract Optimist_Initializer is Test { ...@@ -68,11 +68,6 @@ contract Optimist_Initializer is Test {
} }
contract OptimistTest is Optimist_Initializer { contract OptimistTest is Optimist_Initializer {
function setUp() public {
super._setUp();
_initializeContracts();
}
function test_optimist_initialize() external { function test_optimist_initialize() external {
// expect name to be set // expect name to be set
assertEq(optimist.name(), name); assertEq(optimist.name(), name);
......
...@@ -15,7 +15,7 @@ contract Transactor_Initializer is Test { ...@@ -15,7 +15,7 @@ contract Transactor_Initializer is Test {
Reverter reverter; Reverter reverter;
CallRecorder callRecorded; CallRecorder callRecorded;
function _setUp() public { function setUp() public {
// Deploy Reverter and CallRecorder helper contracts // Deploy Reverter and CallRecorder helper contracts
reverter = new Reverter(); reverter = new Reverter();
callRecorded = new CallRecorder(); callRecorded = new CallRecorder();
...@@ -34,10 +34,6 @@ contract Transactor_Initializer is Test { ...@@ -34,10 +34,6 @@ contract Transactor_Initializer is Test {
} }
contract TransactorTest is Transactor_Initializer { contract TransactorTest is Transactor_Initializer {
function setUp() public {
super._setUp();
}
// Tests if the owner was set correctly during deploy // Tests if the owner was set correctly during deploy
function test_constructor() external { function test_constructor() external {
assertEq(address(alice), transactor.owner()); assertEq(address(alice), transactor.owner());
...@@ -49,7 +45,7 @@ contract TransactorTest is Transactor_Initializer { ...@@ -49,7 +45,7 @@ contract TransactorTest is Transactor_Initializer {
bytes memory data = abi.encodeWithSelector(callRecorded.record.selector); bytes memory data = abi.encodeWithSelector(callRecorded.record.selector);
// Run CALL // Run CALL
vm.prank(alice); vm.prank(alice);
vm.expectCall(address(callRecorded), data); vm.expectCall(address(callRecorded), 200_000 wei, data);
transactor.CALL(address(callRecorded), data, 200_000 wei); transactor.CALL(address(callRecorded), data, 200_000 wei);
} }
......
{ {
"address": "0xd5F62980C9d1bAa2A983bF16F8874A92F0050C48", "address": "0x7eC64a8a591bFf829ff6C8be76074D540ACb813F",
"abi": [ "abi": [
{ {
"anonymous": false, "anonymous": false,
...@@ -46,21 +46,62 @@ ...@@ -46,21 +46,62 @@
"type": "function" "type": "function"
} }
], ],
"transactionHash": "0x29af2bb56b520b9f7115dd3f4deda3dec0d6db0dfe45ef6a440a1f1b678242be",
"receipt": {
"to": "0x4e59b44847b379578588920cA78FbF26c0B4956C",
"from": "0x9C6373dE60c2D3297b18A8f964618ac46E011B58",
"contractAddress": null,
"transactionIndex": 158,
"gasUsed": "176628",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"blockHash": "0x64d24a3fac9011c7cb71b7811a78d0220251808bd5b4c5c71ac234ca77e1f792",
"transactionHash": "0x29af2bb56b520b9f7115dd3f4deda3dec0d6db0dfe45ef6a440a1f1b678242be",
"logs": [],
"blockNumber": 16485623,
"cumulativeGasUsed": "13944967",
"status": 1,
"byzantium": true
},
"args": [], "args": [],
"numDeployments": 1, "numDeployments": 2,
"solcInputHash": "b09909e91a3ff9823ceba49a3a845230", "solcInputHash": "2373b7ba869baea4fec58e6e7f7b8988",
"metadata": "{\"compiler\":{\"version\":\"0.8.9+commit.e5eed63a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"threshold\",\"type\":\"uint256\"}],\"indexed\":false,\"internalType\":\"struct CheckBalanceHigh.Params\",\"name\":\"params\",\"type\":\"tuple\"}],\"name\":\"_EventToExposeStructInABI__Params\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"_params\",\"type\":\"bytes\"}],\"name\":\"check\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"title\":\"CheckBalanceHigh\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"DripCheck for checking if an account's balance is above a given threshold.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/universal/drippie/dripchecks/CheckBalanceHigh.sol\":\"CheckBalanceHigh\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"contracts/universal/drippie/IDripCheck.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.9;\\n\\ninterface IDripCheck {\\n // DripCheck contracts that want to take parameters as inputs MUST expose a struct called\\n // Params and an event _EventForExposingParamsStructInABI(Params params). This makes it\\n // possible to easily encode parameters on the client side. Solidity does not support generics\\n // so it's not possible to do this with explicit typing.\\n\\n function check(bytes memory _params) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x73db6ada63ed1f0eba24efd36099139e66908aa45c79c0d1defe2a59a9e9eb9d\",\"license\":\"MIT\"},\"contracts/universal/drippie/dripchecks/CheckBalanceHigh.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.9;\\n\\nimport { IDripCheck } from \\\"../IDripCheck.sol\\\";\\n\\n/**\\n * @title CheckBalanceHigh\\n * @notice DripCheck for checking if an account's balance is above a given threshold.\\n */\\ncontract CheckBalanceHigh is IDripCheck {\\n event _EventToExposeStructInABI__Params(Params params);\\n struct Params {\\n address target;\\n uint256 threshold;\\n }\\n\\n function check(bytes memory _params) external view returns (bool) {\\n Params memory params = abi.decode(_params, (Params));\\n\\n // Check target balance is above threshold.\\n return params.target.balance > params.threshold;\\n }\\n}\\n\",\"keccak256\":\"0x517f69b75b62b7b74d3d4fceb45ecacb9cf1d68b62a8517e2985a6e0da0a7575\",\"license\":\"MIT\"}},\"version\":1}", "metadata": "{\"compiler\":{\"version\":\"0.8.16+commit.07a7930e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"threshold\",\"type\":\"uint256\"}],\"indexed\":false,\"internalType\":\"struct CheckBalanceHigh.Params\",\"name\":\"params\",\"type\":\"tuple\"}],\"name\":\"_EventToExposeStructInABI__Params\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"_params\",\"type\":\"bytes\"}],\"name\":\"check\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"events\":{\"_EventToExposeStructInABI__Params((address,uint256))\":{\"params\":{\"params\":\"Parameters to encode.\"}}},\"kind\":\"dev\",\"methods\":{\"check(bytes)\":{\"params\":{\"_params\":\"Encoded parameters for the drip check.\"},\"returns\":{\"_0\":\"Whether the drip should be executed.\"}}},\"title\":\"CheckBalanceHigh\",\"version\":1},\"userdoc\":{\"events\":{\"_EventToExposeStructInABI__Params((address,uint256))\":{\"notice\":\"External event used to help client-side tooling encode parameters.\"}},\"kind\":\"user\",\"methods\":{\"check(bytes)\":{\"notice\":\"Checks whether a drip should be executable.\"}},\"notice\":\"DripCheck for checking if an account's balance is above a given threshold.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/universal/drippie/dripchecks/CheckBalanceHigh.sol\":\"CheckBalanceHigh\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"contracts/universal/drippie/IDripCheck.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ninterface IDripCheck {\\n // DripCheck contracts that want to take parameters as inputs MUST expose a struct called\\n // Params and an event _EventForExposingParamsStructInABI(Params params). This makes it\\n // possible to easily encode parameters on the client side. Solidity does not support generics\\n // so it's not possible to do this with explicit typing.\\n\\n /**\\n * @notice Checks whether a drip should be executable.\\n *\\n * @param _params Encoded parameters for the drip check.\\n *\\n * @return Whether the drip should be executed.\\n */\\n function check(bytes memory _params) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xb52c89360566b2963dfd82cb2cc23f0c3ce4503a69e8563878e8aa80b6c60b3f\",\"license\":\"MIT\"},\"contracts/universal/drippie/dripchecks/CheckBalanceHigh.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.8.16;\\n\\nimport { IDripCheck } from \\\"../IDripCheck.sol\\\";\\n\\n/**\\n * @title CheckBalanceHigh\\n * @notice DripCheck for checking if an account's balance is above a given threshold.\\n */\\ncontract CheckBalanceHigh is IDripCheck {\\n struct Params {\\n address target;\\n uint256 threshold;\\n }\\n\\n /**\\n * @notice External event used to help client-side tooling encode parameters.\\n *\\n * @param params Parameters to encode.\\n */\\n event _EventToExposeStructInABI__Params(Params params);\\n\\n /**\\n * @inheritdoc IDripCheck\\n */\\n function check(bytes memory _params) external view returns (bool) {\\n Params memory params = abi.decode(_params, (Params));\\n\\n // Check target balance is above threshold.\\n return params.target.balance > params.threshold;\\n }\\n}\\n\",\"keccak256\":\"0xcb27d9e50a7c32406872b8fdc4ca62ee0d27372eb9077657f6d16f3cd3b58c85\",\"license\":\"MIT\"}},\"version\":1}",
"bytecode": "0x608060405234801561001057600080fd5b50610239806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004361003e3660046100c3565b610057565b604051901515815260200160405180910390f35b6000808280602001905181019061006e9190610192565b6020810151905173ffffffffffffffffffffffffffffffffffffffff1631119392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156100d557600080fd5b813567ffffffffffffffff808211156100ed57600080fd5b818401915084601f83011261010157600080fd5b81358181111561011357610113610094565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561015957610159610094565b8160405282815287602084870101111561017257600080fd5b826020860160208301376000928101602001929092525095945050505050565b6000604082840312156101a457600080fd5b6040516040810181811067ffffffffffffffff821117156101c7576101c7610094565b604052825173ffffffffffffffffffffffffffffffffffffffff811681146101ee57600080fd5b8152602092830151928101929092525091905056fea2646970667358221220c3948eadf9cbb80bb928bc392291d583593cefbaff6536ec1485a7602e7f6b7f64736f6c63430008090033", "bytecode": "0x608060405234801561001057600080fd5b50610239806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004361003e3660046100c3565b610057565b604051901515815260200160405180910390f35b6000808280602001905181019061006e9190610192565b6020810151905173ffffffffffffffffffffffffffffffffffffffff1631119392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156100d557600080fd5b813567ffffffffffffffff808211156100ed57600080fd5b818401915084601f83011261010157600080fd5b81358181111561011357610113610094565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561015957610159610094565b8160405282815287602084870101111561017257600080fd5b826020860160208301376000928101602001929092525095945050505050565b6000604082840312156101a457600080fd5b6040516040810181811067ffffffffffffffff821117156101c7576101c7610094565b604052825173ffffffffffffffffffffffffffffffffffffffff811681146101ee57600080fd5b8152602092830151928101929092525091905056fea26469706673582212203818d112b628fa60d8cffe88e7198c860e268b9375b8b118dacdbf531c397ef864736f6c63430008100033",
"deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004361003e3660046100c3565b610057565b604051901515815260200160405180910390f35b6000808280602001905181019061006e9190610192565b6020810151905173ffffffffffffffffffffffffffffffffffffffff1631119392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156100d557600080fd5b813567ffffffffffffffff808211156100ed57600080fd5b818401915084601f83011261010157600080fd5b81358181111561011357610113610094565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561015957610159610094565b8160405282815287602084870101111561017257600080fd5b826020860160208301376000928101602001929092525095945050505050565b6000604082840312156101a457600080fd5b6040516040810181811067ffffffffffffffff821117156101c7576101c7610094565b604052825173ffffffffffffffffffffffffffffffffffffffff811681146101ee57600080fd5b8152602092830151928101929092525091905056fea2646970667358221220c3948eadf9cbb80bb928bc392291d583593cefbaff6536ec1485a7602e7f6b7f64736f6c63430008090033", "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004361003e3660046100c3565b610057565b604051901515815260200160405180910390f35b6000808280602001905181019061006e9190610192565b6020810151905173ffffffffffffffffffffffffffffffffffffffff1631119392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156100d557600080fd5b813567ffffffffffffffff808211156100ed57600080fd5b818401915084601f83011261010157600080fd5b81358181111561011357610113610094565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561015957610159610094565b8160405282815287602084870101111561017257600080fd5b826020860160208301376000928101602001929092525095945050505050565b6000604082840312156101a457600080fd5b6040516040810181811067ffffffffffffffff821117156101c7576101c7610094565b604052825173ffffffffffffffffffffffffffffffffffffffff811681146101ee57600080fd5b8152602092830151928101929092525091905056fea26469706673582212203818d112b628fa60d8cffe88e7198c860e268b9375b8b118dacdbf531c397ef864736f6c63430008100033",
"devdoc": { "devdoc": {
"events": {
"_EventToExposeStructInABI__Params((address,uint256))": {
"params": {
"params": "Parameters to encode."
}
}
},
"kind": "dev", "kind": "dev",
"methods": {}, "methods": {
"check(bytes)": {
"params": {
"_params": "Encoded parameters for the drip check."
},
"returns": {
"_0": "Whether the drip should be executed."
}
}
},
"title": "CheckBalanceHigh", "title": "CheckBalanceHigh",
"version": 1 "version": 1
}, },
"userdoc": { "userdoc": {
"events": {
"_EventToExposeStructInABI__Params((address,uint256))": {
"notice": "External event used to help client-side tooling encode parameters."
}
},
"kind": "user", "kind": "user",
"methods": {}, "methods": {
"check(bytes)": {
"notice": "Checks whether a drip should be executable."
}
},
"notice": "DripCheck for checking if an account's balance is above a given threshold.", "notice": "DripCheck for checking if an account's balance is above a given threshold.",
"version": 1 "version": 1
}, },
......
{ {
"address": "0x00B1D6438A4E7E3733Bd9fCe4A068b07A0E47620", "address": "0x381a4eFC2A2C914eA1889722bB4B44Fa6BD5b640",
"abi": [ "abi": [
{ {
"anonymous": false, "anonymous": false,
...@@ -46,37 +46,62 @@ ...@@ -46,37 +46,62 @@
"type": "function" "type": "function"
} }
], ],
"transactionHash": "0x65f0d04160b33d020aed8bfc4207f169d230549d8cff494f600b104fe0e689a1", "transactionHash": "0x914ed8cd67ba7ae6117f48f54d28a8bc95c024bcf81892fb5e3ef7cc3e6816bf",
"receipt": { "receipt": {
"to": "0x4e59b44847b379578588920cA78FbF26c0B4956C", "to": "0x4e59b44847b379578588920cA78FbF26c0B4956C",
"from": "0xc37f6a6c4AB335E20d10F034B90386E2fb70bbF5", "from": "0x9C6373dE60c2D3297b18A8f964618ac46E011B58",
"contractAddress": null, "contractAddress": null,
"transactionIndex": 81, "transactionIndex": 142,
"gasUsed": "176640", "gasUsed": "176640",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"blockHash": "0xb1ddf0d044777738b0a1e53a28129e5d58076dbaa9b190843b76354706d8880b", "blockHash": "0x1146150967938fa2bc31b565eec819de4993de51c9f26921139c6c5251316d68",
"transactionHash": "0x65f0d04160b33d020aed8bfc4207f169d230549d8cff494f600b104fe0e689a1", "transactionHash": "0x914ed8cd67ba7ae6117f48f54d28a8bc95c024bcf81892fb5e3ef7cc3e6816bf",
"logs": [], "logs": [],
"blockNumber": 14981100, "blockNumber": 16485625,
"cumulativeGasUsed": "5262258", "cumulativeGasUsed": "11747659",
"status": 1, "status": 1,
"byzantium": true "byzantium": true
}, },
"args": [], "args": [],
"numDeployments": 1, "numDeployments": 2,
"solcInputHash": "b09909e91a3ff9823ceba49a3a845230", "solcInputHash": "2373b7ba869baea4fec58e6e7f7b8988",
"metadata": "{\"compiler\":{\"version\":\"0.8.9+commit.e5eed63a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"threshold\",\"type\":\"uint256\"}],\"indexed\":false,\"internalType\":\"struct CheckBalanceLow.Params\",\"name\":\"params\",\"type\":\"tuple\"}],\"name\":\"_EventToExposeStructInABI__Params\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"_params\",\"type\":\"bytes\"}],\"name\":\"check\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"title\":\"CheckBalanceLow\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"DripCheck for checking if an account's balance is below a given threshold.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/universal/drippie/dripchecks/CheckBalanceLow.sol\":\"CheckBalanceLow\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"contracts/universal/drippie/IDripCheck.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.9;\\n\\ninterface IDripCheck {\\n // DripCheck contracts that want to take parameters as inputs MUST expose a struct called\\n // Params and an event _EventForExposingParamsStructInABI(Params params). This makes it\\n // possible to easily encode parameters on the client side. Solidity does not support generics\\n // so it's not possible to do this with explicit typing.\\n\\n function check(bytes memory _params) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x73db6ada63ed1f0eba24efd36099139e66908aa45c79c0d1defe2a59a9e9eb9d\",\"license\":\"MIT\"},\"contracts/universal/drippie/dripchecks/CheckBalanceLow.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.9;\\n\\nimport { IDripCheck } from \\\"../IDripCheck.sol\\\";\\n\\n/**\\n * @title CheckBalanceLow\\n * @notice DripCheck for checking if an account's balance is below a given threshold.\\n */\\ncontract CheckBalanceLow is IDripCheck {\\n event _EventToExposeStructInABI__Params(Params params);\\n struct Params {\\n address target;\\n uint256 threshold;\\n }\\n\\n function check(bytes memory _params) external view returns (bool) {\\n Params memory params = abi.decode(_params, (Params));\\n\\n // Check target ETH balance is below threshold.\\n return params.target.balance < params.threshold;\\n }\\n}\\n\",\"keccak256\":\"0xd6d8e6abcc1428666132e9d8140243fff23c564b6474927e18f30fe078801842\",\"license\":\"MIT\"}},\"version\":1}", "metadata": "{\"compiler\":{\"version\":\"0.8.16+commit.07a7930e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"threshold\",\"type\":\"uint256\"}],\"indexed\":false,\"internalType\":\"struct CheckBalanceLow.Params\",\"name\":\"params\",\"type\":\"tuple\"}],\"name\":\"_EventToExposeStructInABI__Params\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"_params\",\"type\":\"bytes\"}],\"name\":\"check\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"events\":{\"_EventToExposeStructInABI__Params((address,uint256))\":{\"params\":{\"params\":\"Parameters to encode.\"}}},\"kind\":\"dev\",\"methods\":{\"check(bytes)\":{\"params\":{\"_params\":\"Encoded parameters for the drip check.\"},\"returns\":{\"_0\":\"Whether the drip should be executed.\"}}},\"title\":\"CheckBalanceLow\",\"version\":1},\"userdoc\":{\"events\":{\"_EventToExposeStructInABI__Params((address,uint256))\":{\"notice\":\"External event used to help client-side tooling encode parameters.\"}},\"kind\":\"user\",\"methods\":{\"check(bytes)\":{\"notice\":\"Checks whether a drip should be executable.\"}},\"notice\":\"DripCheck for checking if an account's balance is below a given threshold.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/universal/drippie/dripchecks/CheckBalanceLow.sol\":\"CheckBalanceLow\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"contracts/universal/drippie/IDripCheck.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ninterface IDripCheck {\\n // DripCheck contracts that want to take parameters as inputs MUST expose a struct called\\n // Params and an event _EventForExposingParamsStructInABI(Params params). This makes it\\n // possible to easily encode parameters on the client side. Solidity does not support generics\\n // so it's not possible to do this with explicit typing.\\n\\n /**\\n * @notice Checks whether a drip should be executable.\\n *\\n * @param _params Encoded parameters for the drip check.\\n *\\n * @return Whether the drip should be executed.\\n */\\n function check(bytes memory _params) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xb52c89360566b2963dfd82cb2cc23f0c3ce4503a69e8563878e8aa80b6c60b3f\",\"license\":\"MIT\"},\"contracts/universal/drippie/dripchecks/CheckBalanceLow.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.8.16;\\n\\nimport { IDripCheck } from \\\"../IDripCheck.sol\\\";\\n\\n/**\\n * @title CheckBalanceLow\\n * @notice DripCheck for checking if an account's balance is below a given threshold.\\n */\\ncontract CheckBalanceLow is IDripCheck {\\n struct Params {\\n address target;\\n uint256 threshold;\\n }\\n\\n /**\\n * @notice External event used to help client-side tooling encode parameters.\\n *\\n * @param params Parameters to encode.\\n */\\n event _EventToExposeStructInABI__Params(Params params);\\n\\n /**\\n * @inheritdoc IDripCheck\\n */\\n function check(bytes memory _params) external view returns (bool) {\\n Params memory params = abi.decode(_params, (Params));\\n\\n // Check target ETH balance is below threshold.\\n return params.target.balance < params.threshold;\\n }\\n}\\n\",\"keccak256\":\"0x6a1187a80093770931296d3360b1ecc7d17f69157fe88f628989b257548b564b\",\"license\":\"MIT\"}},\"version\":1}",
"bytecode": "0x608060405234801561001057600080fd5b50610239806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004361003e3660046100c3565b610057565b604051901515815260200160405180910390f35b6000808280602001905181019061006e9190610192565b6020810151905173ffffffffffffffffffffffffffffffffffffffff1631109392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156100d557600080fd5b813567ffffffffffffffff808211156100ed57600080fd5b818401915084601f83011261010157600080fd5b81358181111561011357610113610094565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561015957610159610094565b8160405282815287602084870101111561017257600080fd5b826020860160208301376000928101602001929092525095945050505050565b6000604082840312156101a457600080fd5b6040516040810181811067ffffffffffffffff821117156101c7576101c7610094565b604052825173ffffffffffffffffffffffffffffffffffffffff811681146101ee57600080fd5b8152602092830151928101929092525091905056fea264697066735822122075d8f6816a6709f5b54613834c7be6979cb3af4abf6a1d38244135be431d562b64736f6c63430008090033", "bytecode": "0x608060405234801561001057600080fd5b50610239806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004361003e3660046100c3565b610057565b604051901515815260200160405180910390f35b6000808280602001905181019061006e9190610192565b6020810151905173ffffffffffffffffffffffffffffffffffffffff1631109392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156100d557600080fd5b813567ffffffffffffffff808211156100ed57600080fd5b818401915084601f83011261010157600080fd5b81358181111561011357610113610094565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561015957610159610094565b8160405282815287602084870101111561017257600080fd5b826020860160208301376000928101602001929092525095945050505050565b6000604082840312156101a457600080fd5b6040516040810181811067ffffffffffffffff821117156101c7576101c7610094565b604052825173ffffffffffffffffffffffffffffffffffffffff811681146101ee57600080fd5b8152602092830151928101929092525091905056fea2646970667358221220cc86d01120737597addcccffd841244801dcb64ce402d73b8d8569a52348996464736f6c63430008100033",
"deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004361003e3660046100c3565b610057565b604051901515815260200160405180910390f35b6000808280602001905181019061006e9190610192565b6020810151905173ffffffffffffffffffffffffffffffffffffffff1631109392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156100d557600080fd5b813567ffffffffffffffff808211156100ed57600080fd5b818401915084601f83011261010157600080fd5b81358181111561011357610113610094565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561015957610159610094565b8160405282815287602084870101111561017257600080fd5b826020860160208301376000928101602001929092525095945050505050565b6000604082840312156101a457600080fd5b6040516040810181811067ffffffffffffffff821117156101c7576101c7610094565b604052825173ffffffffffffffffffffffffffffffffffffffff811681146101ee57600080fd5b8152602092830151928101929092525091905056fea264697066735822122075d8f6816a6709f5b54613834c7be6979cb3af4abf6a1d38244135be431d562b64736f6c63430008090033", "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004361003e3660046100c3565b610057565b604051901515815260200160405180910390f35b6000808280602001905181019061006e9190610192565b6020810151905173ffffffffffffffffffffffffffffffffffffffff1631109392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156100d557600080fd5b813567ffffffffffffffff808211156100ed57600080fd5b818401915084601f83011261010157600080fd5b81358181111561011357610113610094565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561015957610159610094565b8160405282815287602084870101111561017257600080fd5b826020860160208301376000928101602001929092525095945050505050565b6000604082840312156101a457600080fd5b6040516040810181811067ffffffffffffffff821117156101c7576101c7610094565b604052825173ffffffffffffffffffffffffffffffffffffffff811681146101ee57600080fd5b8152602092830151928101929092525091905056fea2646970667358221220cc86d01120737597addcccffd841244801dcb64ce402d73b8d8569a52348996464736f6c63430008100033",
"devdoc": { "devdoc": {
"events": {
"_EventToExposeStructInABI__Params((address,uint256))": {
"params": {
"params": "Parameters to encode."
}
}
},
"kind": "dev", "kind": "dev",
"methods": {}, "methods": {
"check(bytes)": {
"params": {
"_params": "Encoded parameters for the drip check."
},
"returns": {
"_0": "Whether the drip should be executed."
}
}
},
"title": "CheckBalanceLow", "title": "CheckBalanceLow",
"version": 1 "version": 1
}, },
"userdoc": { "userdoc": {
"events": {
"_EventToExposeStructInABI__Params((address,uint256))": {
"notice": "External event used to help client-side tooling encode parameters."
}
},
"kind": "user", "kind": "user",
"methods": {}, "methods": {
"check(bytes)": {
"notice": "Checks whether a drip should be executable."
}
},
"notice": "DripCheck for checking if an account's balance is below a given threshold.", "notice": "DripCheck for checking if an account's balance is below a given threshold.",
"version": 1 "version": 1
}, },
......
{ {
"address": "0x81d70959f29872A9e597a54658A93962F7D9B597", "address": "0x4f7CFc43f6D262a085F3b946cAC69E7a8E39BBAa",
"abi": [ "abi": [
{ {
"anonymous": false, "anonymous": false,
...@@ -51,37 +51,62 @@ ...@@ -51,37 +51,62 @@
"type": "function" "type": "function"
} }
], ],
"transactionHash": "0x4f3db725a79a177062c8ddbbedb005a96ecb8eab6e71c4172c1b37fa1c708688", "transactionHash": "0x18d80365aa0edda05af40c91fa32c610df2101f8c388d274cb50b882f5147167",
"receipt": { "receipt": {
"to": "0x4e59b44847b379578588920cA78FbF26c0B4956C", "to": "0x4e59b44847b379578588920cA78FbF26c0B4956C",
"from": "0xc37f6a6c4AB335E20d10F034B90386E2fb70bbF5", "from": "0x9C6373dE60c2D3297b18A8f964618ac46E011B58",
"contractAddress": null, "contractAddress": null,
"transactionIndex": 53, "transactionIndex": 47,
"gasUsed": "225248", "gasUsed": "222032",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"blockHash": "0xae292ed733027c5232a5775cd788ce31394cf4e7a068b7f2edbf8bd142e40d8f", "blockHash": "0xe3b313c0071bf10c41546a0f95ad53cda542f1b487fe1452e137ef652a0089a1",
"transactionHash": "0x4f3db725a79a177062c8ddbbedb005a96ecb8eab6e71c4172c1b37fa1c708688", "transactionHash": "0x18d80365aa0edda05af40c91fa32c610df2101f8c388d274cb50b882f5147167",
"logs": [], "logs": [],
"blockNumber": 14981104, "blockNumber": 16485627,
"cumulativeGasUsed": "5063510", "cumulativeGasUsed": "5224175",
"status": 1, "status": 1,
"byzantium": true "byzantium": true
}, },
"args": [], "args": [],
"numDeployments": 1, "numDeployments": 2,
"solcInputHash": "b09909e91a3ff9823ceba49a3a845230", "solcInputHash": "2373b7ba869baea4fec58e6e7f7b8988",
"metadata": "{\"compiler\":{\"version\":\"0.8.9+commit.e5eed63a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"treasury\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"threshold\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"}],\"indexed\":false,\"internalType\":\"struct CheckGelatoLow.Params\",\"name\":\"params\",\"type\":\"tuple\"}],\"name\":\"_EventToExposeStructInABI__Params\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"_params\",\"type\":\"bytes\"}],\"name\":\"check\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"title\":\"CheckGelatoLow\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"DripCheck for checking if an account's Gelato ETH balance is below some threshold.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/universal/drippie/dripchecks/CheckGelatoLow.sol\":\"CheckGelatoLow\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"contracts/universal/drippie/IDripCheck.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.9;\\n\\ninterface IDripCheck {\\n // DripCheck contracts that want to take parameters as inputs MUST expose a struct called\\n // Params and an event _EventForExposingParamsStructInABI(Params params). This makes it\\n // possible to easily encode parameters on the client side. Solidity does not support generics\\n // so it's not possible to do this with explicit typing.\\n\\n function check(bytes memory _params) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x73db6ada63ed1f0eba24efd36099139e66908aa45c79c0d1defe2a59a9e9eb9d\",\"license\":\"MIT\"},\"contracts/universal/drippie/dripchecks/CheckGelatoLow.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.9;\\n\\nimport { IDripCheck } from \\\"../IDripCheck.sol\\\";\\n\\ninterface IGelatoTreasury {\\n function userTokenBalance(address _user, address _token) external view returns (uint256);\\n}\\n\\n/**\\n * @title CheckGelatoLow\\n * @notice DripCheck for checking if an account's Gelato ETH balance is below some threshold.\\n */\\ncontract CheckGelatoLow is IDripCheck {\\n event _EventToExposeStructInABI__Params(Params params);\\n struct Params {\\n address treasury;\\n uint256 threshold;\\n address recipient;\\n }\\n\\n function check(bytes memory _params) external view returns (bool) {\\n Params memory params = abi.decode(_params, (Params));\\n\\n // Check GelatoTreasury ETH balance is below threshold.\\n return\\n IGelatoTreasury(params.treasury).userTokenBalance(\\n params.recipient,\\n // Gelato represents ETH as 0xeeeee....eeeee\\n 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\\n ) < params.threshold;\\n }\\n}\\n\",\"keccak256\":\"0x8d936bc5e037a1b05225f2cd208ef6899b0d29cd3b91c73e5c16762eb242e5ef\",\"license\":\"MIT\"}},\"version\":1}", "metadata": "{\"compiler\":{\"version\":\"0.8.16+commit.07a7930e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"treasury\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"threshold\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"}],\"indexed\":false,\"internalType\":\"struct CheckGelatoLow.Params\",\"name\":\"params\",\"type\":\"tuple\"}],\"name\":\"_EventToExposeStructInABI__Params\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"_params\",\"type\":\"bytes\"}],\"name\":\"check\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"events\":{\"_EventToExposeStructInABI__Params((address,uint256,address))\":{\"params\":{\"params\":\"Parameters to encode.\"}}},\"kind\":\"dev\",\"methods\":{\"check(bytes)\":{\"params\":{\"_params\":\"Encoded parameters for the drip check.\"},\"returns\":{\"_0\":\"Whether the drip should be executed.\"}}},\"title\":\"CheckGelatoLow\",\"version\":1},\"userdoc\":{\"events\":{\"_EventToExposeStructInABI__Params((address,uint256,address))\":{\"notice\":\"External event used to help client-side tooling encode parameters.\"}},\"kind\":\"user\",\"methods\":{\"check(bytes)\":{\"notice\":\"Checks whether a drip should be executable.\"}},\"notice\":\"DripCheck for checking if an account's Gelato ETH balance is below some threshold.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/universal/drippie/dripchecks/CheckGelatoLow.sol\":\"CheckGelatoLow\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"contracts/universal/drippie/IDripCheck.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ninterface IDripCheck {\\n // DripCheck contracts that want to take parameters as inputs MUST expose a struct called\\n // Params and an event _EventForExposingParamsStructInABI(Params params). This makes it\\n // possible to easily encode parameters on the client side. Solidity does not support generics\\n // so it's not possible to do this with explicit typing.\\n\\n /**\\n * @notice Checks whether a drip should be executable.\\n *\\n * @param _params Encoded parameters for the drip check.\\n *\\n * @return Whether the drip should be executed.\\n */\\n function check(bytes memory _params) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xb52c89360566b2963dfd82cb2cc23f0c3ce4503a69e8563878e8aa80b6c60b3f\",\"license\":\"MIT\"},\"contracts/universal/drippie/dripchecks/CheckGelatoLow.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.8.16;\\n\\nimport { IDripCheck } from \\\"../IDripCheck.sol\\\";\\n\\ninterface IGelatoTreasury {\\n function userTokenBalance(address _user, address _token) external view returns (uint256);\\n}\\n\\n/**\\n * @title CheckGelatoLow\\n * @notice DripCheck for checking if an account's Gelato ETH balance is below some threshold.\\n */\\ncontract CheckGelatoLow is IDripCheck {\\n struct Params {\\n address treasury;\\n uint256 threshold;\\n address recipient;\\n }\\n\\n /**\\n * @notice External event used to help client-side tooling encode parameters.\\n *\\n * @param params Parameters to encode.\\n */\\n event _EventToExposeStructInABI__Params(Params params);\\n\\n /**\\n * @inheritdoc IDripCheck\\n */\\n function check(bytes memory _params) external view returns (bool) {\\n Params memory params = abi.decode(_params, (Params));\\n\\n // Check GelatoTreasury ETH balance is below threshold.\\n return\\n IGelatoTreasury(params.treasury).userTokenBalance(\\n params.recipient,\\n // Gelato represents ETH as 0xeeeee....eeeee\\n 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\\n ) < params.threshold;\\n }\\n}\\n\",\"keccak256\":\"0x1a127a2c8955525cc2e5cd3c6703edc785c662a79222f524df4574cf47ddd864\",\"license\":\"MIT\"}},\"version\":1}",
"bytecode": "0x608060405234801561001057600080fd5b5061031b806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004361003e36600461016f565b610057565b604051901515815260200160405180910390f35b6000808280602001905181019061006e9190610267565b6020810151815160408084015190517fb47064c800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6024820152939450919291169063b47064c89060440160206040518083038186803b15801561010057600080fd5b505afa158015610114573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061013891906102cc565b109392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561018157600080fd5b813567ffffffffffffffff8082111561019957600080fd5b818401915084601f8301126101ad57600080fd5b8135818111156101bf576101bf610140565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561020557610205610140565b8160405282815287602084870101111561021e57600080fd5b826020860160208301376000928101602001929092525095945050505050565b805173ffffffffffffffffffffffffffffffffffffffff8116811461026257600080fd5b919050565b60006060828403121561027957600080fd5b6040516060810181811067ffffffffffffffff8211171561029c5761029c610140565b6040526102a88361023e565b8152602083015160208201526102c06040840161023e565b60408201529392505050565b6000602082840312156102de57600080fd5b505191905056fea2646970667358221220e4e865f160af7ad5bd6e586c93c567a5f9a5ee5a652397683119d14d6f5e658464736f6c63430008090033", "bytecode": "0x608060405234801561001057600080fd5b5061030c806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004361003e366004610160565b610057565b604051901515815260200160405180910390f35b6000808280602001905181019061006e9190610258565b6020810151815160408084015190517fb47064c800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6024820152939450919291169063b47064c890604401602060405180830381865afa158015610105573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061012991906102bd565b109392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561017257600080fd5b813567ffffffffffffffff8082111561018a57600080fd5b818401915084601f83011261019e57600080fd5b8135818111156101b0576101b0610131565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156101f6576101f6610131565b8160405282815287602084870101111561020f57600080fd5b826020860160208301376000928101602001929092525095945050505050565b805173ffffffffffffffffffffffffffffffffffffffff8116811461025357600080fd5b919050565b60006060828403121561026a57600080fd5b6040516060810181811067ffffffffffffffff8211171561028d5761028d610131565b6040526102998361022f565b8152602083015160208201526102b16040840161022f565b60408201529392505050565b6000602082840312156102cf57600080fd5b505191905056fea2646970667358221220d5d7b760af134f89109f20dbf88fd89e78bb4b81bed97f9aa45772b3775b388964736f6c63430008100033",
"deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004361003e36600461016f565b610057565b604051901515815260200160405180910390f35b6000808280602001905181019061006e9190610267565b6020810151815160408084015190517fb47064c800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6024820152939450919291169063b47064c89060440160206040518083038186803b15801561010057600080fd5b505afa158015610114573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061013891906102cc565b109392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561018157600080fd5b813567ffffffffffffffff8082111561019957600080fd5b818401915084601f8301126101ad57600080fd5b8135818111156101bf576101bf610140565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561020557610205610140565b8160405282815287602084870101111561021e57600080fd5b826020860160208301376000928101602001929092525095945050505050565b805173ffffffffffffffffffffffffffffffffffffffff8116811461026257600080fd5b919050565b60006060828403121561027957600080fd5b6040516060810181811067ffffffffffffffff8211171561029c5761029c610140565b6040526102a88361023e565b8152602083015160208201526102c06040840161023e565b60408201529392505050565b6000602082840312156102de57600080fd5b505191905056fea2646970667358221220e4e865f160af7ad5bd6e586c93c567a5f9a5ee5a652397683119d14d6f5e658464736f6c63430008090033", "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004361003e366004610160565b610057565b604051901515815260200160405180910390f35b6000808280602001905181019061006e9190610258565b6020810151815160408084015190517fb47064c800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff918216600482015273eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6024820152939450919291169063b47064c890604401602060405180830381865afa158015610105573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061012991906102bd565b109392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561017257600080fd5b813567ffffffffffffffff8082111561018a57600080fd5b818401915084601f83011261019e57600080fd5b8135818111156101b0576101b0610131565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156101f6576101f6610131565b8160405282815287602084870101111561020f57600080fd5b826020860160208301376000928101602001929092525095945050505050565b805173ffffffffffffffffffffffffffffffffffffffff8116811461025357600080fd5b919050565b60006060828403121561026a57600080fd5b6040516060810181811067ffffffffffffffff8211171561028d5761028d610131565b6040526102998361022f565b8152602083015160208201526102b16040840161022f565b60408201529392505050565b6000602082840312156102cf57600080fd5b505191905056fea2646970667358221220d5d7b760af134f89109f20dbf88fd89e78bb4b81bed97f9aa45772b3775b388964736f6c63430008100033",
"devdoc": { "devdoc": {
"events": {
"_EventToExposeStructInABI__Params((address,uint256,address))": {
"params": {
"params": "Parameters to encode."
}
}
},
"kind": "dev", "kind": "dev",
"methods": {}, "methods": {
"check(bytes)": {
"params": {
"_params": "Encoded parameters for the drip check."
},
"returns": {
"_0": "Whether the drip should be executed."
}
}
},
"title": "CheckGelatoLow", "title": "CheckGelatoLow",
"version": 1 "version": 1
}, },
"userdoc": { "userdoc": {
"events": {
"_EventToExposeStructInABI__Params((address,uint256,address))": {
"notice": "External event used to help client-side tooling encode parameters."
}
},
"kind": "user", "kind": "user",
"methods": {}, "methods": {
"check(bytes)": {
"notice": "Checks whether a drip should be executable."
}
},
"notice": "DripCheck for checking if an account's Gelato ETH balance is below some threshold.", "notice": "DripCheck for checking if an account's Gelato ETH balance is below some threshold.",
"version": 1 "version": 1
}, },
......
{ {
"address": "0x7e2B28af043c347C18fc37f62B5D001df6BFa26c", "address": "0x5c741a38cb11424711231777D71689C458eE835D",
"abi": [ "abi": [
{ {
"inputs": [ "inputs": [
...@@ -21,37 +21,50 @@ ...@@ -21,37 +21,50 @@
"type": "function" "type": "function"
} }
], ],
"transactionHash": "0x02af8af4f6eda0944e437af66082a71b3a0c9c52f3ab2ac4e4929538f94d9411", "transactionHash": "0xf9c55443c3afb82190503b06ac5914eb3ea85179ec1572a0f22d781452f624dd",
"receipt": { "receipt": {
"to": "0x4e59b44847b379578588920cA78FbF26c0B4956C", "to": "0x4e59b44847b379578588920cA78FbF26c0B4956C",
"from": "0xc37f6a6c4AB335E20d10F034B90386E2fb70bbF5", "from": "0x9C6373dE60c2D3297b18A8f964618ac46E011B58",
"contractAddress": null, "contractAddress": null,
"transactionIndex": 6, "transactionIndex": 129,
"gasUsed": "139230", "gasUsed": "139230",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"blockHash": "0xbf599dc7b5468b1291bd41db9290db892c3e883c7f24cfa123f0ce4fa19d8bf3", "blockHash": "0x5a37331d49bcc789cd37c2dd45be47bb35724fd974aab0573e8b6eb58f1690e5",
"transactionHash": "0x02af8af4f6eda0944e437af66082a71b3a0c9c52f3ab2ac4e4929538f94d9411", "transactionHash": "0xf9c55443c3afb82190503b06ac5914eb3ea85179ec1572a0f22d781452f624dd",
"logs": [], "logs": [],
"blockNumber": 14981108, "blockNumber": 16485630,
"cumulativeGasUsed": "630300", "cumulativeGasUsed": "13196107",
"status": 1, "status": 1,
"byzantium": true "byzantium": true
}, },
"args": [], "args": [],
"numDeployments": 1, "numDeployments": 2,
"solcInputHash": "b09909e91a3ff9823ceba49a3a845230", "solcInputHash": "2373b7ba869baea4fec58e6e7f7b8988",
"metadata": "{\"compiler\":{\"version\":\"0.8.9+commit.e5eed63a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"check\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"title\":\"CheckTrue\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"DripCheck that always returns true.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/universal/drippie/dripchecks/CheckTrue.sol\":\"CheckTrue\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"contracts/universal/drippie/IDripCheck.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.9;\\n\\ninterface IDripCheck {\\n // DripCheck contracts that want to take parameters as inputs MUST expose a struct called\\n // Params and an event _EventForExposingParamsStructInABI(Params params). This makes it\\n // possible to easily encode parameters on the client side. Solidity does not support generics\\n // so it's not possible to do this with explicit typing.\\n\\n function check(bytes memory _params) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x73db6ada63ed1f0eba24efd36099139e66908aa45c79c0d1defe2a59a9e9eb9d\",\"license\":\"MIT\"},\"contracts/universal/drippie/dripchecks/CheckTrue.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.9;\\n\\nimport { IDripCheck } from \\\"../IDripCheck.sol\\\";\\n\\n/**\\n * @title CheckTrue\\n * @notice DripCheck that always returns true.\\n */\\ncontract CheckTrue is IDripCheck {\\n function check(bytes memory) external pure returns (bool) {\\n return true;\\n }\\n}\\n\",\"keccak256\":\"0x2ed60821a4302130b567da45f15dce5a7c41e5d5b016c32ec09c92d4fb5ba6e6\",\"license\":\"MIT\"}},\"version\":1}", "metadata": "{\"compiler\":{\"version\":\"0.8.16+commit.07a7930e\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"check\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"check(bytes)\":{\"params\":{\"_params\":\"Encoded parameters for the drip check.\"},\"returns\":{\"_0\":\"Whether the drip should be executed.\"}}},\"title\":\"CheckTrue\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"check(bytes)\":{\"notice\":\"Checks whether a drip should be executable.\"}},\"notice\":\"DripCheck that always returns true.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/universal/drippie/dripchecks/CheckTrue.sol\":\"CheckTrue\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"contracts/universal/drippie/IDripCheck.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.0;\\n\\ninterface IDripCheck {\\n // DripCheck contracts that want to take parameters as inputs MUST expose a struct called\\n // Params and an event _EventForExposingParamsStructInABI(Params params). This makes it\\n // possible to easily encode parameters on the client side. Solidity does not support generics\\n // so it's not possible to do this with explicit typing.\\n\\n /**\\n * @notice Checks whether a drip should be executable.\\n *\\n * @param _params Encoded parameters for the drip check.\\n *\\n * @return Whether the drip should be executed.\\n */\\n function check(bytes memory _params) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xb52c89360566b2963dfd82cb2cc23f0c3ce4503a69e8563878e8aa80b6c60b3f\",\"license\":\"MIT\"},\"contracts/universal/drippie/dripchecks/CheckTrue.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.8.16;\\n\\nimport { IDripCheck } from \\\"../IDripCheck.sol\\\";\\n\\n/**\\n * @title CheckTrue\\n * @notice DripCheck that always returns true.\\n */\\ncontract CheckTrue is IDripCheck {\\n /**\\n * @inheritdoc IDripCheck\\n */\\n function check(bytes memory) external pure returns (bool) {\\n return true;\\n }\\n}\\n\",\"keccak256\":\"0xf2f5474f12983c30ca4fe9d19e7f88e6d2262e4a6f779e86b4a2117498fdbea5\",\"license\":\"MIT\"}},\"version\":1}",
"bytecode": "0x608060405234801561001057600080fd5b5061018c806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004461003e366004610087565b50600190565b604051901515815260200160405180910390f35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561009957600080fd5b813567ffffffffffffffff808211156100b157600080fd5b818401915084601f8301126100c557600080fd5b8135818111156100d7576100d7610058565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561011d5761011d610058565b8160405282815287602084870101111561013657600080fd5b82602086016020830137600092810160200192909252509594505050505056fea26469706673582212207179cc803626c6d5a28bb5725d572d164ec9b64d4f37f10f220761ee045840fc64736f6c63430008090033", "bytecode": "0x608060405234801561001057600080fd5b5061018c806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004461003e366004610087565b50600190565b604051901515815260200160405180910390f35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561009957600080fd5b813567ffffffffffffffff808211156100b157600080fd5b818401915084601f8301126100c557600080fd5b8135818111156100d7576100d7610058565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561011d5761011d610058565b8160405282815287602084870101111561013657600080fd5b82602086016020830137600092810160200192909252509594505050505056fea26469706673582212200bb899cb0e5f9dce180ebe72a1dfade46ffc5ec3cab398d1f2af09e8b9ce76d164736f6c63430008100033",
"deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004461003e366004610087565b50600190565b604051901515815260200160405180910390f35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561009957600080fd5b813567ffffffffffffffff808211156100b157600080fd5b818401915084601f8301126100c557600080fd5b8135818111156100d7576100d7610058565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561011d5761011d610058565b8160405282815287602084870101111561013657600080fd5b82602086016020830137600092810160200192909252509594505050505056fea26469706673582212207179cc803626c6d5a28bb5725d572d164ec9b64d4f37f10f220761ee045840fc64736f6c63430008090033", "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c64b3bb514610030575b600080fd5b61004461003e366004610087565b50600190565b604051901515815260200160405180910390f35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561009957600080fd5b813567ffffffffffffffff808211156100b157600080fd5b818401915084601f8301126100c557600080fd5b8135818111156100d7576100d7610058565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561011d5761011d610058565b8160405282815287602084870101111561013657600080fd5b82602086016020830137600092810160200192909252509594505050505056fea26469706673582212200bb899cb0e5f9dce180ebe72a1dfade46ffc5ec3cab398d1f2af09e8b9ce76d164736f6c63430008100033",
"devdoc": { "devdoc": {
"kind": "dev", "kind": "dev",
"methods": {}, "methods": {
"check(bytes)": {
"params": {
"_params": "Encoded parameters for the drip check."
},
"returns": {
"_0": "Whether the drip should be executed."
}
}
},
"title": "CheckTrue", "title": "CheckTrue",
"version": 1 "version": 1
}, },
"userdoc": { "userdoc": {
"kind": "user", "kind": "user",
"methods": {}, "methods": {
"check(bytes)": {
"notice": "Checks whether a drip should be executable."
}
},
"notice": "DripCheck that always returns true.", "notice": "DripCheck that always returns true.",
"version": 1 "version": 1
}, },
......
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"language": "Solidity",
"sources": {
"contracts/testing/helpers/TestERC20.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@rari-capital/solmate/src/tokens/ERC20.sol\";\n\ncontract TestERC20 is ERC20 {\n constructor() ERC20(\"TEST\", \"TST\", 18) {}\n\n function mint(address to, uint256 value) public {\n _mint(to, value);\n }\n}\n"
},
"@rari-capital/solmate/src/tokens/ERC20.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\n/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.\n/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)\n/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)\n/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.\nabstract contract ERC20 {\n /*//////////////////////////////////////////////////////////////\n EVENTS\n //////////////////////////////////////////////////////////////*/\n\n event Transfer(address indexed from, address indexed to, uint256 amount);\n\n event Approval(address indexed owner, address indexed spender, uint256 amount);\n\n /*//////////////////////////////////////////////////////////////\n METADATA STORAGE\n //////////////////////////////////////////////////////////////*/\n\n string public name;\n\n string public symbol;\n\n uint8 public immutable decimals;\n\n /*//////////////////////////////////////////////////////////////\n ERC20 STORAGE\n //////////////////////////////////////////////////////////////*/\n\n uint256 public totalSupply;\n\n mapping(address => uint256) public balanceOf;\n\n mapping(address => mapping(address => uint256)) public allowance;\n\n /*//////////////////////////////////////////////////////////////\n EIP-2612 STORAGE\n //////////////////////////////////////////////////////////////*/\n\n uint256 internal immutable INITIAL_CHAIN_ID;\n\n bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;\n\n mapping(address => uint256) public nonces;\n\n /*//////////////////////////////////////////////////////////////\n CONSTRUCTOR\n //////////////////////////////////////////////////////////////*/\n\n constructor(\n string memory _name,\n string memory _symbol,\n uint8 _decimals\n ) {\n name = _name;\n symbol = _symbol;\n decimals = _decimals;\n\n INITIAL_CHAIN_ID = block.chainid;\n INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();\n }\n\n /*//////////////////////////////////////////////////////////////\n ERC20 LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function approve(address spender, uint256 amount) public virtual returns (bool) {\n allowance[msg.sender][spender] = amount;\n\n emit Approval(msg.sender, spender, amount);\n\n return true;\n }\n\n function transfer(address to, uint256 amount) public virtual returns (bool) {\n balanceOf[msg.sender] -= amount;\n\n // Cannot overflow because the sum of all user\n // balances can't exceed the max uint256 value.\n unchecked {\n balanceOf[to] += amount;\n }\n\n emit Transfer(msg.sender, to, amount);\n\n return true;\n }\n\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) public virtual returns (bool) {\n uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.\n\n if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;\n\n balanceOf[from] -= amount;\n\n // Cannot overflow because the sum of all user\n // balances can't exceed the max uint256 value.\n unchecked {\n balanceOf[to] += amount;\n }\n\n emit Transfer(from, to, amount);\n\n return true;\n }\n\n /*//////////////////////////////////////////////////////////////\n EIP-2612 LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) public virtual {\n require(deadline >= block.timestamp, \"PERMIT_DEADLINE_EXPIRED\");\n\n // Unchecked because the only math done is incrementing\n // the owner's nonce which cannot realistically overflow.\n unchecked {\n address recoveredAddress = ecrecover(\n keccak256(\n abi.encodePacked(\n \"\\x19\\x01\",\n DOMAIN_SEPARATOR(),\n keccak256(\n abi.encode(\n keccak256(\n \"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\"\n ),\n owner,\n spender,\n value,\n nonces[owner]++,\n deadline\n )\n )\n )\n ),\n v,\n r,\n s\n );\n\n require(recoveredAddress != address(0) && recoveredAddress == owner, \"INVALID_SIGNER\");\n\n allowance[recoveredAddress][spender] = value;\n }\n\n emit Approval(owner, spender, value);\n }\n\n function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {\n return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();\n }\n\n function computeDomainSeparator() internal view virtual returns (bytes32) {\n return\n keccak256(\n abi.encode(\n keccak256(\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\"),\n keccak256(bytes(name)),\n keccak256(\"1\"),\n block.chainid,\n address(this)\n )\n );\n }\n\n /*//////////////////////////////////////////////////////////////\n INTERNAL MINT/BURN LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function _mint(address to, uint256 amount) internal virtual {\n totalSupply += amount;\n\n // Cannot overflow because the sum of all user\n // balances can't exceed the max uint256 value.\n unchecked {\n balanceOf[to] += amount;\n }\n\n emit Transfer(address(0), to, amount);\n }\n\n function _burn(address from, uint256 amount) internal virtual {\n balanceOf[from] -= amount;\n\n // Cannot underflow because a user's balance\n // will never be larger than the total supply.\n unchecked {\n totalSupply -= amount;\n }\n\n emit Transfer(from, address(0), amount);\n }\n}\n"
},
"contracts/universal/AssetReceiver.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { ERC20 } from \"@rari-capital/solmate/src/tokens/ERC20.sol\";\nimport { ERC721 } from \"@rari-capital/solmate/src/tokens/ERC721.sol\";\nimport { Transactor } from \"./Transactor.sol\";\n\n/**\n * @title AssetReceiver\n * @notice AssetReceiver is a minimal contract for receiving funds assets in the form of either\n * ETH, ERC20 tokens, or ERC721 tokens. Only the contract owner may withdraw the assets.\n */\ncontract AssetReceiver is Transactor {\n /**\n * @notice Emitted when ETH is received by this address.\n *\n * @param from Address that sent ETH to this contract.\n * @param amount Amount of ETH received.\n */\n event ReceivedETH(address indexed from, uint256 amount);\n\n /**\n * @notice Emitted when ETH is withdrawn from this address.\n *\n * @param withdrawer Address that triggered the withdrawal.\n * @param recipient Address that received the withdrawal.\n * @param amount ETH amount withdrawn.\n */\n event WithdrewETH(address indexed withdrawer, address indexed recipient, uint256 amount);\n\n /**\n * @notice Emitted when ERC20 tokens are withdrawn from this address.\n *\n * @param withdrawer Address that triggered the withdrawal.\n * @param recipient Address that received the withdrawal.\n * @param asset Address of the token being withdrawn.\n * @param amount ERC20 amount withdrawn.\n */\n event WithdrewERC20(\n address indexed withdrawer,\n address indexed recipient,\n address indexed asset,\n uint256 amount\n );\n\n /**\n * @notice Emitted when ERC20 tokens are withdrawn from this address.\n *\n * @param withdrawer Address that triggered the withdrawal.\n * @param recipient Address that received the withdrawal.\n * @param asset Address of the token being withdrawn.\n * @param id Token ID being withdrawn.\n */\n event WithdrewERC721(\n address indexed withdrawer,\n address indexed recipient,\n address indexed asset,\n uint256 id\n );\n\n /**\n * @param _owner Initial contract owner.\n */\n constructor(address _owner) Transactor(_owner) {}\n\n /**\n * @notice Make sure we can receive ETH.\n */\n receive() external payable {\n emit ReceivedETH(msg.sender, msg.value);\n }\n\n /**\n * @notice Withdraws full ETH balance to the recipient.\n *\n * @param _to Address to receive the ETH balance.\n */\n function withdrawETH(address payable _to) external onlyOwner {\n withdrawETH(_to, address(this).balance);\n }\n\n /**\n * @notice Withdraws partial ETH balance to the recipient.\n *\n * @param _to Address to receive the ETH balance.\n * @param _amount Amount of ETH to withdraw.\n */\n function withdrawETH(address payable _to, uint256 _amount) public onlyOwner {\n // slither-disable-next-line reentrancy-unlimited-gas\n (bool success, ) = _to.call{ value: _amount }(\"\");\n emit WithdrewETH(msg.sender, _to, _amount);\n }\n\n /**\n * @notice Withdraws full ERC20 balance to the recipient.\n *\n * @param _asset ERC20 token to withdraw.\n * @param _to Address to receive the ERC20 balance.\n */\n function withdrawERC20(ERC20 _asset, address _to) external onlyOwner {\n withdrawERC20(_asset, _to, _asset.balanceOf(address(this)));\n }\n\n /**\n * @notice Withdraws partial ERC20 balance to the recipient.\n *\n * @param _asset ERC20 token to withdraw.\n * @param _to Address to receive the ERC20 balance.\n * @param _amount Amount of ERC20 to withdraw.\n */\n function withdrawERC20(\n ERC20 _asset,\n address _to,\n uint256 _amount\n ) public onlyOwner {\n // slither-disable-next-line unchecked-transfer\n _asset.transfer(_to, _amount);\n // slither-disable-next-line reentrancy-events\n emit WithdrewERC20(msg.sender, _to, address(_asset), _amount);\n }\n\n /**\n * @notice Withdraws ERC721 token to the recipient.\n *\n * @param _asset ERC721 token to withdraw.\n * @param _to Address to receive the ERC721 token.\n * @param _id Token ID of the ERC721 token to withdraw.\n */\n function withdrawERC721(\n ERC721 _asset,\n address _to,\n uint256 _id\n ) external onlyOwner {\n _asset.transferFrom(address(this), _to, _id);\n // slither-disable-next-line reentrancy-events\n emit WithdrewERC721(msg.sender, _to, address(_asset), _id);\n }\n}\n"
},
"@rari-capital/solmate/src/tokens/ERC721.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\n/// @notice Modern, minimalist, and gas efficient ERC-721 implementation.\n/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)\nabstract contract ERC721 {\n /*//////////////////////////////////////////////////////////////\n EVENTS\n //////////////////////////////////////////////////////////////*/\n\n event Transfer(address indexed from, address indexed to, uint256 indexed id);\n\n event Approval(address indexed owner, address indexed spender, uint256 indexed id);\n\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n /*//////////////////////////////////////////////////////////////\n METADATA STORAGE/LOGIC\n //////////////////////////////////////////////////////////////*/\n\n string public name;\n\n string public symbol;\n\n function tokenURI(uint256 id) public view virtual returns (string memory);\n\n /*//////////////////////////////////////////////////////////////\n ERC721 BALANCE/OWNER STORAGE\n //////////////////////////////////////////////////////////////*/\n\n mapping(uint256 => address) internal _ownerOf;\n\n mapping(address => uint256) internal _balanceOf;\n\n function ownerOf(uint256 id) public view virtual returns (address owner) {\n require((owner = _ownerOf[id]) != address(0), \"NOT_MINTED\");\n }\n\n function balanceOf(address owner) public view virtual returns (uint256) {\n require(owner != address(0), \"ZERO_ADDRESS\");\n\n return _balanceOf[owner];\n }\n\n /*//////////////////////////////////////////////////////////////\n ERC721 APPROVAL STORAGE\n //////////////////////////////////////////////////////////////*/\n\n mapping(uint256 => address) public getApproved;\n\n mapping(address => mapping(address => bool)) public isApprovedForAll;\n\n /*//////////////////////////////////////////////////////////////\n CONSTRUCTOR\n //////////////////////////////////////////////////////////////*/\n\n constructor(string memory _name, string memory _symbol) {\n name = _name;\n symbol = _symbol;\n }\n\n /*//////////////////////////////////////////////////////////////\n ERC721 LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function approve(address spender, uint256 id) public virtual {\n address owner = _ownerOf[id];\n\n require(msg.sender == owner || isApprovedForAll[owner][msg.sender], \"NOT_AUTHORIZED\");\n\n getApproved[id] = spender;\n\n emit Approval(owner, spender, id);\n }\n\n function setApprovalForAll(address operator, bool approved) public virtual {\n isApprovedForAll[msg.sender][operator] = approved;\n\n emit ApprovalForAll(msg.sender, operator, approved);\n }\n\n function transferFrom(\n address from,\n address to,\n uint256 id\n ) public virtual {\n require(from == _ownerOf[id], \"WRONG_FROM\");\n\n require(to != address(0), \"INVALID_RECIPIENT\");\n\n require(\n msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id],\n \"NOT_AUTHORIZED\"\n );\n\n // Underflow of the sender's balance is impossible because we check for\n // ownership above and the recipient's balance can't realistically overflow.\n unchecked {\n _balanceOf[from]--;\n\n _balanceOf[to]++;\n }\n\n _ownerOf[id] = to;\n\n delete getApproved[id];\n\n emit Transfer(from, to, id);\n }\n\n function safeTransferFrom(\n address from,\n address to,\n uint256 id\n ) public virtual {\n transferFrom(from, to, id);\n\n if (to.code.length != 0)\n require(\n ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, \"\") ==\n ERC721TokenReceiver.onERC721Received.selector,\n \"UNSAFE_RECIPIENT\"\n );\n }\n\n function safeTransferFrom(\n address from,\n address to,\n uint256 id,\n bytes calldata data\n ) public virtual {\n transferFrom(from, to, id);\n\n if (to.code.length != 0)\n require(\n ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==\n ERC721TokenReceiver.onERC721Received.selector,\n \"UNSAFE_RECIPIENT\"\n );\n }\n\n /*//////////////////////////////////////////////////////////////\n ERC165 LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {\n return\n interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165\n interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721\n interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata\n }\n\n /*//////////////////////////////////////////////////////////////\n INTERNAL MINT/BURN LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function _mint(address to, uint256 id) internal virtual {\n require(to != address(0), \"INVALID_RECIPIENT\");\n\n require(_ownerOf[id] == address(0), \"ALREADY_MINTED\");\n\n // Counter overflow is incredibly unrealistic.\n unchecked {\n _balanceOf[to]++;\n }\n\n _ownerOf[id] = to;\n\n emit Transfer(address(0), to, id);\n }\n\n function _burn(uint256 id) internal virtual {\n address owner = _ownerOf[id];\n\n require(owner != address(0), \"NOT_MINTED\");\n\n // Ownership check above ensures no underflow.\n unchecked {\n _balanceOf[owner]--;\n }\n\n delete _ownerOf[id];\n\n delete getApproved[id];\n\n emit Transfer(owner, address(0), id);\n }\n\n /*//////////////////////////////////////////////////////////////\n INTERNAL SAFE MINT LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function _safeMint(address to, uint256 id) internal virtual {\n _mint(to, id);\n\n if (to.code.length != 0)\n require(\n ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, \"\") ==\n ERC721TokenReceiver.onERC721Received.selector,\n \"UNSAFE_RECIPIENT\"\n );\n }\n\n function _safeMint(\n address to,\n uint256 id,\n bytes memory data\n ) internal virtual {\n _mint(to, id);\n\n if (to.code.length != 0)\n require(\n ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==\n ERC721TokenReceiver.onERC721Received.selector,\n \"UNSAFE_RECIPIENT\"\n );\n }\n}\n\n/// @notice A generic interface for a contract which properly accepts ERC721 tokens.\n/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)\nabstract contract ERC721TokenReceiver {\n function onERC721Received(\n address,\n address,\n uint256,\n bytes calldata\n ) external virtual returns (bytes4) {\n return ERC721TokenReceiver.onERC721Received.selector;\n }\n}\n"
},
"contracts/universal/Transactor.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { Owned } from \"@rari-capital/solmate/src/auth/Owned.sol\";\n\n/**\n * @title Transactor\n * @notice Transactor is a minimal contract that can send transactions.\n */\ncontract Transactor is Owned {\n /**\n * @param _owner Initial contract owner.\n */\n constructor(address _owner) Owned(_owner) {}\n\n /**\n * Sends a CALL to a target address.\n *\n * @param _target Address to call.\n * @param _data Data to send with the call.\n * @param _value ETH value to send with the call.\n *\n * @return Boolean success value.\n * @return Bytes data returned by the call.\n */\n function CALL(\n address _target,\n bytes memory _data,\n uint256 _value\n ) external payable onlyOwner returns (bool, bytes memory) {\n return _target.call{ value: _value }(_data);\n }\n\n /**\n * Sends a DELEGATECALL to a target address.\n *\n * @param _target Address to call.\n * @param _data Data to send with the call.\n *\n * @return Boolean success value.\n * @return Bytes data returned by the call.\n */\n function DELEGATECALL(address _target, bytes memory _data)\n external\n payable\n onlyOwner\n returns (bool, bytes memory)\n {\n // slither-disable-next-line controlled-delegatecall\n return _target.delegatecall(_data);\n }\n}\n"
},
"@rari-capital/solmate/src/auth/Owned.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-only\npragma solidity >=0.8.0;\n\n/// @notice Simple single owner authorization mixin.\n/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)\nabstract contract Owned {\n /*//////////////////////////////////////////////////////////////\n EVENTS\n //////////////////////////////////////////////////////////////*/\n\n event OwnerUpdated(address indexed user, address indexed newOwner);\n\n /*//////////////////////////////////////////////////////////////\n OWNERSHIP STORAGE\n //////////////////////////////////////////////////////////////*/\n\n address public owner;\n\n modifier onlyOwner() virtual {\n require(msg.sender == owner, \"UNAUTHORIZED\");\n\n _;\n }\n\n /*//////////////////////////////////////////////////////////////\n CONSTRUCTOR\n //////////////////////////////////////////////////////////////*/\n\n constructor(address _owner) {\n owner = _owner;\n\n emit OwnerUpdated(address(0), _owner);\n }\n\n /*//////////////////////////////////////////////////////////////\n OWNERSHIP LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function setOwner(address newOwner) public virtual onlyOwner {\n owner = newOwner;\n\n emit OwnerUpdated(msg.sender, newOwner);\n }\n}\n"
},
"contracts/universal/drippie/Drippie.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.16;\n\nimport { AssetReceiver } from \"../AssetReceiver.sol\";\nimport { IDripCheck } from \"./IDripCheck.sol\";\n\n/**\n * @title Drippie\n * @notice Drippie is a system for managing automated contract interactions. A specific interaction\n * is called a \"drip\" and can be executed according to some condition (called a dripcheck)\n * and an execution interval. Drips cannot be executed faster than the execution interval.\n * Drips can trigger arbitrary contract calls where the calling contract is this contract\n * address. Drips can also send ETH value, which makes them ideal for keeping addresses\n * sufficiently funded with ETH. Drippie is designed to be connected with smart contract\n * automation services so that drips can be executed automatically. However, Drippie is\n * specifically designed to be separated from these services so that trust assumptions are\n * better compartmentalized.\n */\ncontract Drippie is AssetReceiver {\n /**\n * @notice Enum representing different status options for a given drip.\n *\n * @custom:value NONE Drip does not exist.\n * @custom:value PAUSED Drip is paused and cannot be executed until reactivated.\n * @custom:value ACTIVE Drip is active and can be executed.\n * @custom:value ARCHIVED Drip is archived and can no longer be executed or reactivated.\n */\n enum DripStatus {\n NONE,\n PAUSED,\n ACTIVE,\n ARCHIVED\n }\n\n /**\n * @notice Represents a drip action.\n */\n struct DripAction {\n address payable target;\n bytes data;\n uint256 value;\n }\n\n /**\n * @notice Represents the configuration for a given drip.\n */\n struct DripConfig {\n bool reentrant;\n uint256 interval;\n IDripCheck dripcheck;\n bytes checkparams;\n DripAction[] actions;\n }\n\n /**\n * @notice Represents the state of an active drip.\n */\n struct DripState {\n DripStatus status;\n DripConfig config;\n uint256 last;\n uint256 count;\n }\n\n /**\n * @notice Emitted when a new drip is created.\n *\n * @param nameref Indexed name parameter (hashed).\n * @param name Unindexed name parameter (unhashed).\n * @param config Config for the created drip.\n */\n event DripCreated(\n // Emit name twice because indexed version is hashed.\n string indexed nameref,\n string name,\n DripConfig config\n );\n\n /**\n * @notice Emitted when a drip status is updated.\n *\n * @param nameref Indexed name parameter (hashed).\n * @param name Unindexed name parameter (unhashed).\n * @param status New drip status.\n */\n event DripStatusUpdated(\n // Emit name twice because indexed version is hashed.\n string indexed nameref,\n string name,\n DripStatus status\n );\n\n /**\n * @notice Emitted when a drip is executed.\n *\n * @param nameref Indexed name parameter (hashed).\n * @param name Unindexed name parameter (unhashed).\n * @param executor Address that executed the drip.\n * @param timestamp Time when the drip was executed.\n */\n event DripExecuted(\n // Emit name twice because indexed version is hashed.\n string indexed nameref,\n string name,\n address executor,\n uint256 timestamp\n );\n\n /**\n * @notice Maps from drip names to drip states.\n */\n mapping(string => DripState) public drips;\n\n /**\n * @param _owner Initial contract owner.\n */\n constructor(address _owner) AssetReceiver(_owner) {}\n\n /**\n * @notice Creates a new drip with the given name and configuration. Once created, drips cannot\n * be modified in any way (this is a security measure). If you want to update a drip,\n * simply pause (and potentially archive) the existing drip and create a new one.\n *\n * @param _name Name of the drip.\n * @param _config Configuration for the drip.\n */\n function create(string calldata _name, DripConfig calldata _config) external onlyOwner {\n // Make sure this drip doesn't already exist. We *must* guarantee that no other function\n // will ever set the status of a drip back to NONE after it's been created. This is why\n // archival is a separate status.\n require(\n drips[_name].status == DripStatus.NONE,\n \"Drippie: drip with that name already exists\"\n );\n\n // Validate the drip interval, only allowing an interval of zero if the drip has explicitly\n // been marked as reentrant. Prevents client-side bugs making a drip infinitely executable\n // within the same block (of course, restricted by gas limits).\n if (_config.reentrant) {\n require(\n _config.interval == 0,\n \"Drippie: if allowing reentrant drip, must set interval to zero\"\n );\n } else {\n require(\n _config.interval > 0,\n \"Drippie: interval must be greater than zero if drip is not reentrant\"\n );\n }\n\n // We initialize this way because Solidity won't let us copy arrays into storage yet.\n DripState storage state = drips[_name];\n state.status = DripStatus.PAUSED;\n state.config.reentrant = _config.reentrant;\n state.config.interval = _config.interval;\n state.config.dripcheck = _config.dripcheck;\n state.config.checkparams = _config.checkparams;\n\n // Solidity doesn't let us copy arrays into storage, so we push each array one by one.\n for (uint256 i = 0; i < _config.actions.length; i++) {\n state.config.actions.push(_config.actions[i]);\n }\n\n // Tell the world!\n emit DripCreated(_name, _name, _config);\n }\n\n /**\n * @notice Sets the status for a given drip. The behavior of this function depends on the\n * status that the user is trying to set. A drip can always move between ACTIVE and\n * PAUSED, but it can never move back to NONE and once ARCHIVED, it can never move back\n * to ACTIVE or PAUSED.\n *\n * @param _name Name of the drip to update.\n * @param _status New drip status.\n */\n function status(string calldata _name, DripStatus _status) external onlyOwner {\n // Make sure we can never set drip status back to NONE. A simple security measure to\n // prevent accidental overwrites if this code is ever updated down the line.\n require(\n _status != DripStatus.NONE,\n \"Drippie: drip status can never be set back to NONE after creation\"\n );\n\n // Load the drip status once to avoid unnecessary SLOADs.\n DripStatus curr = drips[_name].status;\n\n // Make sure the drip in question actually exists. Not strictly necessary but there doesn't\n // seem to be any clear reason why you would want to do this, and it may save some gas in\n // the case of a front-end bug.\n require(\n curr != DripStatus.NONE,\n \"Drippie: drip with that name does not exist and cannot be updated\"\n );\n\n // Once a drip has been archived, it cannot be un-archived. This is, after all, the entire\n // point of archiving a drip.\n require(\n curr != DripStatus.ARCHIVED,\n \"Drippie: drip with that name has been archived and cannot be updated\"\n );\n\n // Although not strictly necessary, we make sure that the status here is actually changing.\n // This may save the client some gas if there's a front-end bug and the user accidentally\n // tries to \"change\" the status to the same value as before.\n require(\n curr != _status,\n \"Drippie: cannot set drip status to the same status as its current status\"\n );\n\n // If the user is trying to archive this drip, make sure the drip has been paused. We do\n // not allow users to archive active drips so that the effects of this action are more\n // abundantly clear.\n if (_status == DripStatus.ARCHIVED) {\n require(\n curr == DripStatus.PAUSED,\n \"Drippie: drip must first be paused before being archived\"\n );\n }\n\n // If we made it here then we can safely update the status.\n drips[_name].status = _status;\n emit DripStatusUpdated(_name, _name, _status);\n }\n\n /**\n * @notice Checks if a given drip is executable.\n *\n * @param _name Drip to check.\n *\n * @return True if the drip is executable, reverts otherwise.\n */\n function executable(string calldata _name) public view returns (bool) {\n DripState storage state = drips[_name];\n\n // Only allow active drips to be executed, an obvious security measure.\n require(\n state.status == DripStatus.ACTIVE,\n \"Drippie: selected drip does not exist or is not currently active\"\n );\n\n // Don't drip if the drip interval has not yet elapsed since the last time we dripped. This\n // is a safety measure that prevents a malicious recipient from, e.g., spending all of\n // their funds and repeatedly requesting new drips. Limits the potential impact of a\n // compromised recipient to just a single drip interval, after which the drip can be paused\n // by the owner address.\n require(\n state.last + state.config.interval <= block.timestamp,\n \"Drippie: drip interval has not elapsed since last drip\"\n );\n\n // Make sure we're allowed to execute this drip.\n require(\n state.config.dripcheck.check(state.config.checkparams),\n \"Drippie: dripcheck failed so drip is not yet ready to be triggered\"\n );\n\n // Alright, we're good to execute.\n return true;\n }\n\n /**\n * @notice Triggers a drip. This function is deliberately left as a public function because the\n * assumption being made here is that setting the drip to ACTIVE is an affirmative\n * signal that the drip should be executable according to the drip parameters, drip\n * check, and drip interval. Note that drip parameters are read entirely from the state\n * and are not supplied as user input, so there should not be any way for a\n * non-authorized user to influence the behavior of the drip. Note that the drip check\n * is executed only **once** at the beginning of the call to the drip function and will\n * not be executed again between the drip actions within this call.\n *\n * @param _name Name of the drip to trigger.\n */\n function drip(string calldata _name) external {\n DripState storage state = drips[_name];\n\n // Make sure the drip can be executed. Since executable reverts if the drip is not ready to\n // be executed, we don't need to do an assertion that the returned value is true.\n executable(_name);\n\n // Update the last execution time for this drip before the call. Note that it's entirely\n // possible for a drip to be executed multiple times per block or even multiple times\n // within the same transaction (via re-entrancy) if the drip interval is set to zero. Users\n // should set a drip interval of 1 if they'd like the drip to be executed only once per\n // block (since this will then prevent re-entrancy).\n state.last = block.timestamp;\n\n // Update the number of times this drip has been executed. Although this increases the cost\n // of using Drippie, it slightly simplifies the client-side by not having to worry about\n // counting drips via events. Useful for monitoring the rate of drip execution.\n state.count++;\n\n // Execute each action in the drip. We allow drips to have multiple actions because there\n // are scenarios in which a contract must do multiple things atomically. For example, the\n // contract may need to withdraw ETH from one account and then deposit that ETH into\n // another account within the same transaction.\n uint256 len = state.config.actions.length;\n for (uint256 i = 0; i < len; i++) {\n // Must be marked as \"storage\" because copying structs into memory is not yet supported\n // by Solidity. Won't significantly reduce gas costs but at least makes it easier to\n // read what the rest of this section is doing.\n DripAction storage action = state.config.actions[i];\n\n // Actually execute the action. We could use ExcessivelySafeCall here but not strictly\n // necessary (worst case, a drip gets bricked IFF the target is malicious, doubt this\n // will ever happen in practice). Could save a marginal amount of gas to ignore the\n // returndata.\n // slither-disable-next-line calls-loop\n (bool success, ) = action.target.call{ value: action.value }(action.data);\n\n // Generally should not happen, but could if there's a misconfiguration (e.g., passing\n // the wrong data to the target contract), the recipient is not payable, or\n // insufficient gas was supplied to this transaction. We revert so the drip can be\n // fixed and triggered again later. Means we cannot emit an event to alert of the\n // failure, but can reasonably be detected by off-chain services even without an event.\n // Note that this forces the drip executor to supply sufficient gas to the call\n // (assuming there is some sufficient gas limit that exists, otherwise the drip will\n // not execute).\n require(\n success,\n \"Drippie: drip was unsuccessful, please check your configuration for mistakes\"\n );\n }\n\n emit DripExecuted(_name, _name, msg.sender, block.timestamp);\n }\n}\n"
},
"contracts/universal/drippie/IDripCheck.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IDripCheck {\n // DripCheck contracts that want to take parameters as inputs MUST expose a struct called\n // Params and an event _EventForExposingParamsStructInABI(Params params). This makes it\n // possible to easily encode parameters on the client side. Solidity does not support generics\n // so it's not possible to do this with explicit typing.\n\n /**\n * @notice Checks whether a drip should be executable.\n *\n * @param _params Encoded parameters for the drip check.\n *\n * @return Whether the drip should be executed.\n */\n function check(bytes memory _params) external view returns (bool);\n}\n"
},
"contracts/universal/drippie/dripchecks/CheckTrue.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.16;\n\nimport { IDripCheck } from \"../IDripCheck.sol\";\n\n/**\n * @title CheckTrue\n * @notice DripCheck that always returns true.\n */\ncontract CheckTrue is IDripCheck {\n /**\n * @inheritdoc IDripCheck\n */\n function check(bytes memory) external pure returns (bool) {\n return true;\n }\n}\n"
},
"contracts/universal/drippie/dripchecks/CheckGelatoLow.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.16;\n\nimport { IDripCheck } from \"../IDripCheck.sol\";\n\ninterface IGelatoTreasury {\n function userTokenBalance(address _user, address _token) external view returns (uint256);\n}\n\n/**\n * @title CheckGelatoLow\n * @notice DripCheck for checking if an account's Gelato ETH balance is below some threshold.\n */\ncontract CheckGelatoLow is IDripCheck {\n struct Params {\n address treasury;\n uint256 threshold;\n address recipient;\n }\n\n /**\n * @notice External event used to help client-side tooling encode parameters.\n *\n * @param params Parameters to encode.\n */\n event _EventToExposeStructInABI__Params(Params params);\n\n /**\n * @inheritdoc IDripCheck\n */\n function check(bytes memory _params) external view returns (bool) {\n Params memory params = abi.decode(_params, (Params));\n\n // Check GelatoTreasury ETH balance is below threshold.\n return\n IGelatoTreasury(params.treasury).userTokenBalance(\n params.recipient,\n // Gelato represents ETH as 0xeeeee....eeeee\n 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\n ) < params.threshold;\n }\n}\n"
},
"contracts/universal/drippie/dripchecks/CheckBalanceLow.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.16;\n\nimport { IDripCheck } from \"../IDripCheck.sol\";\n\n/**\n * @title CheckBalanceLow\n * @notice DripCheck for checking if an account's balance is below a given threshold.\n */\ncontract CheckBalanceLow is IDripCheck {\n struct Params {\n address target;\n uint256 threshold;\n }\n\n /**\n * @notice External event used to help client-side tooling encode parameters.\n *\n * @param params Parameters to encode.\n */\n event _EventToExposeStructInABI__Params(Params params);\n\n /**\n * @inheritdoc IDripCheck\n */\n function check(bytes memory _params) external view returns (bool) {\n Params memory params = abi.decode(_params, (Params));\n\n // Check target ETH balance is below threshold.\n return params.target.balance < params.threshold;\n }\n}\n"
},
"contracts/universal/drippie/dripchecks/CheckBalanceHigh.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.16;\n\nimport { IDripCheck } from \"../IDripCheck.sol\";\n\n/**\n * @title CheckBalanceHigh\n * @notice DripCheck for checking if an account's balance is above a given threshold.\n */\ncontract CheckBalanceHigh is IDripCheck {\n struct Params {\n address target;\n uint256 threshold;\n }\n\n /**\n * @notice External event used to help client-side tooling encode parameters.\n *\n * @param params Parameters to encode.\n */\n event _EventToExposeStructInABI__Params(Params params);\n\n /**\n * @inheritdoc IDripCheck\n */\n function check(bytes memory _params) external view returns (bool) {\n Params memory params = abi.decode(_params, (Params));\n\n // Check target balance is above threshold.\n return params.target.balance > params.threshold;\n }\n}\n"
},
"contracts/testing/helpers/TestERC721.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { ERC721 } from \"@rari-capital/solmate/src/tokens/ERC721.sol\";\n\ncontract TestERC721 is ERC721 {\n constructor() ERC721(\"TEST\", \"TST\") {}\n\n function mint(address to, uint256 tokenId) public {\n _mint(to, tokenId);\n }\n\n function tokenURI(uint256) public pure virtual override returns (string memory) {}\n}\n"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 10000
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata",
"devdoc",
"userdoc",
"storageLayout",
"evm.gasEstimates"
],
"": [
"ast"
]
}
},
"metadata": {
"useLiteralContent": true
}
}
}
\ No newline at end of file
{ {
"address": "0x1BEb19F1685ddF2F774884902119Fa2FA5d8f509", "address": "0xbeD744818e96AAD8a51324291a6f6Cb20A0c22be",
"abi": [ "abi": [
{ {
"inputs": [], "inputs": [],
...@@ -67,6 +67,29 @@ ...@@ -67,6 +67,29 @@
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "type": "function"
}, },
{
"inputs": [
{
"internalType": "address",
"name": "_about",
"type": "address"
},
{
"internalType": "bytes32",
"name": "_key",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_val",
"type": "bytes"
}
],
"name": "attest",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
...@@ -110,28 +133,28 @@ ...@@ -110,28 +133,28 @@
"type": "function" "type": "function"
} }
], ],
"transactionHash": "0xfea89fc1eefeaa83bd8d88eb9002594746c12b6761d674f82e821567483df315", "transactionHash": "0x4c0aae61c0030872cb5f8654c36fbc192cfab6f09f93df5c0f80268382d964e9",
"receipt": { "receipt": {
"to": "0x4e59b44847b379578588920cA78FbF26c0B4956C", "to": "0x4e59b44847b379578588920cA78FbF26c0B4956C",
"from": "0x9C6373dE60c2D3297b18A8f964618ac46E011B58", "from": "0x956a5152D0f498dBA0c5966577bb44262F8F7078",
"contractAddress": null, "contractAddress": null,
"transactionIndex": 0, "transactionIndex": 1,
"gasUsed": "645110", "gasUsed": "666309",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"blockHash": "0x6043e32ed00ca470e6676a00f619eb1fed995b06b0159f77ff545b950bae515a", "blockHash": "0xe683044625d767223da2ecd65fd81a34251ffbef27abd3302530be36e99971d0",
"transactionHash": "0xfea89fc1eefeaa83bd8d88eb9002594746c12b6761d674f82e821567483df315", "transactionHash": "0x4c0aae61c0030872cb5f8654c36fbc192cfab6f09f93df5c0f80268382d964e9",
"logs": [], "logs": [],
"blockNumber": 3451191, "blockNumber": 4667191,
"cumulativeGasUsed": "645110", "cumulativeGasUsed": "666309",
"status": 1, "status": 1,
"byzantium": true "byzantium": true
}, },
"args": [], "args": [],
"numDeployments": 1, "numDeployments": 2,
"solcInputHash": "45837d34ff24b9cb2ae34232b60ea874", "solcInputHash": "3aa2ad7d005d9515a1f12df8da17d178",
"metadata": "{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"creator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"about\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"key\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"val\",\"type\":\"bytes\"}],\"name\":\"AttestationCreated\",\"type\":\"event\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"about\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"key\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"val\",\"type\":\"bytes\"}],\"internalType\":\"struct AttestationStation.AttestationData[]\",\"name\":\"_attestations\",\"type\":\"tuple[]\"}],\"name\":\"attest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"attestations\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Optimism CollectiveGitcoin\",\"events\":{\"AttestationCreated(address,address,bytes32,bytes)\":{\"params\":{\"about\":\"Address attestation is about.\",\"creator\":\"Address that made the attestation.\",\"key\":\"Key of the attestation.\",\"val\":\"Value of the attestation.\"}}},\"kind\":\"dev\",\"methods\":{\"attest((address,bytes32,bytes)[])\":{\"params\":{\"_attestations\":\"An array of attestation data.\"}},\"constructor\":{\"custom:semver\":\"1.0.0\"},\"version()\":{\"returns\":{\"_0\":\"Semver contract version as a string.\"}}},\"title\":\"AttestationStation\",\"version\":1},\"userdoc\":{\"events\":{\"AttestationCreated(address,address,bytes32,bytes)\":{\"notice\":\"Emitted when Attestation is created.\"}},\"kind\":\"user\",\"methods\":{\"attest((address,bytes32,bytes)[])\":{\"notice\":\"Allows anyone to create attestations.\"},\"attestations(address,address,bytes32)\":{\"notice\":\"Maps addresses to attestations. Creator => About => Key => Value.\"},\"version()\":{\"notice\":\"Returns the full semver contract version.\"}},\"notice\":\"Where attestations live.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/universal/op-nft/AttestationStation.sol\":\"AttestationStation\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.15;\\n\\nimport { Strings } from \\\"@openzeppelin/contracts/utils/Strings.sol\\\";\\n\\n/**\\n * @title Semver\\n * @notice Semver is a simple contract for managing contract versions.\\n */\\ncontract Semver {\\n /**\\n * @notice Contract version number (major).\\n */\\n uint256 private immutable MAJOR_VERSION;\\n\\n /**\\n * @notice Contract version number (minor).\\n */\\n uint256 private immutable MINOR_VERSION;\\n\\n /**\\n * @notice Contract version number (patch).\\n */\\n uint256 private immutable PATCH_VERSION;\\n\\n /**\\n * @param _major Version number (major).\\n * @param _minor Version number (minor).\\n * @param _patch Version number (patch).\\n */\\n constructor(\\n uint256 _major,\\n uint256 _minor,\\n uint256 _patch\\n ) {\\n MAJOR_VERSION = _major;\\n MINOR_VERSION = _minor;\\n PATCH_VERSION = _patch;\\n }\\n\\n /**\\n * @notice Returns the full semver contract version.\\n *\\n * @return Semver contract version as a string.\\n */\\n function version() public view returns (string memory) {\\n return\\n string(\\n abi.encodePacked(\\n Strings.toString(MAJOR_VERSION),\\n \\\".\\\",\\n Strings.toString(MINOR_VERSION),\\n \\\".\\\",\\n Strings.toString(PATCH_VERSION)\\n )\\n );\\n }\\n}\\n\",\"keccak256\":\"0x979b13465de4996a1105850abbf48abe7f71d5e18a8d4af318597ee14c165fdf\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Strings.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev String operations.\\n */\\nlibrary Strings {\\n bytes16 private constant _HEX_SYMBOLS = \\\"0123456789abcdef\\\";\\n uint8 private constant _ADDRESS_LENGTH = 20;\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\\n */\\n function toString(uint256 value) internal pure returns (string memory) {\\n // Inspired by OraclizeAPI's implementation - MIT licence\\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\\n\\n if (value == 0) {\\n return \\\"0\\\";\\n }\\n uint256 temp = value;\\n uint256 digits;\\n while (temp != 0) {\\n digits++;\\n temp /= 10;\\n }\\n bytes memory buffer = new bytes(digits);\\n while (value != 0) {\\n digits -= 1;\\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\\n value /= 10;\\n }\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\\n */\\n function toHexString(uint256 value) internal pure returns (string memory) {\\n if (value == 0) {\\n return \\\"0x00\\\";\\n }\\n uint256 temp = value;\\n uint256 length = 0;\\n while (temp != 0) {\\n length++;\\n temp >>= 8;\\n }\\n return toHexString(value, length);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\\n */\\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\\n bytes memory buffer = new bytes(2 * length + 2);\\n buffer[0] = \\\"0\\\";\\n buffer[1] = \\\"x\\\";\\n for (uint256 i = 2 * length + 1; i > 1; --i) {\\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\\n value >>= 4;\\n }\\n require(value == 0, \\\"Strings: hex length insufficient\\\");\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\\n */\\n function toHexString(address addr) internal pure returns (string memory) {\\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\\n }\\n}\\n\",\"keccak256\":\"0xaf159a8b1923ad2a26d516089bceca9bdeaeacd04be50983ea00ba63070f08a3\",\"license\":\"MIT\"},\"contracts/universal/op-nft/AttestationStation.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.8.15;\\n\\nimport { Semver } from \\\"@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol\\\";\\n\\n/**\\n * @title AttestationStation\\n * @author Optimism Collective\\n * @author Gitcoin\\n * @notice Where attestations live.\\n */\\ncontract AttestationStation is Semver {\\n /**\\n * @notice Struct representing data that is being attested.\\n *\\n * @custom:field about Address for which the attestation is about.\\n * @custom:field key A bytes32 key for the attestation.\\n * @custom:field val The attestation as arbitrary bytes.\\n */\\n struct AttestationData {\\n address about;\\n bytes32 key;\\n bytes val;\\n }\\n\\n /**\\n * @notice Maps addresses to attestations. Creator => About => Key => Value.\\n */\\n mapping(address => mapping(address => mapping(bytes32 => bytes))) public attestations;\\n\\n /**\\n * @notice Emitted when Attestation is created.\\n *\\n * @param creator Address that made the attestation.\\n * @param about Address attestation is about.\\n * @param key Key of the attestation.\\n * @param val Value of the attestation.\\n */\\n event AttestationCreated(\\n address indexed creator,\\n address indexed about,\\n bytes32 indexed key,\\n bytes val\\n );\\n\\n /**\\n * @custom:semver 1.0.0\\n */\\n constructor() Semver(1, 0, 0) {}\\n\\n /**\\n * @notice Allows anyone to create attestations.\\n *\\n * @param _attestations An array of attestation data.\\n */\\n function attest(AttestationData[] memory _attestations) public {\\n uint256 length = _attestations.length;\\n for (uint256 i = 0; i < length; ) {\\n AttestationData memory attestation = _attestations[i];\\n attestations[msg.sender][attestation.about][attestation.key] = attestation.val;\\n\\n emit AttestationCreated(\\n msg.sender,\\n attestation.about,\\n attestation.key,\\n attestation.val\\n );\\n\\n unchecked {\\n ++i;\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x670fde721c291828c3f6343a40bb315dbbcc5e4411125158892ffe54459d69b7\",\"license\":\"MIT\"}},\"version\":1}", "metadata": "{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"creator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"about\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"key\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"val\",\"type\":\"bytes\"}],\"name\":\"AttestationCreated\",\"type\":\"event\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"about\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"key\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"val\",\"type\":\"bytes\"}],\"internalType\":\"struct AttestationStation.AttestationData[]\",\"name\":\"_attestations\",\"type\":\"tuple[]\"}],\"name\":\"attest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_about\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"_key\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"_val\",\"type\":\"bytes\"}],\"name\":\"attest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"attestations\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Optimism CollectiveGitcoin\",\"events\":{\"AttestationCreated(address,address,bytes32,bytes)\":{\"params\":{\"about\":\"Address attestation is about.\",\"creator\":\"Address that made the attestation.\",\"key\":\"Key of the attestation.\",\"val\":\"Value of the attestation.\"}}},\"kind\":\"dev\",\"methods\":{\"attest((address,bytes32,bytes)[])\":{\"params\":{\"_attestations\":\"An array of attestation data.\"}},\"attest(address,bytes32,bytes)\":{\"params\":{\"_about\":\"Address that the attestation is about.\",\"_key\":\"A key used to namespace the attestation.\",\"_val\":\"An arbitrary value stored as part of the attestation.\"}},\"constructor\":{\"custom:semver\":\"1.1.0\"},\"version()\":{\"returns\":{\"_0\":\"Semver contract version as a string.\"}}},\"title\":\"AttestationStation\",\"version\":1},\"userdoc\":{\"events\":{\"AttestationCreated(address,address,bytes32,bytes)\":{\"notice\":\"Emitted when Attestation is created.\"}},\"kind\":\"user\",\"methods\":{\"attest((address,bytes32,bytes)[])\":{\"notice\":\"Allows anyone to create attestations.\"},\"attest(address,bytes32,bytes)\":{\"notice\":\"Allows anyone to create an attestation.\"},\"attestations(address,address,bytes32)\":{\"notice\":\"Maps addresses to attestations. Creator => About => Key => Value.\"},\"version()\":{\"notice\":\"Returns the full semver contract version.\"}},\"notice\":\"Where attestations live.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/universal/op-nft/AttestationStation.sol\":\"AttestationStation\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.15;\\n\\nimport { Strings } from \\\"@openzeppelin/contracts/utils/Strings.sol\\\";\\n\\n/**\\n * @title Semver\\n * @notice Semver is a simple contract for managing contract versions.\\n */\\ncontract Semver {\\n /**\\n * @notice Contract version number (major).\\n */\\n uint256 private immutable MAJOR_VERSION;\\n\\n /**\\n * @notice Contract version number (minor).\\n */\\n uint256 private immutable MINOR_VERSION;\\n\\n /**\\n * @notice Contract version number (patch).\\n */\\n uint256 private immutable PATCH_VERSION;\\n\\n /**\\n * @param _major Version number (major).\\n * @param _minor Version number (minor).\\n * @param _patch Version number (patch).\\n */\\n constructor(\\n uint256 _major,\\n uint256 _minor,\\n uint256 _patch\\n ) {\\n MAJOR_VERSION = _major;\\n MINOR_VERSION = _minor;\\n PATCH_VERSION = _patch;\\n }\\n\\n /**\\n * @notice Returns the full semver contract version.\\n *\\n * @return Semver contract version as a string.\\n */\\n function version() public view returns (string memory) {\\n return\\n string(\\n abi.encodePacked(\\n Strings.toString(MAJOR_VERSION),\\n \\\".\\\",\\n Strings.toString(MINOR_VERSION),\\n \\\".\\\",\\n Strings.toString(PATCH_VERSION)\\n )\\n );\\n }\\n}\\n\",\"keccak256\":\"0x979b13465de4996a1105850abbf48abe7f71d5e18a8d4af318597ee14c165fdf\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Strings.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev String operations.\\n */\\nlibrary Strings {\\n bytes16 private constant _HEX_SYMBOLS = \\\"0123456789abcdef\\\";\\n uint8 private constant _ADDRESS_LENGTH = 20;\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\\n */\\n function toString(uint256 value) internal pure returns (string memory) {\\n // Inspired by OraclizeAPI's implementation - MIT licence\\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\\n\\n if (value == 0) {\\n return \\\"0\\\";\\n }\\n uint256 temp = value;\\n uint256 digits;\\n while (temp != 0) {\\n digits++;\\n temp /= 10;\\n }\\n bytes memory buffer = new bytes(digits);\\n while (value != 0) {\\n digits -= 1;\\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\\n value /= 10;\\n }\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\\n */\\n function toHexString(uint256 value) internal pure returns (string memory) {\\n if (value == 0) {\\n return \\\"0x00\\\";\\n }\\n uint256 temp = value;\\n uint256 length = 0;\\n while (temp != 0) {\\n length++;\\n temp >>= 8;\\n }\\n return toHexString(value, length);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\\n */\\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\\n bytes memory buffer = new bytes(2 * length + 2);\\n buffer[0] = \\\"0\\\";\\n buffer[1] = \\\"x\\\";\\n for (uint256 i = 2 * length + 1; i > 1; --i) {\\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\\n value >>= 4;\\n }\\n require(value == 0, \\\"Strings: hex length insufficient\\\");\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\\n */\\n function toHexString(address addr) internal pure returns (string memory) {\\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\\n }\\n}\\n\",\"keccak256\":\"0xaf159a8b1923ad2a26d516089bceca9bdeaeacd04be50983ea00ba63070f08a3\",\"license\":\"MIT\"},\"contracts/universal/op-nft/AttestationStation.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.8.15;\\n\\nimport { Semver } from \\\"@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol\\\";\\n\\n/**\\n * @title AttestationStation\\n * @author Optimism Collective\\n * @author Gitcoin\\n * @notice Where attestations live.\\n */\\ncontract AttestationStation is Semver {\\n /**\\n * @notice Struct representing data that is being attested.\\n *\\n * @custom:field about Address for which the attestation is about.\\n * @custom:field key A bytes32 key for the attestation.\\n * @custom:field val The attestation as arbitrary bytes.\\n */\\n struct AttestationData {\\n address about;\\n bytes32 key;\\n bytes val;\\n }\\n\\n /**\\n * @notice Maps addresses to attestations. Creator => About => Key => Value.\\n */\\n mapping(address => mapping(address => mapping(bytes32 => bytes))) public attestations;\\n\\n /**\\n * @notice Emitted when Attestation is created.\\n *\\n * @param creator Address that made the attestation.\\n * @param about Address attestation is about.\\n * @param key Key of the attestation.\\n * @param val Value of the attestation.\\n */\\n event AttestationCreated(\\n address indexed creator,\\n address indexed about,\\n bytes32 indexed key,\\n bytes val\\n );\\n\\n /**\\n * @custom:semver 1.1.0\\n */\\n constructor() Semver(1, 1, 0) {}\\n\\n /**\\n * @notice Allows anyone to create an attestation.\\n *\\n * @param _about Address that the attestation is about.\\n * @param _key A key used to namespace the attestation.\\n * @param _val An arbitrary value stored as part of the attestation.\\n */\\n function attest(\\n address _about,\\n bytes32 _key,\\n bytes memory _val\\n ) public {\\n attestations[msg.sender][_about][_key] = _val;\\n\\n emit AttestationCreated(msg.sender, _about, _key, _val);\\n }\\n\\n /**\\n * @notice Allows anyone to create attestations.\\n *\\n * @param _attestations An array of attestation data.\\n */\\n function attest(AttestationData[] calldata _attestations) external {\\n uint256 length = _attestations.length;\\n for (uint256 i = 0; i < length; ) {\\n AttestationData memory attestation = _attestations[i];\\n\\n attest(attestation.about, attestation.key, attestation.val);\\n\\n unchecked {\\n ++i;\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x80718fdc09061ea8f8b99a0b846eee46c5a2a1372dbb0cbc3d40953cb3af19e0\",\"license\":\"MIT\"}},\"version\":1}",
"bytecode": "0x60e060405234801561001057600080fd5b5060016080819052600060a081905260c081905280610aba61004a8239600061018f015260006101660152600061013d0152610aba6000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806329b42cb51461004657806354fd4d501461006f5780635eb5ea1014610077575b600080fd5b610059610054366004610437565b61008c565b60405161006691906104ed565b60405180910390f35b610059610136565b61008a6100853660046105ae565b6101d9565b005b60006020818152938152604080822085529281528281209093528252902080546100b590610737565b80601f01602080910402602001604051908101604052809291908181526020018280546100e190610737565b801561012e5780601f106101035761010080835404028352916020019161012e565b820191906000526020600020905b81548152906001019060200180831161011157829003601f168201915b505050505081565b60606101617f00000000000000000000000000000000000000000000000000000000000000006102d1565b61018a7f00000000000000000000000000000000000000000000000000000000000000006102d1565b6101b37f00000000000000000000000000000000000000000000000000000000000000006102d1565b6040516020016101c59392919061078a565b604051602081830303815290604052905090565b805160005b818110156102cc5760008382815181106101fa576101fa610800565b602090810291909101810151604080820151336000908152808552828120845173ffffffffffffffffffffffffffffffffffffffff1682528552828120848601518252909452922090925090610250908261087d565b508060200151816000015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f28710dfecab43d1e29e02aa56b2e1e610c0bae19135c9cf7a83a1adb6df96d8584604001516040516102bb91906104ed565b60405180910390a4506001016101de565b505050565b60608160000361031457505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b811561033e5780610328816109c6565b91506103379050600a83610a2d565b9150610318565b60008167ffffffffffffffff81111561035957610359610507565b6040519080825280601f01601f191660200182016040528015610383576020820181803683370190505b5090505b841561040657610398600183610a41565b91506103a5600a86610a58565b6103b0906030610a6c565b60f81b8183815181106103c5576103c5610800565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053506103ff600a86610a2d565b9450610387565b949350505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461043257600080fd5b919050565b60008060006060848603121561044c57600080fd5b6104558461040e565b92506104636020850161040e565b9150604084013590509250925092565b60005b8381101561048e578181015183820152602001610476565b8381111561049d576000848401525b50505050565b600081518084526104bb816020860160208601610473565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061050060208301846104a3565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516060810167ffffffffffffffff8111828210171561055957610559610507565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156105a6576105a6610507565b604052919050565b600060208083850312156105c157600080fd5b823567ffffffffffffffff808211156105d957600080fd5b818501915085601f8301126105ed57600080fd5b8135818111156105ff576105ff610507565b8060051b61060e85820161055f565b918252838101850191858101908984111561062857600080fd5b86860192505b8383101561072a578235858111156106465760008081fd5b860160607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0828d03810182131561067d5760008081fd5b610685610536565b6106908b850161040e565b81526040848101358c8301529284013592898411156106af5760008081fd5b83850194508e603f8601126106c657600093508384fd5b8b8501359350898411156106dc576106dc610507565b6106ec8c84601f8701160161055f565b92508383528e818587010111156107035760008081fd5b838186018d85013760009383018c019390935291820152835250918601919086019061062e565b9998505050505050505050565b600181811c9082168061074b57607f821691505b602082108103610784577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b6000845161079c818460208901610473565b80830190507f2e0000000000000000000000000000000000000000000000000000000000000080825285516107d8816001850160208a01610473565b600192019182015283516107f3816002840160208801610473565b0160020195945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b601f8211156102cc57600081815260208120601f850160051c810160208610156108565750805b601f850160051c820191505b8181101561087557828155600101610862565b505050505050565b815167ffffffffffffffff81111561089757610897610507565b6108ab816108a58454610737565b8461082f565b602080601f8311600181146108fe57600084156108c85750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555610875565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b8281101561094b5788860151825594840194600190910190840161092c565b508582101561098757878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036109f7576109f7610997565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082610a3c57610a3c6109fe565b500490565b600082821015610a5357610a53610997565b500390565b600082610a6757610a676109fe565b500690565b60008219821115610a7f57610a7f610997565b50019056fea26469706673582212204abcd56633387050a41a0ae027bbf9077ae2fb5080fa84124e2aa381adb3f98964736f6c634300080f0033", "bytecode": "0x60e060405234801561001057600080fd5b506001608081905260a052600060c05260805160a05160c051610b1c61004f60003960006101ad015260006101840152600061015b0152610b1c6000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806329b42cb51461005157806354fd4d501461007a5780635eb5ea1014610082578063702b9dee14610097575b600080fd5b61006461005f36600461046c565b6100aa565b604051610071919061051e565b60405180910390f35b610064610154565b610095610090366004610538565b6101f7565b005b6100956100a5366004610687565b61025a565b60006020818152938152604080822085529281528281209093528252902080546100d3906106de565b80601f01602080910402602001604051908101604052809291908181526020018280546100ff906106de565b801561014c5780601f106101215761010080835404028352916020019161014c565b820191906000526020600020905b81548152906001019060200180831161012f57829003601f168201915b505050505081565b606061017f7f0000000000000000000000000000000000000000000000000000000000000000610306565b6101a87f0000000000000000000000000000000000000000000000000000000000000000610306565b6101d17f0000000000000000000000000000000000000000000000000000000000000000610306565b6040516020016101e393929190610731565b604051602081830303815290604052905090565b8060005b81811015610254576000848483818110610217576102176107a7565b905060200281019061022991906107d6565b61023290610814565b905061024b81600001518260200151836040015161025a565b506001016101fb565b50505050565b3360009081526020818152604080832073ffffffffffffffffffffffffffffffffffffffff871684528252808320858452909152902061029a82826108df565b50818373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f28710dfecab43d1e29e02aa56b2e1e610c0bae19135c9cf7a83a1adb6df96d85846040516102f9919061051e565b60405180910390a4505050565b60608160000361034957505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b8115610373578061035d81610a28565b915061036c9050600a83610a8f565b915061034d565b60008167ffffffffffffffff81111561038e5761038e6105ad565b6040519080825280601f01601f1916602001820160405280156103b8576020820181803683370190505b5090505b841561043b576103cd600183610aa3565b91506103da600a86610aba565b6103e5906030610ace565b60f81b8183815181106103fa576103fa6107a7565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350610434600a86610a8f565b94506103bc565b949350505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461046757600080fd5b919050565b60008060006060848603121561048157600080fd5b61048a84610443565b925061049860208501610443565b9150604084013590509250925092565b60005b838110156104c35781810151838201526020016104ab565b838111156102545750506000910152565b600081518084526104ec8160208601602086016104a8565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061053160208301846104d4565b9392505050565b6000806020838503121561054b57600080fd5b823567ffffffffffffffff8082111561056357600080fd5b818501915085601f83011261057757600080fd5b81358181111561058657600080fd5b8660208260051b850101111561059b57600080fd5b60209290920196919550909350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f8301126105ed57600080fd5b813567ffffffffffffffff80821115610608576106086105ad565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171561064e5761064e6105ad565b8160405283815286602085880101111561066757600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121561069c57600080fd5b6106a584610443565b925060208401359150604084013567ffffffffffffffff8111156106c857600080fd5b6106d4868287016105dc565b9150509250925092565b600181811c908216806106f257607f821691505b60208210810361072b577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b600084516107438184602089016104a8565b80830190507f2e00000000000000000000000000000000000000000000000000000000000000808252855161077f816001850160208a016104a8565b6001920191820152835161079a8160028401602088016104a8565b0160020195945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa183360301811261080a57600080fd5b9190910192915050565b60006060823603121561082657600080fd5b6040516060810167ffffffffffffffff828210818311171561084a5761084a6105ad565b8160405261085785610443565b835260208501356020840152604085013591508082111561087757600080fd5b50610884368286016105dc565b60408301525092915050565b601f8211156108da57600081815260208120601f850160051c810160208610156108b75750805b601f850160051c820191505b818110156108d6578281556001016108c3565b5050505b505050565b815167ffffffffffffffff8111156108f9576108f96105ad565b61090d8161090784546106de565b84610890565b602080601f831160018114610960576000841561092a5750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b1785556108d6565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156109ad5788860151825594840194600190910190840161098e565b50858210156109e957878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610a5957610a596109f9565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082610a9e57610a9e610a60565b500490565b600082821015610ab557610ab56109f9565b500390565b600082610ac957610ac9610a60565b500690565b60008219821115610ae157610ae16109f9565b50019056fea2646970667358221220bfd4abacc1c2510682b093eea8745255cdd9f45c5b16c168540522a65ddc1f3264736f6c634300080f0033",
"deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c806329b42cb51461004657806354fd4d501461006f5780635eb5ea1014610077575b600080fd5b610059610054366004610437565b61008c565b60405161006691906104ed565b60405180910390f35b610059610136565b61008a6100853660046105ae565b6101d9565b005b60006020818152938152604080822085529281528281209093528252902080546100b590610737565b80601f01602080910402602001604051908101604052809291908181526020018280546100e190610737565b801561012e5780601f106101035761010080835404028352916020019161012e565b820191906000526020600020905b81548152906001019060200180831161011157829003601f168201915b505050505081565b60606101617f00000000000000000000000000000000000000000000000000000000000000006102d1565b61018a7f00000000000000000000000000000000000000000000000000000000000000006102d1565b6101b37f00000000000000000000000000000000000000000000000000000000000000006102d1565b6040516020016101c59392919061078a565b604051602081830303815290604052905090565b805160005b818110156102cc5760008382815181106101fa576101fa610800565b602090810291909101810151604080820151336000908152808552828120845173ffffffffffffffffffffffffffffffffffffffff1682528552828120848601518252909452922090925090610250908261087d565b508060200151816000015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f28710dfecab43d1e29e02aa56b2e1e610c0bae19135c9cf7a83a1adb6df96d8584604001516040516102bb91906104ed565b60405180910390a4506001016101de565b505050565b60608160000361031457505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b811561033e5780610328816109c6565b91506103379050600a83610a2d565b9150610318565b60008167ffffffffffffffff81111561035957610359610507565b6040519080825280601f01601f191660200182016040528015610383576020820181803683370190505b5090505b841561040657610398600183610a41565b91506103a5600a86610a58565b6103b0906030610a6c565b60f81b8183815181106103c5576103c5610800565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053506103ff600a86610a2d565b9450610387565b949350505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461043257600080fd5b919050565b60008060006060848603121561044c57600080fd5b6104558461040e565b92506104636020850161040e565b9150604084013590509250925092565b60005b8381101561048e578181015183820152602001610476565b8381111561049d576000848401525b50505050565b600081518084526104bb816020860160208601610473565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061050060208301846104a3565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516060810167ffffffffffffffff8111828210171561055957610559610507565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156105a6576105a6610507565b604052919050565b600060208083850312156105c157600080fd5b823567ffffffffffffffff808211156105d957600080fd5b818501915085601f8301126105ed57600080fd5b8135818111156105ff576105ff610507565b8060051b61060e85820161055f565b918252838101850191858101908984111561062857600080fd5b86860192505b8383101561072a578235858111156106465760008081fd5b860160607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0828d03810182131561067d5760008081fd5b610685610536565b6106908b850161040e565b81526040848101358c8301529284013592898411156106af5760008081fd5b83850194508e603f8601126106c657600093508384fd5b8b8501359350898411156106dc576106dc610507565b6106ec8c84601f8701160161055f565b92508383528e818587010111156107035760008081fd5b838186018d85013760009383018c019390935291820152835250918601919086019061062e565b9998505050505050505050565b600181811c9082168061074b57607f821691505b602082108103610784577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b6000845161079c818460208901610473565b80830190507f2e0000000000000000000000000000000000000000000000000000000000000080825285516107d8816001850160208a01610473565b600192019182015283516107f3816002840160208801610473565b0160020195945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b601f8211156102cc57600081815260208120601f850160051c810160208610156108565750805b601f850160051c820191505b8181101561087557828155600101610862565b505050505050565b815167ffffffffffffffff81111561089757610897610507565b6108ab816108a58454610737565b8461082f565b602080601f8311600181146108fe57600084156108c85750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555610875565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b8281101561094b5788860151825594840194600190910190840161092c565b508582101561098757878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036109f7576109f7610997565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082610a3c57610a3c6109fe565b500490565b600082821015610a5357610a53610997565b500390565b600082610a6757610a676109fe565b500690565b60008219821115610a7f57610a7f610997565b50019056fea26469706673582212204abcd56633387050a41a0ae027bbf9077ae2fb5080fa84124e2aa381adb3f98964736f6c634300080f0033", "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c806329b42cb51461005157806354fd4d501461007a5780635eb5ea1014610082578063702b9dee14610097575b600080fd5b61006461005f36600461046c565b6100aa565b604051610071919061051e565b60405180910390f35b610064610154565b610095610090366004610538565b6101f7565b005b6100956100a5366004610687565b61025a565b60006020818152938152604080822085529281528281209093528252902080546100d3906106de565b80601f01602080910402602001604051908101604052809291908181526020018280546100ff906106de565b801561014c5780601f106101215761010080835404028352916020019161014c565b820191906000526020600020905b81548152906001019060200180831161012f57829003601f168201915b505050505081565b606061017f7f0000000000000000000000000000000000000000000000000000000000000000610306565b6101a87f0000000000000000000000000000000000000000000000000000000000000000610306565b6101d17f0000000000000000000000000000000000000000000000000000000000000000610306565b6040516020016101e393929190610731565b604051602081830303815290604052905090565b8060005b81811015610254576000848483818110610217576102176107a7565b905060200281019061022991906107d6565b61023290610814565b905061024b81600001518260200151836040015161025a565b506001016101fb565b50505050565b3360009081526020818152604080832073ffffffffffffffffffffffffffffffffffffffff871684528252808320858452909152902061029a82826108df565b50818373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f28710dfecab43d1e29e02aa56b2e1e610c0bae19135c9cf7a83a1adb6df96d85846040516102f9919061051e565b60405180910390a4505050565b60608160000361034957505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b8115610373578061035d81610a28565b915061036c9050600a83610a8f565b915061034d565b60008167ffffffffffffffff81111561038e5761038e6105ad565b6040519080825280601f01601f1916602001820160405280156103b8576020820181803683370190505b5090505b841561043b576103cd600183610aa3565b91506103da600a86610aba565b6103e5906030610ace565b60f81b8183815181106103fa576103fa6107a7565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350610434600a86610a8f565b94506103bc565b949350505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461046757600080fd5b919050565b60008060006060848603121561048157600080fd5b61048a84610443565b925061049860208501610443565b9150604084013590509250925092565b60005b838110156104c35781810151838201526020016104ab565b838111156102545750506000910152565b600081518084526104ec8160208601602086016104a8565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061053160208301846104d4565b9392505050565b6000806020838503121561054b57600080fd5b823567ffffffffffffffff8082111561056357600080fd5b818501915085601f83011261057757600080fd5b81358181111561058657600080fd5b8660208260051b850101111561059b57600080fd5b60209290920196919550909350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f8301126105ed57600080fd5b813567ffffffffffffffff80821115610608576106086105ad565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171561064e5761064e6105ad565b8160405283815286602085880101111561066757600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121561069c57600080fd5b6106a584610443565b925060208401359150604084013567ffffffffffffffff8111156106c857600080fd5b6106d4868287016105dc565b9150509250925092565b600181811c908216806106f257607f821691505b60208210810361072b577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b600084516107438184602089016104a8565b80830190507f2e00000000000000000000000000000000000000000000000000000000000000808252855161077f816001850160208a016104a8565b6001920191820152835161079a8160028401602088016104a8565b0160020195945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa183360301811261080a57600080fd5b9190910192915050565b60006060823603121561082657600080fd5b6040516060810167ffffffffffffffff828210818311171561084a5761084a6105ad565b8160405261085785610443565b835260208501356020840152604085013591508082111561087757600080fd5b50610884368286016105dc565b60408301525092915050565b601f8211156108da57600081815260208120601f850160051c810160208610156108b75750805b601f850160051c820191505b818110156108d6578281556001016108c3565b5050505b505050565b815167ffffffffffffffff8111156108f9576108f96105ad565b61090d8161090784546106de565b84610890565b602080601f831160018114610960576000841561092a5750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b1785556108d6565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156109ad5788860151825594840194600190910190840161098e565b50858210156109e957878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610a5957610a596109f9565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082610a9e57610a9e610a60565b500490565b600082821015610ab557610ab56109f9565b500390565b600082610ac957610ac9610a60565b500690565b60008219821115610ae157610ae16109f9565b50019056fea2646970667358221220bfd4abacc1c2510682b093eea8745255cdd9f45c5b16c168540522a65ddc1f3264736f6c634300080f0033",
"devdoc": { "devdoc": {
"author": "Optimism CollectiveGitcoin", "author": "Optimism CollectiveGitcoin",
"events": { "events": {
...@@ -151,8 +174,15 @@ ...@@ -151,8 +174,15 @@
"_attestations": "An array of attestation data." "_attestations": "An array of attestation data."
} }
}, },
"attest(address,bytes32,bytes)": {
"params": {
"_about": "Address that the attestation is about.",
"_key": "A key used to namespace the attestation.",
"_val": "An arbitrary value stored as part of the attestation."
}
},
"constructor": { "constructor": {
"custom:semver": "1.0.0" "custom:semver": "1.1.0"
}, },
"version()": { "version()": {
"returns": { "returns": {
...@@ -174,6 +204,9 @@ ...@@ -174,6 +204,9 @@
"attest((address,bytes32,bytes)[])": { "attest((address,bytes32,bytes)[])": {
"notice": "Allows anyone to create attestations." "notice": "Allows anyone to create attestations."
}, },
"attest(address,bytes32,bytes)": {
"notice": "Allows anyone to create an attestation."
},
"attestations(address,address,bytes32)": { "attestations(address,address,bytes32)": {
"notice": "Maps addresses to attestations. Creator => About => Key => Value." "notice": "Maps addresses to attestations. Creator => About => Key => Value."
}, },
...@@ -187,7 +220,7 @@ ...@@ -187,7 +220,7 @@
"storageLayout": { "storageLayout": {
"storage": [ "storage": [
{ {
"astId": 8191, "astId": 2135,
"contract": "contracts/universal/op-nft/AttestationStation.sol:AttestationStation", "contract": "contracts/universal/op-nft/AttestationStation.sol:AttestationStation",
"label": "attestations", "label": "attestations",
"offset": 0, "offset": 0,
......
{
"language": "Solidity",
"sources": {
"contracts/universal/op-nft/AttestationStation.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.15;\n\nimport { Semver } from \"@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol\";\n\n/**\n * @title AttestationStation\n * @author Optimism Collective\n * @author Gitcoin\n * @notice Where attestations live.\n */\ncontract AttestationStation is Semver {\n /**\n * @notice Struct representing data that is being attested.\n *\n * @custom:field about Address for which the attestation is about.\n * @custom:field key A bytes32 key for the attestation.\n * @custom:field val The attestation as arbitrary bytes.\n */\n struct AttestationData {\n address about;\n bytes32 key;\n bytes val;\n }\n\n /**\n * @notice Maps addresses to attestations. Creator => About => Key => Value.\n */\n mapping(address => mapping(address => mapping(bytes32 => bytes))) public attestations;\n\n /**\n * @notice Emitted when Attestation is created.\n *\n * @param creator Address that made the attestation.\n * @param about Address attestation is about.\n * @param key Key of the attestation.\n * @param val Value of the attestation.\n */\n event AttestationCreated(\n address indexed creator,\n address indexed about,\n bytes32 indexed key,\n bytes val\n );\n\n /**\n * @custom:semver 1.1.0\n */\n constructor() Semver(1, 1, 0) {}\n\n /**\n * @notice Allows anyone to create an attestation.\n *\n * @param _about Address that the attestation is about.\n * @param _key A key used to namespace the attestation.\n * @param _val An arbitrary value stored as part of the attestation.\n */\n function attest(\n address _about,\n bytes32 _key,\n bytes memory _val\n ) public {\n attestations[msg.sender][_about][_key] = _val;\n\n emit AttestationCreated(msg.sender, _about, _key, _val);\n }\n\n /**\n * @notice Allows anyone to create attestations.\n *\n * @param _attestations An array of attestation data.\n */\n function attest(AttestationData[] calldata _attestations) external {\n uint256 length = _attestations.length;\n for (uint256 i = 0; i < length; ) {\n AttestationData memory attestation = _attestations[i];\n\n attest(attestation.about, attestation.key, attestation.val);\n\n unchecked {\n ++i;\n }\n }\n }\n}\n"
},
"@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.15;\n\nimport { Strings } from \"@openzeppelin/contracts/utils/Strings.sol\";\n\n/**\n * @title Semver\n * @notice Semver is a simple contract for managing contract versions.\n */\ncontract Semver {\n /**\n * @notice Contract version number (major).\n */\n uint256 private immutable MAJOR_VERSION;\n\n /**\n * @notice Contract version number (minor).\n */\n uint256 private immutable MINOR_VERSION;\n\n /**\n * @notice Contract version number (patch).\n */\n uint256 private immutable PATCH_VERSION;\n\n /**\n * @param _major Version number (major).\n * @param _minor Version number (minor).\n * @param _patch Version number (patch).\n */\n constructor(\n uint256 _major,\n uint256 _minor,\n uint256 _patch\n ) {\n MAJOR_VERSION = _major;\n MINOR_VERSION = _minor;\n PATCH_VERSION = _patch;\n }\n\n /**\n * @notice Returns the full semver contract version.\n *\n * @return Semver contract version as a string.\n */\n function version() public view returns (string memory) {\n return\n string(\n abi.encodePacked(\n Strings.toString(MAJOR_VERSION),\n \".\",\n Strings.toString(MINOR_VERSION),\n \".\",\n Strings.toString(PATCH_VERSION)\n )\n );\n }\n}\n"
},
"@openzeppelin/contracts/utils/Strings.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\n uint8 private constant _ADDRESS_LENGTH = 20;\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n // Inspired by OraclizeAPI's implementation - MIT licence\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\n\n if (value == 0) {\n return \"0\";\n }\n uint256 temp = value;\n uint256 digits;\n while (temp != 0) {\n digits++;\n temp /= 10;\n }\n bytes memory buffer = new bytes(digits);\n while (value != 0) {\n digits -= 1;\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\n value /= 10;\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n if (value == 0) {\n return \"0x00\";\n }\n uint256 temp = value;\n uint256 length = 0;\n while (temp != 0) {\n length++;\n temp >>= 8;\n }\n return toHexString(value, length);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\n */\n function toHexString(address addr) internal pure returns (string memory) {\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\n }\n}\n"
},
"contracts/universal/op-nft/Optimist.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.15;\n\nimport { Semver } from \"@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol\";\nimport {\n ERC721BurnableUpgradeable\n} from \"@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol\";\nimport { AttestationStation } from \"./AttestationStation.sol\";\nimport { Strings } from \"@openzeppelin/contracts/utils/Strings.sol\";\n\n/**\n * @author Optimism Collective\n * @author Gitcoin\n * @title Optimist\n * @notice A Soul Bound Token for real humans only(tm).\n */\ncontract Optimist is ERC721BurnableUpgradeable, Semver {\n /**\n * @notice Address of the AttestationStation contract.\n */\n AttestationStation public immutable ATTESTATION_STATION;\n\n /**\n * @notice Attestor who attests to baseURI and allowlist.\n */\n address public immutable ATTESTOR;\n\n /**\n * @custom:semver 1.0.0\n * @param _name Token name.\n * @param _symbol Token symbol.\n * @param _attestor Address of the attestor.\n * @param _attestationStation Address of the AttestationStation contract.\n */\n constructor(\n string memory _name,\n string memory _symbol,\n address _attestor,\n AttestationStation _attestationStation\n ) Semver(1, 0, 0) {\n ATTESTOR = _attestor;\n ATTESTATION_STATION = _attestationStation;\n initialize(_name, _symbol);\n }\n\n /**\n * @notice Initializes the Optimist contract.\n *\n * @param _name Token name.\n * @param _symbol Token symbol.\n */\n function initialize(string memory _name, string memory _symbol) public initializer {\n __ERC721_init(_name, _symbol);\n __ERC721Burnable_init();\n }\n\n /**\n * @notice Allows an address to mint an Optimist NFT. Token ID is the uint256 representation\n * of the recipient's address. Recipients must be permitted to mint, eventually anyone\n * will be able to mint. One token per address.\n *\n * @param _recipient Address of the token recipient.\n */\n function mint(address _recipient) public {\n require(isOnAllowList(_recipient), \"Optimist: address is not on allowList\");\n _safeMint(_recipient, tokenIdOfAddress(_recipient));\n }\n\n /**\n * @notice Returns the baseURI for all tokens.\n *\n * @return BaseURI for all tokens.\n */\n function baseURI() public view returns (string memory) {\n return\n string(\n abi.encodePacked(\n ATTESTATION_STATION.attestations(\n ATTESTOR,\n address(this),\n bytes32(\"optimist.base-uri\")\n )\n )\n );\n }\n\n /**\n * @notice Returns the token URI for a given token by ID\n *\n * @param _tokenId Token ID to query.\n\n * @return Token URI for the given token by ID.\n */\n function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) {\n return\n string(\n abi.encodePacked(\n baseURI(),\n \"/\",\n // Properly format the token ID as a 20 byte hex string (address).\n Strings.toHexString(_tokenId, 20),\n \".json\"\n )\n );\n }\n\n /**\n * @notice Checks whether a given address is allowed to mint the Optimist NFT yet. Since the\n * Optimist NFT will also be used as part of the Citizens House, mints are currently\n * restricted. Eventually anyone will be able to mint.\n *\n * @return Whether or not the address is allowed to mint yet.\n */\n function isOnAllowList(address _recipient) public view returns (bool) {\n return\n ATTESTATION_STATION\n .attestations(ATTESTOR, _recipient, bytes32(\"optimist.can-mint\"))\n .length > 0;\n }\n\n /**\n * @notice Returns the token ID for the token owned by a given address. This is the uint256\n * representation of the given address.\n *\n * @return Token ID for the token owned by the given address.\n */\n function tokenIdOfAddress(address _owner) public pure returns (uint256) {\n return uint256(uint160(_owner));\n }\n\n /**\n * @notice Disabled for the Optimist NFT (Soul Bound Token).\n */\n function approve(address, uint256) public pure override {\n revert(\"Optimist: soul bound token\");\n }\n\n /**\n * @notice Disabled for the Optimist NFT (Soul Bound Token).\n */\n function setApprovalForAll(address, bool) public virtual override {\n revert(\"Optimist: soul bound token\");\n }\n\n /**\n * @notice Prevents transfers of the Optimist NFT (Soul Bound Token).\n *\n * @param _from Address of the token sender.\n * @param _to Address of the token recipient.\n */\n function _beforeTokenTransfer(\n address _from,\n address _to,\n uint256\n ) internal virtual override {\n require(_from == address(0) || _to == address(0), \"Optimist: soul bound token\");\n }\n}\n"
},
"@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/extensions/ERC721Burnable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../ERC721Upgradeable.sol\";\nimport \"../../../utils/ContextUpgradeable.sol\";\nimport \"../../../proxy/utils/Initializable.sol\";\n\n/**\n * @title ERC721 Burnable Token\n * @dev ERC721 Token that can be burned (destroyed).\n */\nabstract contract ERC721BurnableUpgradeable is Initializable, ContextUpgradeable, ERC721Upgradeable {\n function __ERC721Burnable_init() internal onlyInitializing {\n }\n\n function __ERC721Burnable_init_unchained() internal onlyInitializing {\n }\n /**\n * @dev Burns `tokenId`. See {ERC721-_burn}.\n *\n * Requirements:\n *\n * - The caller must own `tokenId` or be an approved operator.\n */\n function burn(uint256 tokenId) public virtual {\n //solhint-disable-next-line max-line-length\n require(_isApprovedOrOwner(_msgSender(), tokenId), \"ERC721: caller is not token owner nor approved\");\n _burn(tokenId);\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[50] private __gap;\n}\n"
},
"@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/ERC721.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC721Upgradeable.sol\";\nimport \"./IERC721ReceiverUpgradeable.sol\";\nimport \"./extensions/IERC721MetadataUpgradeable.sol\";\nimport \"../../utils/AddressUpgradeable.sol\";\nimport \"../../utils/ContextUpgradeable.sol\";\nimport \"../../utils/StringsUpgradeable.sol\";\nimport \"../../utils/introspection/ERC165Upgradeable.sol\";\nimport \"../../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including\n * the Metadata extension, but not including the Enumerable extension, which is available separately as\n * {ERC721Enumerable}.\n */\ncontract ERC721Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradeable, IERC721Upgradeable, IERC721MetadataUpgradeable {\n using AddressUpgradeable for address;\n using StringsUpgradeable for uint256;\n\n // Token name\n string private _name;\n\n // Token symbol\n string private _symbol;\n\n // Mapping from token ID to owner address\n mapping(uint256 => address) private _owners;\n\n // Mapping owner address to token count\n mapping(address => uint256) private _balances;\n\n // Mapping from token ID to approved address\n mapping(uint256 => address) private _tokenApprovals;\n\n // Mapping from owner to operator approvals\n mapping(address => mapping(address => bool)) private _operatorApprovals;\n\n /**\n * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.\n */\n function __ERC721_init(string memory name_, string memory symbol_) internal onlyInitializing {\n __ERC721_init_unchained(name_, symbol_);\n }\n\n function __ERC721_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165Upgradeable) returns (bool) {\n return\n interfaceId == type(IERC721Upgradeable).interfaceId ||\n interfaceId == type(IERC721MetadataUpgradeable).interfaceId ||\n super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev See {IERC721-balanceOf}.\n */\n function balanceOf(address owner) public view virtual override returns (uint256) {\n require(owner != address(0), \"ERC721: address zero is not a valid owner\");\n return _balances[owner];\n }\n\n /**\n * @dev See {IERC721-ownerOf}.\n */\n function ownerOf(uint256 tokenId) public view virtual override returns (address) {\n address owner = _owners[tokenId];\n require(owner != address(0), \"ERC721: invalid token ID\");\n return owner;\n }\n\n /**\n * @dev See {IERC721Metadata-name}.\n */\n function name() public view virtual override returns (string memory) {\n return _name;\n }\n\n /**\n * @dev See {IERC721Metadata-symbol}.\n */\n function symbol() public view virtual override returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev See {IERC721Metadata-tokenURI}.\n */\n function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {\n _requireMinted(tokenId);\n\n string memory baseURI = _baseURI();\n return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : \"\";\n }\n\n /**\n * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each\n * token will be the concatenation of the `baseURI` and the `tokenId`. Empty\n * by default, can be overridden in child contracts.\n */\n function _baseURI() internal view virtual returns (string memory) {\n return \"\";\n }\n\n /**\n * @dev See {IERC721-approve}.\n */\n function approve(address to, uint256 tokenId) public virtual override {\n address owner = ERC721Upgradeable.ownerOf(tokenId);\n require(to != owner, \"ERC721: approval to current owner\");\n\n require(\n _msgSender() == owner || isApprovedForAll(owner, _msgSender()),\n \"ERC721: approve caller is not token owner nor approved for all\"\n );\n\n _approve(to, tokenId);\n }\n\n /**\n * @dev See {IERC721-getApproved}.\n */\n function getApproved(uint256 tokenId) public view virtual override returns (address) {\n _requireMinted(tokenId);\n\n return _tokenApprovals[tokenId];\n }\n\n /**\n * @dev See {IERC721-setApprovalForAll}.\n */\n function setApprovalForAll(address operator, bool approved) public virtual override {\n _setApprovalForAll(_msgSender(), operator, approved);\n }\n\n /**\n * @dev See {IERC721-isApprovedForAll}.\n */\n function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {\n return _operatorApprovals[owner][operator];\n }\n\n /**\n * @dev See {IERC721-transferFrom}.\n */\n function transferFrom(\n address from,\n address to,\n uint256 tokenId\n ) public virtual override {\n //solhint-disable-next-line max-line-length\n require(_isApprovedOrOwner(_msgSender(), tokenId), \"ERC721: caller is not token owner nor approved\");\n\n _transfer(from, to, tokenId);\n }\n\n /**\n * @dev See {IERC721-safeTransferFrom}.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId\n ) public virtual override {\n safeTransferFrom(from, to, tokenId, \"\");\n }\n\n /**\n * @dev See {IERC721-safeTransferFrom}.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId,\n bytes memory data\n ) public virtual override {\n require(_isApprovedOrOwner(_msgSender(), tokenId), \"ERC721: caller is not token owner nor approved\");\n _safeTransfer(from, to, tokenId, data);\n }\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\n *\n * `data` is additional data, it has no specified format and it is sent in call to `to`.\n *\n * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.\n * implement alternative mechanisms to perform token transfer, such as signature-based.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function _safeTransfer(\n address from,\n address to,\n uint256 tokenId,\n bytes memory data\n ) internal virtual {\n _transfer(from, to, tokenId);\n require(_checkOnERC721Received(from, to, tokenId, data), \"ERC721: transfer to non ERC721Receiver implementer\");\n }\n\n /**\n * @dev Returns whether `tokenId` exists.\n *\n * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.\n *\n * Tokens start existing when they are minted (`_mint`),\n * and stop existing when they are burned (`_burn`).\n */\n function _exists(uint256 tokenId) internal view virtual returns (bool) {\n return _owners[tokenId] != address(0);\n }\n\n /**\n * @dev Returns whether `spender` is allowed to manage `tokenId`.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {\n address owner = ERC721Upgradeable.ownerOf(tokenId);\n return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);\n }\n\n /**\n * @dev Safely mints `tokenId` and transfers it to `to`.\n *\n * Requirements:\n *\n * - `tokenId` must not exist.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function _safeMint(address to, uint256 tokenId) internal virtual {\n _safeMint(to, tokenId, \"\");\n }\n\n /**\n * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is\n * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.\n */\n function _safeMint(\n address to,\n uint256 tokenId,\n bytes memory data\n ) internal virtual {\n _mint(to, tokenId);\n require(\n _checkOnERC721Received(address(0), to, tokenId, data),\n \"ERC721: transfer to non ERC721Receiver implementer\"\n );\n }\n\n /**\n * @dev Mints `tokenId` and transfers it to `to`.\n *\n * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible\n *\n * Requirements:\n *\n * - `tokenId` must not exist.\n * - `to` cannot be the zero address.\n *\n * Emits a {Transfer} event.\n */\n function _mint(address to, uint256 tokenId) internal virtual {\n require(to != address(0), \"ERC721: mint to the zero address\");\n require(!_exists(tokenId), \"ERC721: token already minted\");\n\n _beforeTokenTransfer(address(0), to, tokenId);\n\n _balances[to] += 1;\n _owners[tokenId] = to;\n\n emit Transfer(address(0), to, tokenId);\n\n _afterTokenTransfer(address(0), to, tokenId);\n }\n\n /**\n * @dev Destroys `tokenId`.\n * The approval is cleared when the token is burned.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n *\n * Emits a {Transfer} event.\n */\n function _burn(uint256 tokenId) internal virtual {\n address owner = ERC721Upgradeable.ownerOf(tokenId);\n\n _beforeTokenTransfer(owner, address(0), tokenId);\n\n // Clear approvals\n _approve(address(0), tokenId);\n\n _balances[owner] -= 1;\n delete _owners[tokenId];\n\n emit Transfer(owner, address(0), tokenId);\n\n _afterTokenTransfer(owner, address(0), tokenId);\n }\n\n /**\n * @dev Transfers `tokenId` from `from` to `to`.\n * As opposed to {transferFrom}, this imposes no restrictions on msg.sender.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - `tokenId` token must be owned by `from`.\n *\n * Emits a {Transfer} event.\n */\n function _transfer(\n address from,\n address to,\n uint256 tokenId\n ) internal virtual {\n require(ERC721Upgradeable.ownerOf(tokenId) == from, \"ERC721: transfer from incorrect owner\");\n require(to != address(0), \"ERC721: transfer to the zero address\");\n\n _beforeTokenTransfer(from, to, tokenId);\n\n // Clear approvals from the previous owner\n _approve(address(0), tokenId);\n\n _balances[from] -= 1;\n _balances[to] += 1;\n _owners[tokenId] = to;\n\n emit Transfer(from, to, tokenId);\n\n _afterTokenTransfer(from, to, tokenId);\n }\n\n /**\n * @dev Approve `to` to operate on `tokenId`\n *\n * Emits an {Approval} event.\n */\n function _approve(address to, uint256 tokenId) internal virtual {\n _tokenApprovals[tokenId] = to;\n emit Approval(ERC721Upgradeable.ownerOf(tokenId), to, tokenId);\n }\n\n /**\n * @dev Approve `operator` to operate on all of `owner` tokens\n *\n * Emits an {ApprovalForAll} event.\n */\n function _setApprovalForAll(\n address owner,\n address operator,\n bool approved\n ) internal virtual {\n require(owner != operator, \"ERC721: approve to caller\");\n _operatorApprovals[owner][operator] = approved;\n emit ApprovalForAll(owner, operator, approved);\n }\n\n /**\n * @dev Reverts if the `tokenId` has not been minted yet.\n */\n function _requireMinted(uint256 tokenId) internal view virtual {\n require(_exists(tokenId), \"ERC721: invalid token ID\");\n }\n\n /**\n * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.\n * The call is not executed if the target address is not a contract.\n *\n * @param from address representing the previous owner of the given token ID\n * @param to target address that will receive the tokens\n * @param tokenId uint256 ID of the token to be transferred\n * @param data bytes optional data to send along with the call\n * @return bool whether the call correctly returned the expected magic value\n */\n function _checkOnERC721Received(\n address from,\n address to,\n uint256 tokenId,\n bytes memory data\n ) private returns (bool) {\n if (to.isContract()) {\n try IERC721ReceiverUpgradeable(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {\n return retval == IERC721ReceiverUpgradeable.onERC721Received.selector;\n } catch (bytes memory reason) {\n if (reason.length == 0) {\n revert(\"ERC721: transfer to non ERC721Receiver implementer\");\n } else {\n /// @solidity memory-safe-assembly\n assembly {\n revert(add(32, reason), mload(reason))\n }\n }\n }\n } else {\n return true;\n }\n }\n\n /**\n * @dev Hook that is called before any token transfer. This includes minting\n * and burning.\n *\n * Calling conditions:\n *\n * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be\n * transferred to `to`.\n * - When `from` is zero, `tokenId` will be minted for `to`.\n * - When `to` is zero, ``from``'s `tokenId` will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 tokenId\n ) internal virtual {}\n\n /**\n * @dev Hook that is called after any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _afterTokenTransfer(\n address from,\n address to,\n uint256 tokenId\n ) internal virtual {}\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[44] private __gap;\n}\n"
},
"@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\nimport \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract ContextUpgradeable is Initializable {\n function __Context_init() internal onlyInitializing {\n }\n\n function __Context_init_unchained() internal onlyInitializing {\n }\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[50] private __gap;\n}\n"
},
"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol)\n\npragma solidity ^0.8.2;\n\nimport \"../../utils/AddressUpgradeable.sol\";\n\n/**\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\n *\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\n * reused. This mechanism prevents re-execution of each \"step\" but allows the creation of new initialization steps in\n * case an upgrade adds a module that needs to be initialized.\n *\n * For example:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * contract MyToken is ERC20Upgradeable {\n * function initialize() initializer public {\n * __ERC20_init(\"MyToken\", \"MTK\");\n * }\n * }\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\n * function initializeV2() reinitializer(2) public {\n * __ERC20Permit_init(\"MyToken\");\n * }\n * }\n * ```\n *\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\n *\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\n *\n * [CAUTION]\n * ====\n * Avoid leaving a contract uninitialized.\n *\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * /// @custom:oz-upgrades-unsafe-allow constructor\n * constructor() {\n * _disableInitializers();\n * }\n * ```\n * ====\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n * @custom:oz-retyped-from bool\n */\n uint8 private _initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private _initializing;\n\n /**\n * @dev Triggered when the contract has been initialized or reinitialized.\n */\n event Initialized(uint8 version);\n\n /**\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\n * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`.\n */\n modifier initializer() {\n bool isTopLevelCall = !_initializing;\n require(\n (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),\n \"Initializable: contract is already initialized\"\n );\n _initialized = 1;\n if (isTopLevelCall) {\n _initializing = true;\n }\n _;\n if (isTopLevelCall) {\n _initializing = false;\n emit Initialized(1);\n }\n }\n\n /**\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\n * used to initialize parent contracts.\n *\n * `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original\n * initialization step. This is essential to configure modules that are added through upgrades and that require\n * initialization.\n *\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\n * a contract, executing them in the right order is up to the developer or operator.\n */\n modifier reinitializer(uint8 version) {\n require(!_initializing && _initialized < version, \"Initializable: contract is already initialized\");\n _initialized = version;\n _initializing = true;\n _;\n _initializing = false;\n emit Initialized(version);\n }\n\n /**\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\n */\n modifier onlyInitializing() {\n require(_initializing, \"Initializable: contract is not initializing\");\n _;\n }\n\n /**\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\n * through proxies.\n */\n function _disableInitializers() internal virtual {\n require(!_initializing, \"Initializable: contract is initializing\");\n if (_initialized < type(uint8).max) {\n _initialized = type(uint8).max;\n emit Initialized(type(uint8).max);\n }\n }\n}\n"
},
"@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../../utils/introspection/IERC165Upgradeable.sol\";\n\n/**\n * @dev Required interface of an ERC721 compliant contract.\n */\ninterface IERC721Upgradeable is IERC165Upgradeable {\n /**\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\n */\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\n */\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\n */\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n /**\n * @dev Returns the number of tokens in ``owner``'s account.\n */\n function balanceOf(address owner) external view returns (uint256 balance);\n\n /**\n * @dev Returns the owner of the `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function ownerOf(uint256 tokenId) external view returns (address owner);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId,\n bytes calldata data\n ) external;\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Transfers `tokenId` token from `from` to `to`.\n *\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\n * The approval is cleared when the token is transferred.\n *\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\n *\n * Requirements:\n *\n * - The caller must own the token or be an approved operator.\n * - `tokenId` must exist.\n *\n * Emits an {Approval} event.\n */\n function approve(address to, uint256 tokenId) external;\n\n /**\n * @dev Approve or remove `operator` as an operator for the caller.\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\n *\n * Requirements:\n *\n * - The `operator` cannot be the caller.\n *\n * Emits an {ApprovalForAll} event.\n */\n function setApprovalForAll(address operator, bool _approved) external;\n\n /**\n * @dev Returns the account approved for `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function getApproved(uint256 tokenId) external view returns (address operator);\n\n /**\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\n *\n * See {setApprovalForAll}\n */\n function isApprovedForAll(address owner, address operator) external view returns (bool);\n}\n"
},
"@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @title ERC721 token receiver interface\n * @dev Interface for any contract that wants to support safeTransfers\n * from ERC721 asset contracts.\n */\ninterface IERC721ReceiverUpgradeable {\n /**\n * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}\n * by `operator` from `from`, this function is called.\n *\n * It must return its Solidity selector to confirm the token transfer.\n * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.\n *\n * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.\n */\n function onERC721Received(\n address operator,\n address from,\n uint256 tokenId,\n bytes calldata data\n ) external returns (bytes4);\n}\n"
},
"@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721MetadataUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC721Upgradeable.sol\";\n\n/**\n * @title ERC-721 Non-Fungible Token Standard, optional metadata extension\n * @dev See https://eips.ethereum.org/EIPS/eip-721\n */\ninterface IERC721MetadataUpgradeable is IERC721Upgradeable {\n /**\n * @dev Returns the token collection name.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the token collection symbol.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.\n */\n function tokenURI(uint256 tokenId) external view returns (string memory);\n}\n"
},
"@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary AddressUpgradeable {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n /// @solidity memory-safe-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n"
},
"@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev String operations.\n */\nlibrary StringsUpgradeable {\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\n uint8 private constant _ADDRESS_LENGTH = 20;\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n // Inspired by OraclizeAPI's implementation - MIT licence\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\n\n if (value == 0) {\n return \"0\";\n }\n uint256 temp = value;\n uint256 digits;\n while (temp != 0) {\n digits++;\n temp /= 10;\n }\n bytes memory buffer = new bytes(digits);\n while (value != 0) {\n digits -= 1;\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\n value /= 10;\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n if (value == 0) {\n return \"0x00\";\n }\n uint256 temp = value;\n uint256 length = 0;\n while (temp != 0) {\n length++;\n temp >>= 8;\n }\n return toHexString(value, length);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\n */\n function toHexString(address addr) internal pure returns (string memory) {\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\n }\n}\n"
},
"@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC165Upgradeable.sol\";\nimport \"../../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Implementation of the {IERC165} interface.\n *\n * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check\n * for the additional interface id that will be supported. For example:\n *\n * ```solidity\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\n * }\n * ```\n *\n * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\n */\nabstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable {\n function __ERC165_init() internal onlyInitializing {\n }\n\n function __ERC165_init_unchained() internal onlyInitializing {\n }\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IERC165Upgradeable).interfaceId;\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[50] private __gap;\n}\n"
},
"@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165Upgradeable {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 10000
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata",
"devdoc",
"userdoc",
"storageLayout",
"evm.gasEstimates"
],
"": [
"ast"
]
}
},
"metadata": {
"useLiteralContent": true
}
}
}
\ No newline at end of file
{ {
"address": "0x1BEb19F1685ddF2F774884902119Fa2FA5d8f509", "address": "0xbeD744818e96AAD8a51324291a6f6Cb20A0c22be",
"abi": [ "abi": [
{ {
"inputs": [], "inputs": [],
...@@ -67,6 +67,29 @@ ...@@ -67,6 +67,29 @@
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "type": "function"
}, },
{
"inputs": [
{
"internalType": "address",
"name": "_about",
"type": "address"
},
{
"internalType": "bytes32",
"name": "_key",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_val",
"type": "bytes"
}
],
"name": "attest",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
...@@ -110,28 +133,28 @@ ...@@ -110,28 +133,28 @@
"type": "function" "type": "function"
} }
], ],
"transactionHash": "0x84a56c4b4090faef9131650fbbcf38e128e9d98ce77a06ca2e7485c8a137b9a6", "transactionHash": "0x0272013b1d20b4b8e2b8153867389c1c75cbbd5c50e564ee71a62dbfcc35f062",
"receipt": { "receipt": {
"to": "0x4e59b44847b379578588920cA78FbF26c0B4956C", "to": "0x4e59b44847b379578588920cA78FbF26c0B4956C",
"from": "0x9C6373dE60c2D3297b18A8f964618ac46E011B58", "from": "0x956a5152D0f498dBA0c5966577bb44262F8F7078",
"contractAddress": null, "contractAddress": null,
"transactionIndex": 0, "transactionIndex": 0,
"gasUsed": "645110", "gasUsed": "666309",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"blockHash": "0xe588af2eaf9e982580a846beb0f8030a896400431219ce0deca38524defc6324", "blockHash": "0x6d539badaadce9732f2f79e1ceaee94499dd0003d42862324ff334bd7e7e6e68",
"transactionHash": "0x84a56c4b4090faef9131650fbbcf38e128e9d98ce77a06ca2e7485c8a137b9a6", "transactionHash": "0x0272013b1d20b4b8e2b8153867389c1c75cbbd5c50e564ee71a62dbfcc35f062",
"logs": [], "logs": [],
"blockNumber": 49669933, "blockNumber": 70643992,
"cumulativeGasUsed": "645110", "cumulativeGasUsed": "666309",
"status": 1, "status": 1,
"byzantium": true "byzantium": true
}, },
"args": [], "args": [],
"numDeployments": 1, "numDeployments": 2,
"solcInputHash": "45837d34ff24b9cb2ae34232b60ea874", "solcInputHash": "3aa2ad7d005d9515a1f12df8da17d178",
"metadata": "{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"creator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"about\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"key\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"val\",\"type\":\"bytes\"}],\"name\":\"AttestationCreated\",\"type\":\"event\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"about\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"key\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"val\",\"type\":\"bytes\"}],\"internalType\":\"struct AttestationStation.AttestationData[]\",\"name\":\"_attestations\",\"type\":\"tuple[]\"}],\"name\":\"attest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"attestations\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Optimism CollectiveGitcoin\",\"events\":{\"AttestationCreated(address,address,bytes32,bytes)\":{\"params\":{\"about\":\"Address attestation is about.\",\"creator\":\"Address that made the attestation.\",\"key\":\"Key of the attestation.\",\"val\":\"Value of the attestation.\"}}},\"kind\":\"dev\",\"methods\":{\"attest((address,bytes32,bytes)[])\":{\"params\":{\"_attestations\":\"An array of attestation data.\"}},\"constructor\":{\"custom:semver\":\"1.0.0\"},\"version()\":{\"returns\":{\"_0\":\"Semver contract version as a string.\"}}},\"title\":\"AttestationStation\",\"version\":1},\"userdoc\":{\"events\":{\"AttestationCreated(address,address,bytes32,bytes)\":{\"notice\":\"Emitted when Attestation is created.\"}},\"kind\":\"user\",\"methods\":{\"attest((address,bytes32,bytes)[])\":{\"notice\":\"Allows anyone to create attestations.\"},\"attestations(address,address,bytes32)\":{\"notice\":\"Maps addresses to attestations. Creator => About => Key => Value.\"},\"version()\":{\"notice\":\"Returns the full semver contract version.\"}},\"notice\":\"Where attestations live.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/universal/op-nft/AttestationStation.sol\":\"AttestationStation\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.15;\\n\\nimport { Strings } from \\\"@openzeppelin/contracts/utils/Strings.sol\\\";\\n\\n/**\\n * @title Semver\\n * @notice Semver is a simple contract for managing contract versions.\\n */\\ncontract Semver {\\n /**\\n * @notice Contract version number (major).\\n */\\n uint256 private immutable MAJOR_VERSION;\\n\\n /**\\n * @notice Contract version number (minor).\\n */\\n uint256 private immutable MINOR_VERSION;\\n\\n /**\\n * @notice Contract version number (patch).\\n */\\n uint256 private immutable PATCH_VERSION;\\n\\n /**\\n * @param _major Version number (major).\\n * @param _minor Version number (minor).\\n * @param _patch Version number (patch).\\n */\\n constructor(\\n uint256 _major,\\n uint256 _minor,\\n uint256 _patch\\n ) {\\n MAJOR_VERSION = _major;\\n MINOR_VERSION = _minor;\\n PATCH_VERSION = _patch;\\n }\\n\\n /**\\n * @notice Returns the full semver contract version.\\n *\\n * @return Semver contract version as a string.\\n */\\n function version() public view returns (string memory) {\\n return\\n string(\\n abi.encodePacked(\\n Strings.toString(MAJOR_VERSION),\\n \\\".\\\",\\n Strings.toString(MINOR_VERSION),\\n \\\".\\\",\\n Strings.toString(PATCH_VERSION)\\n )\\n );\\n }\\n}\\n\",\"keccak256\":\"0x979b13465de4996a1105850abbf48abe7f71d5e18a8d4af318597ee14c165fdf\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Strings.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev String operations.\\n */\\nlibrary Strings {\\n bytes16 private constant _HEX_SYMBOLS = \\\"0123456789abcdef\\\";\\n uint8 private constant _ADDRESS_LENGTH = 20;\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\\n */\\n function toString(uint256 value) internal pure returns (string memory) {\\n // Inspired by OraclizeAPI's implementation - MIT licence\\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\\n\\n if (value == 0) {\\n return \\\"0\\\";\\n }\\n uint256 temp = value;\\n uint256 digits;\\n while (temp != 0) {\\n digits++;\\n temp /= 10;\\n }\\n bytes memory buffer = new bytes(digits);\\n while (value != 0) {\\n digits -= 1;\\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\\n value /= 10;\\n }\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\\n */\\n function toHexString(uint256 value) internal pure returns (string memory) {\\n if (value == 0) {\\n return \\\"0x00\\\";\\n }\\n uint256 temp = value;\\n uint256 length = 0;\\n while (temp != 0) {\\n length++;\\n temp >>= 8;\\n }\\n return toHexString(value, length);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\\n */\\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\\n bytes memory buffer = new bytes(2 * length + 2);\\n buffer[0] = \\\"0\\\";\\n buffer[1] = \\\"x\\\";\\n for (uint256 i = 2 * length + 1; i > 1; --i) {\\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\\n value >>= 4;\\n }\\n require(value == 0, \\\"Strings: hex length insufficient\\\");\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\\n */\\n function toHexString(address addr) internal pure returns (string memory) {\\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\\n }\\n}\\n\",\"keccak256\":\"0xaf159a8b1923ad2a26d516089bceca9bdeaeacd04be50983ea00ba63070f08a3\",\"license\":\"MIT\"},\"contracts/universal/op-nft/AttestationStation.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.8.15;\\n\\nimport { Semver } from \\\"@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol\\\";\\n\\n/**\\n * @title AttestationStation\\n * @author Optimism Collective\\n * @author Gitcoin\\n * @notice Where attestations live.\\n */\\ncontract AttestationStation is Semver {\\n /**\\n * @notice Struct representing data that is being attested.\\n *\\n * @custom:field about Address for which the attestation is about.\\n * @custom:field key A bytes32 key for the attestation.\\n * @custom:field val The attestation as arbitrary bytes.\\n */\\n struct AttestationData {\\n address about;\\n bytes32 key;\\n bytes val;\\n }\\n\\n /**\\n * @notice Maps addresses to attestations. Creator => About => Key => Value.\\n */\\n mapping(address => mapping(address => mapping(bytes32 => bytes))) public attestations;\\n\\n /**\\n * @notice Emitted when Attestation is created.\\n *\\n * @param creator Address that made the attestation.\\n * @param about Address attestation is about.\\n * @param key Key of the attestation.\\n * @param val Value of the attestation.\\n */\\n event AttestationCreated(\\n address indexed creator,\\n address indexed about,\\n bytes32 indexed key,\\n bytes val\\n );\\n\\n /**\\n * @custom:semver 1.0.0\\n */\\n constructor() Semver(1, 0, 0) {}\\n\\n /**\\n * @notice Allows anyone to create attestations.\\n *\\n * @param _attestations An array of attestation data.\\n */\\n function attest(AttestationData[] memory _attestations) public {\\n uint256 length = _attestations.length;\\n for (uint256 i = 0; i < length; ) {\\n AttestationData memory attestation = _attestations[i];\\n attestations[msg.sender][attestation.about][attestation.key] = attestation.val;\\n\\n emit AttestationCreated(\\n msg.sender,\\n attestation.about,\\n attestation.key,\\n attestation.val\\n );\\n\\n unchecked {\\n ++i;\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x670fde721c291828c3f6343a40bb315dbbcc5e4411125158892ffe54459d69b7\",\"license\":\"MIT\"}},\"version\":1}", "metadata": "{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"creator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"about\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"key\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"val\",\"type\":\"bytes\"}],\"name\":\"AttestationCreated\",\"type\":\"event\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"about\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"key\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"val\",\"type\":\"bytes\"}],\"internalType\":\"struct AttestationStation.AttestationData[]\",\"name\":\"_attestations\",\"type\":\"tuple[]\"}],\"name\":\"attest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_about\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"_key\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"_val\",\"type\":\"bytes\"}],\"name\":\"attest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"attestations\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Optimism CollectiveGitcoin\",\"events\":{\"AttestationCreated(address,address,bytes32,bytes)\":{\"params\":{\"about\":\"Address attestation is about.\",\"creator\":\"Address that made the attestation.\",\"key\":\"Key of the attestation.\",\"val\":\"Value of the attestation.\"}}},\"kind\":\"dev\",\"methods\":{\"attest((address,bytes32,bytes)[])\":{\"params\":{\"_attestations\":\"An array of attestation data.\"}},\"attest(address,bytes32,bytes)\":{\"params\":{\"_about\":\"Address that the attestation is about.\",\"_key\":\"A key used to namespace the attestation.\",\"_val\":\"An arbitrary value stored as part of the attestation.\"}},\"constructor\":{\"custom:semver\":\"1.1.0\"},\"version()\":{\"returns\":{\"_0\":\"Semver contract version as a string.\"}}},\"title\":\"AttestationStation\",\"version\":1},\"userdoc\":{\"events\":{\"AttestationCreated(address,address,bytes32,bytes)\":{\"notice\":\"Emitted when Attestation is created.\"}},\"kind\":\"user\",\"methods\":{\"attest((address,bytes32,bytes)[])\":{\"notice\":\"Allows anyone to create attestations.\"},\"attest(address,bytes32,bytes)\":{\"notice\":\"Allows anyone to create an attestation.\"},\"attestations(address,address,bytes32)\":{\"notice\":\"Maps addresses to attestations. Creator => About => Key => Value.\"},\"version()\":{\"notice\":\"Returns the full semver contract version.\"}},\"notice\":\"Where attestations live.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/universal/op-nft/AttestationStation.sol\":\"AttestationStation\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.15;\\n\\nimport { Strings } from \\\"@openzeppelin/contracts/utils/Strings.sol\\\";\\n\\n/**\\n * @title Semver\\n * @notice Semver is a simple contract for managing contract versions.\\n */\\ncontract Semver {\\n /**\\n * @notice Contract version number (major).\\n */\\n uint256 private immutable MAJOR_VERSION;\\n\\n /**\\n * @notice Contract version number (minor).\\n */\\n uint256 private immutable MINOR_VERSION;\\n\\n /**\\n * @notice Contract version number (patch).\\n */\\n uint256 private immutable PATCH_VERSION;\\n\\n /**\\n * @param _major Version number (major).\\n * @param _minor Version number (minor).\\n * @param _patch Version number (patch).\\n */\\n constructor(\\n uint256 _major,\\n uint256 _minor,\\n uint256 _patch\\n ) {\\n MAJOR_VERSION = _major;\\n MINOR_VERSION = _minor;\\n PATCH_VERSION = _patch;\\n }\\n\\n /**\\n * @notice Returns the full semver contract version.\\n *\\n * @return Semver contract version as a string.\\n */\\n function version() public view returns (string memory) {\\n return\\n string(\\n abi.encodePacked(\\n Strings.toString(MAJOR_VERSION),\\n \\\".\\\",\\n Strings.toString(MINOR_VERSION),\\n \\\".\\\",\\n Strings.toString(PATCH_VERSION)\\n )\\n );\\n }\\n}\\n\",\"keccak256\":\"0x979b13465de4996a1105850abbf48abe7f71d5e18a8d4af318597ee14c165fdf\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Strings.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev String operations.\\n */\\nlibrary Strings {\\n bytes16 private constant _HEX_SYMBOLS = \\\"0123456789abcdef\\\";\\n uint8 private constant _ADDRESS_LENGTH = 20;\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\\n */\\n function toString(uint256 value) internal pure returns (string memory) {\\n // Inspired by OraclizeAPI's implementation - MIT licence\\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\\n\\n if (value == 0) {\\n return \\\"0\\\";\\n }\\n uint256 temp = value;\\n uint256 digits;\\n while (temp != 0) {\\n digits++;\\n temp /= 10;\\n }\\n bytes memory buffer = new bytes(digits);\\n while (value != 0) {\\n digits -= 1;\\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\\n value /= 10;\\n }\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\\n */\\n function toHexString(uint256 value) internal pure returns (string memory) {\\n if (value == 0) {\\n return \\\"0x00\\\";\\n }\\n uint256 temp = value;\\n uint256 length = 0;\\n while (temp != 0) {\\n length++;\\n temp >>= 8;\\n }\\n return toHexString(value, length);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\\n */\\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\\n bytes memory buffer = new bytes(2 * length + 2);\\n buffer[0] = \\\"0\\\";\\n buffer[1] = \\\"x\\\";\\n for (uint256 i = 2 * length + 1; i > 1; --i) {\\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\\n value >>= 4;\\n }\\n require(value == 0, \\\"Strings: hex length insufficient\\\");\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\\n */\\n function toHexString(address addr) internal pure returns (string memory) {\\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\\n }\\n}\\n\",\"keccak256\":\"0xaf159a8b1923ad2a26d516089bceca9bdeaeacd04be50983ea00ba63070f08a3\",\"license\":\"MIT\"},\"contracts/universal/op-nft/AttestationStation.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity 0.8.15;\\n\\nimport { Semver } from \\\"@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol\\\";\\n\\n/**\\n * @title AttestationStation\\n * @author Optimism Collective\\n * @author Gitcoin\\n * @notice Where attestations live.\\n */\\ncontract AttestationStation is Semver {\\n /**\\n * @notice Struct representing data that is being attested.\\n *\\n * @custom:field about Address for which the attestation is about.\\n * @custom:field key A bytes32 key for the attestation.\\n * @custom:field val The attestation as arbitrary bytes.\\n */\\n struct AttestationData {\\n address about;\\n bytes32 key;\\n bytes val;\\n }\\n\\n /**\\n * @notice Maps addresses to attestations. Creator => About => Key => Value.\\n */\\n mapping(address => mapping(address => mapping(bytes32 => bytes))) public attestations;\\n\\n /**\\n * @notice Emitted when Attestation is created.\\n *\\n * @param creator Address that made the attestation.\\n * @param about Address attestation is about.\\n * @param key Key of the attestation.\\n * @param val Value of the attestation.\\n */\\n event AttestationCreated(\\n address indexed creator,\\n address indexed about,\\n bytes32 indexed key,\\n bytes val\\n );\\n\\n /**\\n * @custom:semver 1.1.0\\n */\\n constructor() Semver(1, 1, 0) {}\\n\\n /**\\n * @notice Allows anyone to create an attestation.\\n *\\n * @param _about Address that the attestation is about.\\n * @param _key A key used to namespace the attestation.\\n * @param _val An arbitrary value stored as part of the attestation.\\n */\\n function attest(\\n address _about,\\n bytes32 _key,\\n bytes memory _val\\n ) public {\\n attestations[msg.sender][_about][_key] = _val;\\n\\n emit AttestationCreated(msg.sender, _about, _key, _val);\\n }\\n\\n /**\\n * @notice Allows anyone to create attestations.\\n *\\n * @param _attestations An array of attestation data.\\n */\\n function attest(AttestationData[] calldata _attestations) external {\\n uint256 length = _attestations.length;\\n for (uint256 i = 0; i < length; ) {\\n AttestationData memory attestation = _attestations[i];\\n\\n attest(attestation.about, attestation.key, attestation.val);\\n\\n unchecked {\\n ++i;\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x80718fdc09061ea8f8b99a0b846eee46c5a2a1372dbb0cbc3d40953cb3af19e0\",\"license\":\"MIT\"}},\"version\":1}",
"bytecode": "0x60e060405234801561001057600080fd5b5060016080819052600060a081905260c081905280610aba61004a8239600061018f015260006101660152600061013d0152610aba6000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806329b42cb51461004657806354fd4d501461006f5780635eb5ea1014610077575b600080fd5b610059610054366004610437565b61008c565b60405161006691906104ed565b60405180910390f35b610059610136565b61008a6100853660046105ae565b6101d9565b005b60006020818152938152604080822085529281528281209093528252902080546100b590610737565b80601f01602080910402602001604051908101604052809291908181526020018280546100e190610737565b801561012e5780601f106101035761010080835404028352916020019161012e565b820191906000526020600020905b81548152906001019060200180831161011157829003601f168201915b505050505081565b60606101617f00000000000000000000000000000000000000000000000000000000000000006102d1565b61018a7f00000000000000000000000000000000000000000000000000000000000000006102d1565b6101b37f00000000000000000000000000000000000000000000000000000000000000006102d1565b6040516020016101c59392919061078a565b604051602081830303815290604052905090565b805160005b818110156102cc5760008382815181106101fa576101fa610800565b602090810291909101810151604080820151336000908152808552828120845173ffffffffffffffffffffffffffffffffffffffff1682528552828120848601518252909452922090925090610250908261087d565b508060200151816000015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f28710dfecab43d1e29e02aa56b2e1e610c0bae19135c9cf7a83a1adb6df96d8584604001516040516102bb91906104ed565b60405180910390a4506001016101de565b505050565b60608160000361031457505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b811561033e5780610328816109c6565b91506103379050600a83610a2d565b9150610318565b60008167ffffffffffffffff81111561035957610359610507565b6040519080825280601f01601f191660200182016040528015610383576020820181803683370190505b5090505b841561040657610398600183610a41565b91506103a5600a86610a58565b6103b0906030610a6c565b60f81b8183815181106103c5576103c5610800565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053506103ff600a86610a2d565b9450610387565b949350505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461043257600080fd5b919050565b60008060006060848603121561044c57600080fd5b6104558461040e565b92506104636020850161040e565b9150604084013590509250925092565b60005b8381101561048e578181015183820152602001610476565b8381111561049d576000848401525b50505050565b600081518084526104bb816020860160208601610473565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061050060208301846104a3565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516060810167ffffffffffffffff8111828210171561055957610559610507565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156105a6576105a6610507565b604052919050565b600060208083850312156105c157600080fd5b823567ffffffffffffffff808211156105d957600080fd5b818501915085601f8301126105ed57600080fd5b8135818111156105ff576105ff610507565b8060051b61060e85820161055f565b918252838101850191858101908984111561062857600080fd5b86860192505b8383101561072a578235858111156106465760008081fd5b860160607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0828d03810182131561067d5760008081fd5b610685610536565b6106908b850161040e565b81526040848101358c8301529284013592898411156106af5760008081fd5b83850194508e603f8601126106c657600093508384fd5b8b8501359350898411156106dc576106dc610507565b6106ec8c84601f8701160161055f565b92508383528e818587010111156107035760008081fd5b838186018d85013760009383018c019390935291820152835250918601919086019061062e565b9998505050505050505050565b600181811c9082168061074b57607f821691505b602082108103610784577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b6000845161079c818460208901610473565b80830190507f2e0000000000000000000000000000000000000000000000000000000000000080825285516107d8816001850160208a01610473565b600192019182015283516107f3816002840160208801610473565b0160020195945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b601f8211156102cc57600081815260208120601f850160051c810160208610156108565750805b601f850160051c820191505b8181101561087557828155600101610862565b505050505050565b815167ffffffffffffffff81111561089757610897610507565b6108ab816108a58454610737565b8461082f565b602080601f8311600181146108fe57600084156108c85750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555610875565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b8281101561094b5788860151825594840194600190910190840161092c565b508582101561098757878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036109f7576109f7610997565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082610a3c57610a3c6109fe565b500490565b600082821015610a5357610a53610997565b500390565b600082610a6757610a676109fe565b500690565b60008219821115610a7f57610a7f610997565b50019056fea26469706673582212204abcd56633387050a41a0ae027bbf9077ae2fb5080fa84124e2aa381adb3f98964736f6c634300080f0033", "bytecode": "0x60e060405234801561001057600080fd5b506001608081905260a052600060c05260805160a05160c051610b1c61004f60003960006101ad015260006101840152600061015b0152610b1c6000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806329b42cb51461005157806354fd4d501461007a5780635eb5ea1014610082578063702b9dee14610097575b600080fd5b61006461005f36600461046c565b6100aa565b604051610071919061051e565b60405180910390f35b610064610154565b610095610090366004610538565b6101f7565b005b6100956100a5366004610687565b61025a565b60006020818152938152604080822085529281528281209093528252902080546100d3906106de565b80601f01602080910402602001604051908101604052809291908181526020018280546100ff906106de565b801561014c5780601f106101215761010080835404028352916020019161014c565b820191906000526020600020905b81548152906001019060200180831161012f57829003601f168201915b505050505081565b606061017f7f0000000000000000000000000000000000000000000000000000000000000000610306565b6101a87f0000000000000000000000000000000000000000000000000000000000000000610306565b6101d17f0000000000000000000000000000000000000000000000000000000000000000610306565b6040516020016101e393929190610731565b604051602081830303815290604052905090565b8060005b81811015610254576000848483818110610217576102176107a7565b905060200281019061022991906107d6565b61023290610814565b905061024b81600001518260200151836040015161025a565b506001016101fb565b50505050565b3360009081526020818152604080832073ffffffffffffffffffffffffffffffffffffffff871684528252808320858452909152902061029a82826108df565b50818373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f28710dfecab43d1e29e02aa56b2e1e610c0bae19135c9cf7a83a1adb6df96d85846040516102f9919061051e565b60405180910390a4505050565b60608160000361034957505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b8115610373578061035d81610a28565b915061036c9050600a83610a8f565b915061034d565b60008167ffffffffffffffff81111561038e5761038e6105ad565b6040519080825280601f01601f1916602001820160405280156103b8576020820181803683370190505b5090505b841561043b576103cd600183610aa3565b91506103da600a86610aba565b6103e5906030610ace565b60f81b8183815181106103fa576103fa6107a7565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350610434600a86610a8f565b94506103bc565b949350505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461046757600080fd5b919050565b60008060006060848603121561048157600080fd5b61048a84610443565b925061049860208501610443565b9150604084013590509250925092565b60005b838110156104c35781810151838201526020016104ab565b838111156102545750506000910152565b600081518084526104ec8160208601602086016104a8565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061053160208301846104d4565b9392505050565b6000806020838503121561054b57600080fd5b823567ffffffffffffffff8082111561056357600080fd5b818501915085601f83011261057757600080fd5b81358181111561058657600080fd5b8660208260051b850101111561059b57600080fd5b60209290920196919550909350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f8301126105ed57600080fd5b813567ffffffffffffffff80821115610608576106086105ad565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171561064e5761064e6105ad565b8160405283815286602085880101111561066757600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121561069c57600080fd5b6106a584610443565b925060208401359150604084013567ffffffffffffffff8111156106c857600080fd5b6106d4868287016105dc565b9150509250925092565b600181811c908216806106f257607f821691505b60208210810361072b577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b600084516107438184602089016104a8565b80830190507f2e00000000000000000000000000000000000000000000000000000000000000808252855161077f816001850160208a016104a8565b6001920191820152835161079a8160028401602088016104a8565b0160020195945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa183360301811261080a57600080fd5b9190910192915050565b60006060823603121561082657600080fd5b6040516060810167ffffffffffffffff828210818311171561084a5761084a6105ad565b8160405261085785610443565b835260208501356020840152604085013591508082111561087757600080fd5b50610884368286016105dc565b60408301525092915050565b601f8211156108da57600081815260208120601f850160051c810160208610156108b75750805b601f850160051c820191505b818110156108d6578281556001016108c3565b5050505b505050565b815167ffffffffffffffff8111156108f9576108f96105ad565b61090d8161090784546106de565b84610890565b602080601f831160018114610960576000841561092a5750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b1785556108d6565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156109ad5788860151825594840194600190910190840161098e565b50858210156109e957878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610a5957610a596109f9565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082610a9e57610a9e610a60565b500490565b600082821015610ab557610ab56109f9565b500390565b600082610ac957610ac9610a60565b500690565b60008219821115610ae157610ae16109f9565b50019056fea2646970667358221220bfd4abacc1c2510682b093eea8745255cdd9f45c5b16c168540522a65ddc1f3264736f6c634300080f0033",
"deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c806329b42cb51461004657806354fd4d501461006f5780635eb5ea1014610077575b600080fd5b610059610054366004610437565b61008c565b60405161006691906104ed565b60405180910390f35b610059610136565b61008a6100853660046105ae565b6101d9565b005b60006020818152938152604080822085529281528281209093528252902080546100b590610737565b80601f01602080910402602001604051908101604052809291908181526020018280546100e190610737565b801561012e5780601f106101035761010080835404028352916020019161012e565b820191906000526020600020905b81548152906001019060200180831161011157829003601f168201915b505050505081565b60606101617f00000000000000000000000000000000000000000000000000000000000000006102d1565b61018a7f00000000000000000000000000000000000000000000000000000000000000006102d1565b6101b37f00000000000000000000000000000000000000000000000000000000000000006102d1565b6040516020016101c59392919061078a565b604051602081830303815290604052905090565b805160005b818110156102cc5760008382815181106101fa576101fa610800565b602090810291909101810151604080820151336000908152808552828120845173ffffffffffffffffffffffffffffffffffffffff1682528552828120848601518252909452922090925090610250908261087d565b508060200151816000015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f28710dfecab43d1e29e02aa56b2e1e610c0bae19135c9cf7a83a1adb6df96d8584604001516040516102bb91906104ed565b60405180910390a4506001016101de565b505050565b60608160000361031457505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b811561033e5780610328816109c6565b91506103379050600a83610a2d565b9150610318565b60008167ffffffffffffffff81111561035957610359610507565b6040519080825280601f01601f191660200182016040528015610383576020820181803683370190505b5090505b841561040657610398600183610a41565b91506103a5600a86610a58565b6103b0906030610a6c565b60f81b8183815181106103c5576103c5610800565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053506103ff600a86610a2d565b9450610387565b949350505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461043257600080fd5b919050565b60008060006060848603121561044c57600080fd5b6104558461040e565b92506104636020850161040e565b9150604084013590509250925092565b60005b8381101561048e578181015183820152602001610476565b8381111561049d576000848401525b50505050565b600081518084526104bb816020860160208601610473565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061050060208301846104a3565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040516060810167ffffffffffffffff8111828210171561055957610559610507565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156105a6576105a6610507565b604052919050565b600060208083850312156105c157600080fd5b823567ffffffffffffffff808211156105d957600080fd5b818501915085601f8301126105ed57600080fd5b8135818111156105ff576105ff610507565b8060051b61060e85820161055f565b918252838101850191858101908984111561062857600080fd5b86860192505b8383101561072a578235858111156106465760008081fd5b860160607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0828d03810182131561067d5760008081fd5b610685610536565b6106908b850161040e565b81526040848101358c8301529284013592898411156106af5760008081fd5b83850194508e603f8601126106c657600093508384fd5b8b8501359350898411156106dc576106dc610507565b6106ec8c84601f8701160161055f565b92508383528e818587010111156107035760008081fd5b838186018d85013760009383018c019390935291820152835250918601919086019061062e565b9998505050505050505050565b600181811c9082168061074b57607f821691505b602082108103610784577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b6000845161079c818460208901610473565b80830190507f2e0000000000000000000000000000000000000000000000000000000000000080825285516107d8816001850160208a01610473565b600192019182015283516107f3816002840160208801610473565b0160020195945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b601f8211156102cc57600081815260208120601f850160051c810160208610156108565750805b601f850160051c820191505b8181101561087557828155600101610862565b505050505050565b815167ffffffffffffffff81111561089757610897610507565b6108ab816108a58454610737565b8461082f565b602080601f8311600181146108fe57600084156108c85750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555610875565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b8281101561094b5788860151825594840194600190910190840161092c565b508582101561098757878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036109f7576109f7610997565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082610a3c57610a3c6109fe565b500490565b600082821015610a5357610a53610997565b500390565b600082610a6757610a676109fe565b500690565b60008219821115610a7f57610a7f610997565b50019056fea26469706673582212204abcd56633387050a41a0ae027bbf9077ae2fb5080fa84124e2aa381adb3f98964736f6c634300080f0033", "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c806329b42cb51461005157806354fd4d501461007a5780635eb5ea1014610082578063702b9dee14610097575b600080fd5b61006461005f36600461046c565b6100aa565b604051610071919061051e565b60405180910390f35b610064610154565b610095610090366004610538565b6101f7565b005b6100956100a5366004610687565b61025a565b60006020818152938152604080822085529281528281209093528252902080546100d3906106de565b80601f01602080910402602001604051908101604052809291908181526020018280546100ff906106de565b801561014c5780601f106101215761010080835404028352916020019161014c565b820191906000526020600020905b81548152906001019060200180831161012f57829003601f168201915b505050505081565b606061017f7f0000000000000000000000000000000000000000000000000000000000000000610306565b6101a87f0000000000000000000000000000000000000000000000000000000000000000610306565b6101d17f0000000000000000000000000000000000000000000000000000000000000000610306565b6040516020016101e393929190610731565b604051602081830303815290604052905090565b8060005b81811015610254576000848483818110610217576102176107a7565b905060200281019061022991906107d6565b61023290610814565b905061024b81600001518260200151836040015161025a565b506001016101fb565b50505050565b3360009081526020818152604080832073ffffffffffffffffffffffffffffffffffffffff871684528252808320858452909152902061029a82826108df565b50818373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f28710dfecab43d1e29e02aa56b2e1e610c0bae19135c9cf7a83a1adb6df96d85846040516102f9919061051e565b60405180910390a4505050565b60608160000361034957505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b8115610373578061035d81610a28565b915061036c9050600a83610a8f565b915061034d565b60008167ffffffffffffffff81111561038e5761038e6105ad565b6040519080825280601f01601f1916602001820160405280156103b8576020820181803683370190505b5090505b841561043b576103cd600183610aa3565b91506103da600a86610aba565b6103e5906030610ace565b60f81b8183815181106103fa576103fa6107a7565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350610434600a86610a8f565b94506103bc565b949350505050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461046757600080fd5b919050565b60008060006060848603121561048157600080fd5b61048a84610443565b925061049860208501610443565b9150604084013590509250925092565b60005b838110156104c35781810151838201526020016104ab565b838111156102545750506000910152565b600081518084526104ec8160208601602086016104a8565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061053160208301846104d4565b9392505050565b6000806020838503121561054b57600080fd5b823567ffffffffffffffff8082111561056357600080fd5b818501915085601f83011261057757600080fd5b81358181111561058657600080fd5b8660208260051b850101111561059b57600080fd5b60209290920196919550909350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f8301126105ed57600080fd5b813567ffffffffffffffff80821115610608576106086105ad565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171561064e5761064e6105ad565b8160405283815286602085880101111561066757600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008060006060848603121561069c57600080fd5b6106a584610443565b925060208401359150604084013567ffffffffffffffff8111156106c857600080fd5b6106d4868287016105dc565b9150509250925092565b600181811c908216806106f257607f821691505b60208210810361072b577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b600084516107438184602089016104a8565b80830190507f2e00000000000000000000000000000000000000000000000000000000000000808252855161077f816001850160208a016104a8565b6001920191820152835161079a8160028401602088016104a8565b0160020195945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa183360301811261080a57600080fd5b9190910192915050565b60006060823603121561082657600080fd5b6040516060810167ffffffffffffffff828210818311171561084a5761084a6105ad565b8160405261085785610443565b835260208501356020840152604085013591508082111561087757600080fd5b50610884368286016105dc565b60408301525092915050565b601f8211156108da57600081815260208120601f850160051c810160208610156108b75750805b601f850160051c820191505b818110156108d6578281556001016108c3565b5050505b505050565b815167ffffffffffffffff8111156108f9576108f96105ad565b61090d8161090784546106de565b84610890565b602080601f831160018114610960576000841561092a5750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b1785556108d6565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156109ad5788860151825594840194600190910190840161098e565b50858210156109e957878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610a5957610a596109f9565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082610a9e57610a9e610a60565b500490565b600082821015610ab557610ab56109f9565b500390565b600082610ac957610ac9610a60565b500690565b60008219821115610ae157610ae16109f9565b50019056fea2646970667358221220bfd4abacc1c2510682b093eea8745255cdd9f45c5b16c168540522a65ddc1f3264736f6c634300080f0033",
"devdoc": { "devdoc": {
"author": "Optimism CollectiveGitcoin", "author": "Optimism CollectiveGitcoin",
"events": { "events": {
...@@ -151,8 +174,15 @@ ...@@ -151,8 +174,15 @@
"_attestations": "An array of attestation data." "_attestations": "An array of attestation data."
} }
}, },
"attest(address,bytes32,bytes)": {
"params": {
"_about": "Address that the attestation is about.",
"_key": "A key used to namespace the attestation.",
"_val": "An arbitrary value stored as part of the attestation."
}
},
"constructor": { "constructor": {
"custom:semver": "1.0.0" "custom:semver": "1.1.0"
}, },
"version()": { "version()": {
"returns": { "returns": {
...@@ -174,6 +204,9 @@ ...@@ -174,6 +204,9 @@
"attest((address,bytes32,bytes)[])": { "attest((address,bytes32,bytes)[])": {
"notice": "Allows anyone to create attestations." "notice": "Allows anyone to create attestations."
}, },
"attest(address,bytes32,bytes)": {
"notice": "Allows anyone to create an attestation."
},
"attestations(address,address,bytes32)": { "attestations(address,address,bytes32)": {
"notice": "Maps addresses to attestations. Creator => About => Key => Value." "notice": "Maps addresses to attestations. Creator => About => Key => Value."
}, },
...@@ -187,7 +220,7 @@ ...@@ -187,7 +220,7 @@
"storageLayout": { "storageLayout": {
"storage": [ "storage": [
{ {
"astId": 8191, "astId": 2135,
"contract": "contracts/universal/op-nft/AttestationStation.sol:AttestationStation", "contract": "contracts/universal/op-nft/AttestationStation.sol:AttestationStation",
"label": "attestations", "label": "attestations",
"offset": 0, "offset": 0,
......
{
"language": "Solidity",
"sources": {
"contracts/universal/op-nft/AttestationStation.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.15;\n\nimport { Semver } from \"@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol\";\n\n/**\n * @title AttestationStation\n * @author Optimism Collective\n * @author Gitcoin\n * @notice Where attestations live.\n */\ncontract AttestationStation is Semver {\n /**\n * @notice Struct representing data that is being attested.\n *\n * @custom:field about Address for which the attestation is about.\n * @custom:field key A bytes32 key for the attestation.\n * @custom:field val The attestation as arbitrary bytes.\n */\n struct AttestationData {\n address about;\n bytes32 key;\n bytes val;\n }\n\n /**\n * @notice Maps addresses to attestations. Creator => About => Key => Value.\n */\n mapping(address => mapping(address => mapping(bytes32 => bytes))) public attestations;\n\n /**\n * @notice Emitted when Attestation is created.\n *\n * @param creator Address that made the attestation.\n * @param about Address attestation is about.\n * @param key Key of the attestation.\n * @param val Value of the attestation.\n */\n event AttestationCreated(\n address indexed creator,\n address indexed about,\n bytes32 indexed key,\n bytes val\n );\n\n /**\n * @custom:semver 1.1.0\n */\n constructor() Semver(1, 1, 0) {}\n\n /**\n * @notice Allows anyone to create an attestation.\n *\n * @param _about Address that the attestation is about.\n * @param _key A key used to namespace the attestation.\n * @param _val An arbitrary value stored as part of the attestation.\n */\n function attest(\n address _about,\n bytes32 _key,\n bytes memory _val\n ) public {\n attestations[msg.sender][_about][_key] = _val;\n\n emit AttestationCreated(msg.sender, _about, _key, _val);\n }\n\n /**\n * @notice Allows anyone to create attestations.\n *\n * @param _attestations An array of attestation data.\n */\n function attest(AttestationData[] calldata _attestations) external {\n uint256 length = _attestations.length;\n for (uint256 i = 0; i < length; ) {\n AttestationData memory attestation = _attestations[i];\n\n attest(attestation.about, attestation.key, attestation.val);\n\n unchecked {\n ++i;\n }\n }\n }\n}\n"
},
"@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.15;\n\nimport { Strings } from \"@openzeppelin/contracts/utils/Strings.sol\";\n\n/**\n * @title Semver\n * @notice Semver is a simple contract for managing contract versions.\n */\ncontract Semver {\n /**\n * @notice Contract version number (major).\n */\n uint256 private immutable MAJOR_VERSION;\n\n /**\n * @notice Contract version number (minor).\n */\n uint256 private immutable MINOR_VERSION;\n\n /**\n * @notice Contract version number (patch).\n */\n uint256 private immutable PATCH_VERSION;\n\n /**\n * @param _major Version number (major).\n * @param _minor Version number (minor).\n * @param _patch Version number (patch).\n */\n constructor(\n uint256 _major,\n uint256 _minor,\n uint256 _patch\n ) {\n MAJOR_VERSION = _major;\n MINOR_VERSION = _minor;\n PATCH_VERSION = _patch;\n }\n\n /**\n * @notice Returns the full semver contract version.\n *\n * @return Semver contract version as a string.\n */\n function version() public view returns (string memory) {\n return\n string(\n abi.encodePacked(\n Strings.toString(MAJOR_VERSION),\n \".\",\n Strings.toString(MINOR_VERSION),\n \".\",\n Strings.toString(PATCH_VERSION)\n )\n );\n }\n}\n"
},
"@openzeppelin/contracts/utils/Strings.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\n uint8 private constant _ADDRESS_LENGTH = 20;\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n // Inspired by OraclizeAPI's implementation - MIT licence\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\n\n if (value == 0) {\n return \"0\";\n }\n uint256 temp = value;\n uint256 digits;\n while (temp != 0) {\n digits++;\n temp /= 10;\n }\n bytes memory buffer = new bytes(digits);\n while (value != 0) {\n digits -= 1;\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\n value /= 10;\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n if (value == 0) {\n return \"0x00\";\n }\n uint256 temp = value;\n uint256 length = 0;\n while (temp != 0) {\n length++;\n temp >>= 8;\n }\n return toHexString(value, length);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\n */\n function toHexString(address addr) internal pure returns (string memory) {\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\n }\n}\n"
},
"contracts/universal/op-nft/Optimist.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.15;\n\nimport { Semver } from \"@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol\";\nimport {\n ERC721BurnableUpgradeable\n} from \"@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol\";\nimport { AttestationStation } from \"./AttestationStation.sol\";\nimport { Strings } from \"@openzeppelin/contracts/utils/Strings.sol\";\n\n/**\n * @author Optimism Collective\n * @author Gitcoin\n * @title Optimist\n * @notice A Soul Bound Token for real humans only(tm).\n */\ncontract Optimist is ERC721BurnableUpgradeable, Semver {\n /**\n * @notice Address of the AttestationStation contract.\n */\n AttestationStation public immutable ATTESTATION_STATION;\n\n /**\n * @notice Attestor who attests to baseURI and allowlist.\n */\n address public immutable ATTESTOR;\n\n /**\n * @custom:semver 1.0.0\n * @param _name Token name.\n * @param _symbol Token symbol.\n * @param _attestor Address of the attestor.\n * @param _attestationStation Address of the AttestationStation contract.\n */\n constructor(\n string memory _name,\n string memory _symbol,\n address _attestor,\n AttestationStation _attestationStation\n ) Semver(1, 0, 0) {\n ATTESTOR = _attestor;\n ATTESTATION_STATION = _attestationStation;\n initialize(_name, _symbol);\n }\n\n /**\n * @notice Initializes the Optimist contract.\n *\n * @param _name Token name.\n * @param _symbol Token symbol.\n */\n function initialize(string memory _name, string memory _symbol) public initializer {\n __ERC721_init(_name, _symbol);\n __ERC721Burnable_init();\n }\n\n /**\n * @notice Allows an address to mint an Optimist NFT. Token ID is the uint256 representation\n * of the recipient's address. Recipients must be permitted to mint, eventually anyone\n * will be able to mint. One token per address.\n *\n * @param _recipient Address of the token recipient.\n */\n function mint(address _recipient) public {\n require(isOnAllowList(_recipient), \"Optimist: address is not on allowList\");\n _safeMint(_recipient, tokenIdOfAddress(_recipient));\n }\n\n /**\n * @notice Returns the baseURI for all tokens.\n *\n * @return BaseURI for all tokens.\n */\n function baseURI() public view returns (string memory) {\n return\n string(\n abi.encodePacked(\n ATTESTATION_STATION.attestations(\n ATTESTOR,\n address(this),\n bytes32(\"optimist.base-uri\")\n )\n )\n );\n }\n\n /**\n * @notice Returns the token URI for a given token by ID\n *\n * @param _tokenId Token ID to query.\n\n * @return Token URI for the given token by ID.\n */\n function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) {\n return\n string(\n abi.encodePacked(\n baseURI(),\n \"/\",\n // Properly format the token ID as a 20 byte hex string (address).\n Strings.toHexString(_tokenId, 20),\n \".json\"\n )\n );\n }\n\n /**\n * @notice Checks whether a given address is allowed to mint the Optimist NFT yet. Since the\n * Optimist NFT will also be used as part of the Citizens House, mints are currently\n * restricted. Eventually anyone will be able to mint.\n *\n * @return Whether or not the address is allowed to mint yet.\n */\n function isOnAllowList(address _recipient) public view returns (bool) {\n return\n ATTESTATION_STATION\n .attestations(ATTESTOR, _recipient, bytes32(\"optimist.can-mint\"))\n .length > 0;\n }\n\n /**\n * @notice Returns the token ID for the token owned by a given address. This is the uint256\n * representation of the given address.\n *\n * @return Token ID for the token owned by the given address.\n */\n function tokenIdOfAddress(address _owner) public pure returns (uint256) {\n return uint256(uint160(_owner));\n }\n\n /**\n * @notice Disabled for the Optimist NFT (Soul Bound Token).\n */\n function approve(address, uint256) public pure override {\n revert(\"Optimist: soul bound token\");\n }\n\n /**\n * @notice Disabled for the Optimist NFT (Soul Bound Token).\n */\n function setApprovalForAll(address, bool) public virtual override {\n revert(\"Optimist: soul bound token\");\n }\n\n /**\n * @notice Prevents transfers of the Optimist NFT (Soul Bound Token).\n *\n * @param _from Address of the token sender.\n * @param _to Address of the token recipient.\n */\n function _beforeTokenTransfer(\n address _from,\n address _to,\n uint256\n ) internal virtual override {\n require(_from == address(0) || _to == address(0), \"Optimist: soul bound token\");\n }\n}\n"
},
"@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/extensions/ERC721Burnable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../ERC721Upgradeable.sol\";\nimport \"../../../utils/ContextUpgradeable.sol\";\nimport \"../../../proxy/utils/Initializable.sol\";\n\n/**\n * @title ERC721 Burnable Token\n * @dev ERC721 Token that can be burned (destroyed).\n */\nabstract contract ERC721BurnableUpgradeable is Initializable, ContextUpgradeable, ERC721Upgradeable {\n function __ERC721Burnable_init() internal onlyInitializing {\n }\n\n function __ERC721Burnable_init_unchained() internal onlyInitializing {\n }\n /**\n * @dev Burns `tokenId`. See {ERC721-_burn}.\n *\n * Requirements:\n *\n * - The caller must own `tokenId` or be an approved operator.\n */\n function burn(uint256 tokenId) public virtual {\n //solhint-disable-next-line max-line-length\n require(_isApprovedOrOwner(_msgSender(), tokenId), \"ERC721: caller is not token owner nor approved\");\n _burn(tokenId);\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[50] private __gap;\n}\n"
},
"@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/ERC721.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC721Upgradeable.sol\";\nimport \"./IERC721ReceiverUpgradeable.sol\";\nimport \"./extensions/IERC721MetadataUpgradeable.sol\";\nimport \"../../utils/AddressUpgradeable.sol\";\nimport \"../../utils/ContextUpgradeable.sol\";\nimport \"../../utils/StringsUpgradeable.sol\";\nimport \"../../utils/introspection/ERC165Upgradeable.sol\";\nimport \"../../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including\n * the Metadata extension, but not including the Enumerable extension, which is available separately as\n * {ERC721Enumerable}.\n */\ncontract ERC721Upgradeable is Initializable, ContextUpgradeable, ERC165Upgradeable, IERC721Upgradeable, IERC721MetadataUpgradeable {\n using AddressUpgradeable for address;\n using StringsUpgradeable for uint256;\n\n // Token name\n string private _name;\n\n // Token symbol\n string private _symbol;\n\n // Mapping from token ID to owner address\n mapping(uint256 => address) private _owners;\n\n // Mapping owner address to token count\n mapping(address => uint256) private _balances;\n\n // Mapping from token ID to approved address\n mapping(uint256 => address) private _tokenApprovals;\n\n // Mapping from owner to operator approvals\n mapping(address => mapping(address => bool)) private _operatorApprovals;\n\n /**\n * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.\n */\n function __ERC721_init(string memory name_, string memory symbol_) internal onlyInitializing {\n __ERC721_init_unchained(name_, symbol_);\n }\n\n function __ERC721_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165Upgradeable) returns (bool) {\n return\n interfaceId == type(IERC721Upgradeable).interfaceId ||\n interfaceId == type(IERC721MetadataUpgradeable).interfaceId ||\n super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev See {IERC721-balanceOf}.\n */\n function balanceOf(address owner) public view virtual override returns (uint256) {\n require(owner != address(0), \"ERC721: address zero is not a valid owner\");\n return _balances[owner];\n }\n\n /**\n * @dev See {IERC721-ownerOf}.\n */\n function ownerOf(uint256 tokenId) public view virtual override returns (address) {\n address owner = _owners[tokenId];\n require(owner != address(0), \"ERC721: invalid token ID\");\n return owner;\n }\n\n /**\n * @dev See {IERC721Metadata-name}.\n */\n function name() public view virtual override returns (string memory) {\n return _name;\n }\n\n /**\n * @dev See {IERC721Metadata-symbol}.\n */\n function symbol() public view virtual override returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev See {IERC721Metadata-tokenURI}.\n */\n function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {\n _requireMinted(tokenId);\n\n string memory baseURI = _baseURI();\n return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : \"\";\n }\n\n /**\n * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each\n * token will be the concatenation of the `baseURI` and the `tokenId`. Empty\n * by default, can be overridden in child contracts.\n */\n function _baseURI() internal view virtual returns (string memory) {\n return \"\";\n }\n\n /**\n * @dev See {IERC721-approve}.\n */\n function approve(address to, uint256 tokenId) public virtual override {\n address owner = ERC721Upgradeable.ownerOf(tokenId);\n require(to != owner, \"ERC721: approval to current owner\");\n\n require(\n _msgSender() == owner || isApprovedForAll(owner, _msgSender()),\n \"ERC721: approve caller is not token owner nor approved for all\"\n );\n\n _approve(to, tokenId);\n }\n\n /**\n * @dev See {IERC721-getApproved}.\n */\n function getApproved(uint256 tokenId) public view virtual override returns (address) {\n _requireMinted(tokenId);\n\n return _tokenApprovals[tokenId];\n }\n\n /**\n * @dev See {IERC721-setApprovalForAll}.\n */\n function setApprovalForAll(address operator, bool approved) public virtual override {\n _setApprovalForAll(_msgSender(), operator, approved);\n }\n\n /**\n * @dev See {IERC721-isApprovedForAll}.\n */\n function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {\n return _operatorApprovals[owner][operator];\n }\n\n /**\n * @dev See {IERC721-transferFrom}.\n */\n function transferFrom(\n address from,\n address to,\n uint256 tokenId\n ) public virtual override {\n //solhint-disable-next-line max-line-length\n require(_isApprovedOrOwner(_msgSender(), tokenId), \"ERC721: caller is not token owner nor approved\");\n\n _transfer(from, to, tokenId);\n }\n\n /**\n * @dev See {IERC721-safeTransferFrom}.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId\n ) public virtual override {\n safeTransferFrom(from, to, tokenId, \"\");\n }\n\n /**\n * @dev See {IERC721-safeTransferFrom}.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId,\n bytes memory data\n ) public virtual override {\n require(_isApprovedOrOwner(_msgSender(), tokenId), \"ERC721: caller is not token owner nor approved\");\n _safeTransfer(from, to, tokenId, data);\n }\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\n *\n * `data` is additional data, it has no specified format and it is sent in call to `to`.\n *\n * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.\n * implement alternative mechanisms to perform token transfer, such as signature-based.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function _safeTransfer(\n address from,\n address to,\n uint256 tokenId,\n bytes memory data\n ) internal virtual {\n _transfer(from, to, tokenId);\n require(_checkOnERC721Received(from, to, tokenId, data), \"ERC721: transfer to non ERC721Receiver implementer\");\n }\n\n /**\n * @dev Returns whether `tokenId` exists.\n *\n * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.\n *\n * Tokens start existing when they are minted (`_mint`),\n * and stop existing when they are burned (`_burn`).\n */\n function _exists(uint256 tokenId) internal view virtual returns (bool) {\n return _owners[tokenId] != address(0);\n }\n\n /**\n * @dev Returns whether `spender` is allowed to manage `tokenId`.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {\n address owner = ERC721Upgradeable.ownerOf(tokenId);\n return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);\n }\n\n /**\n * @dev Safely mints `tokenId` and transfers it to `to`.\n *\n * Requirements:\n *\n * - `tokenId` must not exist.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function _safeMint(address to, uint256 tokenId) internal virtual {\n _safeMint(to, tokenId, \"\");\n }\n\n /**\n * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is\n * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.\n */\n function _safeMint(\n address to,\n uint256 tokenId,\n bytes memory data\n ) internal virtual {\n _mint(to, tokenId);\n require(\n _checkOnERC721Received(address(0), to, tokenId, data),\n \"ERC721: transfer to non ERC721Receiver implementer\"\n );\n }\n\n /**\n * @dev Mints `tokenId` and transfers it to `to`.\n *\n * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible\n *\n * Requirements:\n *\n * - `tokenId` must not exist.\n * - `to` cannot be the zero address.\n *\n * Emits a {Transfer} event.\n */\n function _mint(address to, uint256 tokenId) internal virtual {\n require(to != address(0), \"ERC721: mint to the zero address\");\n require(!_exists(tokenId), \"ERC721: token already minted\");\n\n _beforeTokenTransfer(address(0), to, tokenId);\n\n _balances[to] += 1;\n _owners[tokenId] = to;\n\n emit Transfer(address(0), to, tokenId);\n\n _afterTokenTransfer(address(0), to, tokenId);\n }\n\n /**\n * @dev Destroys `tokenId`.\n * The approval is cleared when the token is burned.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n *\n * Emits a {Transfer} event.\n */\n function _burn(uint256 tokenId) internal virtual {\n address owner = ERC721Upgradeable.ownerOf(tokenId);\n\n _beforeTokenTransfer(owner, address(0), tokenId);\n\n // Clear approvals\n _approve(address(0), tokenId);\n\n _balances[owner] -= 1;\n delete _owners[tokenId];\n\n emit Transfer(owner, address(0), tokenId);\n\n _afterTokenTransfer(owner, address(0), tokenId);\n }\n\n /**\n * @dev Transfers `tokenId` from `from` to `to`.\n * As opposed to {transferFrom}, this imposes no restrictions on msg.sender.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - `tokenId` token must be owned by `from`.\n *\n * Emits a {Transfer} event.\n */\n function _transfer(\n address from,\n address to,\n uint256 tokenId\n ) internal virtual {\n require(ERC721Upgradeable.ownerOf(tokenId) == from, \"ERC721: transfer from incorrect owner\");\n require(to != address(0), \"ERC721: transfer to the zero address\");\n\n _beforeTokenTransfer(from, to, tokenId);\n\n // Clear approvals from the previous owner\n _approve(address(0), tokenId);\n\n _balances[from] -= 1;\n _balances[to] += 1;\n _owners[tokenId] = to;\n\n emit Transfer(from, to, tokenId);\n\n _afterTokenTransfer(from, to, tokenId);\n }\n\n /**\n * @dev Approve `to` to operate on `tokenId`\n *\n * Emits an {Approval} event.\n */\n function _approve(address to, uint256 tokenId) internal virtual {\n _tokenApprovals[tokenId] = to;\n emit Approval(ERC721Upgradeable.ownerOf(tokenId), to, tokenId);\n }\n\n /**\n * @dev Approve `operator` to operate on all of `owner` tokens\n *\n * Emits an {ApprovalForAll} event.\n */\n function _setApprovalForAll(\n address owner,\n address operator,\n bool approved\n ) internal virtual {\n require(owner != operator, \"ERC721: approve to caller\");\n _operatorApprovals[owner][operator] = approved;\n emit ApprovalForAll(owner, operator, approved);\n }\n\n /**\n * @dev Reverts if the `tokenId` has not been minted yet.\n */\n function _requireMinted(uint256 tokenId) internal view virtual {\n require(_exists(tokenId), \"ERC721: invalid token ID\");\n }\n\n /**\n * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.\n * The call is not executed if the target address is not a contract.\n *\n * @param from address representing the previous owner of the given token ID\n * @param to target address that will receive the tokens\n * @param tokenId uint256 ID of the token to be transferred\n * @param data bytes optional data to send along with the call\n * @return bool whether the call correctly returned the expected magic value\n */\n function _checkOnERC721Received(\n address from,\n address to,\n uint256 tokenId,\n bytes memory data\n ) private returns (bool) {\n if (to.isContract()) {\n try IERC721ReceiverUpgradeable(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {\n return retval == IERC721ReceiverUpgradeable.onERC721Received.selector;\n } catch (bytes memory reason) {\n if (reason.length == 0) {\n revert(\"ERC721: transfer to non ERC721Receiver implementer\");\n } else {\n /// @solidity memory-safe-assembly\n assembly {\n revert(add(32, reason), mload(reason))\n }\n }\n }\n } else {\n return true;\n }\n }\n\n /**\n * @dev Hook that is called before any token transfer. This includes minting\n * and burning.\n *\n * Calling conditions:\n *\n * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be\n * transferred to `to`.\n * - When `from` is zero, `tokenId` will be minted for `to`.\n * - When `to` is zero, ``from``'s `tokenId` will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 tokenId\n ) internal virtual {}\n\n /**\n * @dev Hook that is called after any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _afterTokenTransfer(\n address from,\n address to,\n uint256 tokenId\n ) internal virtual {}\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[44] private __gap;\n}\n"
},
"@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\nimport \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract ContextUpgradeable is Initializable {\n function __Context_init() internal onlyInitializing {\n }\n\n function __Context_init_unchained() internal onlyInitializing {\n }\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[50] private __gap;\n}\n"
},
"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol)\n\npragma solidity ^0.8.2;\n\nimport \"../../utils/AddressUpgradeable.sol\";\n\n/**\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\n *\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\n * reused. This mechanism prevents re-execution of each \"step\" but allows the creation of new initialization steps in\n * case an upgrade adds a module that needs to be initialized.\n *\n * For example:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * contract MyToken is ERC20Upgradeable {\n * function initialize() initializer public {\n * __ERC20_init(\"MyToken\", \"MTK\");\n * }\n * }\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\n * function initializeV2() reinitializer(2) public {\n * __ERC20Permit_init(\"MyToken\");\n * }\n * }\n * ```\n *\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\n *\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\n *\n * [CAUTION]\n * ====\n * Avoid leaving a contract uninitialized.\n *\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * /// @custom:oz-upgrades-unsafe-allow constructor\n * constructor() {\n * _disableInitializers();\n * }\n * ```\n * ====\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n * @custom:oz-retyped-from bool\n */\n uint8 private _initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private _initializing;\n\n /**\n * @dev Triggered when the contract has been initialized or reinitialized.\n */\n event Initialized(uint8 version);\n\n /**\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\n * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`.\n */\n modifier initializer() {\n bool isTopLevelCall = !_initializing;\n require(\n (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),\n \"Initializable: contract is already initialized\"\n );\n _initialized = 1;\n if (isTopLevelCall) {\n _initializing = true;\n }\n _;\n if (isTopLevelCall) {\n _initializing = false;\n emit Initialized(1);\n }\n }\n\n /**\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\n * used to initialize parent contracts.\n *\n * `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original\n * initialization step. This is essential to configure modules that are added through upgrades and that require\n * initialization.\n *\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\n * a contract, executing them in the right order is up to the developer or operator.\n */\n modifier reinitializer(uint8 version) {\n require(!_initializing && _initialized < version, \"Initializable: contract is already initialized\");\n _initialized = version;\n _initializing = true;\n _;\n _initializing = false;\n emit Initialized(version);\n }\n\n /**\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\n */\n modifier onlyInitializing() {\n require(_initializing, \"Initializable: contract is not initializing\");\n _;\n }\n\n /**\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\n * through proxies.\n */\n function _disableInitializers() internal virtual {\n require(!_initializing, \"Initializable: contract is initializing\");\n if (_initialized < type(uint8).max) {\n _initialized = type(uint8).max;\n emit Initialized(type(uint8).max);\n }\n }\n}\n"
},
"@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../../utils/introspection/IERC165Upgradeable.sol\";\n\n/**\n * @dev Required interface of an ERC721 compliant contract.\n */\ninterface IERC721Upgradeable is IERC165Upgradeable {\n /**\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\n */\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\n */\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\n */\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n /**\n * @dev Returns the number of tokens in ``owner``'s account.\n */\n function balanceOf(address owner) external view returns (uint256 balance);\n\n /**\n * @dev Returns the owner of the `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function ownerOf(uint256 tokenId) external view returns (address owner);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId,\n bytes calldata data\n ) external;\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Transfers `tokenId` token from `from` to `to`.\n *\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\n * The approval is cleared when the token is transferred.\n *\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\n *\n * Requirements:\n *\n * - The caller must own the token or be an approved operator.\n * - `tokenId` must exist.\n *\n * Emits an {Approval} event.\n */\n function approve(address to, uint256 tokenId) external;\n\n /**\n * @dev Approve or remove `operator` as an operator for the caller.\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\n *\n * Requirements:\n *\n * - The `operator` cannot be the caller.\n *\n * Emits an {ApprovalForAll} event.\n */\n function setApprovalForAll(address operator, bool _approved) external;\n\n /**\n * @dev Returns the account approved for `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function getApproved(uint256 tokenId) external view returns (address operator);\n\n /**\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\n *\n * See {setApprovalForAll}\n */\n function isApprovedForAll(address owner, address operator) external view returns (bool);\n}\n"
},
"@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @title ERC721 token receiver interface\n * @dev Interface for any contract that wants to support safeTransfers\n * from ERC721 asset contracts.\n */\ninterface IERC721ReceiverUpgradeable {\n /**\n * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}\n * by `operator` from `from`, this function is called.\n *\n * It must return its Solidity selector to confirm the token transfer.\n * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.\n *\n * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.\n */\n function onERC721Received(\n address operator,\n address from,\n uint256 tokenId,\n bytes calldata data\n ) external returns (bytes4);\n}\n"
},
"@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721MetadataUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC721Upgradeable.sol\";\n\n/**\n * @title ERC-721 Non-Fungible Token Standard, optional metadata extension\n * @dev See https://eips.ethereum.org/EIPS/eip-721\n */\ninterface IERC721MetadataUpgradeable is IERC721Upgradeable {\n /**\n * @dev Returns the token collection name.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the token collection symbol.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.\n */\n function tokenURI(uint256 tokenId) external view returns (string memory);\n}\n"
},
"@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary AddressUpgradeable {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n /// @solidity memory-safe-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n"
},
"@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev String operations.\n */\nlibrary StringsUpgradeable {\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\n uint8 private constant _ADDRESS_LENGTH = 20;\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n // Inspired by OraclizeAPI's implementation - MIT licence\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\n\n if (value == 0) {\n return \"0\";\n }\n uint256 temp = value;\n uint256 digits;\n while (temp != 0) {\n digits++;\n temp /= 10;\n }\n bytes memory buffer = new bytes(digits);\n while (value != 0) {\n digits -= 1;\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\n value /= 10;\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n if (value == 0) {\n return \"0x00\";\n }\n uint256 temp = value;\n uint256 length = 0;\n while (temp != 0) {\n length++;\n temp >>= 8;\n }\n return toHexString(value, length);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\n */\n function toHexString(address addr) internal pure returns (string memory) {\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\n }\n}\n"
},
"@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC165Upgradeable.sol\";\nimport \"../../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Implementation of the {IERC165} interface.\n *\n * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check\n * for the additional interface id that will be supported. For example:\n *\n * ```solidity\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\n * }\n * ```\n *\n * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\n */\nabstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable {\n function __ERC165_init() internal onlyInitializing {\n }\n\n function __ERC165_init_unchained() internal onlyInitializing {\n }\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IERC165Upgradeable).interfaceId;\n }\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\n */\n uint256[50] private __gap;\n}\n"
},
"@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165Upgradeable {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 10000
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata",
"devdoc",
"userdoc",
"storageLayout",
"evm.gasEstimates"
],
"": [
"ast"
]
}
},
"metadata": {
"useLiteralContent": true
}
}
}
\ No newline at end of file
...@@ -16,10 +16,9 @@ ...@@ -16,10 +16,9 @@
"build": "yarn build:hh", "build": "yarn build:hh",
"build:hh": "hardhat compile --show-stack-traces", "build:hh": "hardhat compile --show-stack-traces",
"build:forge": "forge build", "build:forge": "forge build",
"test": "yarn test:contracts", "test": "yarn test:forge",
"test:contracts": "hardhat test --show-stack-traces",
"test:forge": "forge test", "test:forge": "forge test",
"test:coverage": "NODE_OPTIONS=--max_old_space_size=8192 hardhat coverage && yarn test:coverage:forge", "test:coverage": "yarn test:coverage:forge",
"test:coverage:forge": "forge coverage", "test:coverage:forge": "forge coverage",
"test:slither": "slither .", "test:slither": "slither .",
"gas-snapshot": "forge snapshot", "gas-snapshot": "forge snapshot",
...@@ -54,7 +53,6 @@ ...@@ -54,7 +53,6 @@
"url": "https://github.com/ethereum-optimism/optimism.git" "url": "https://github.com/ethereum-optimism/optimism.git"
}, },
"devDependencies": { "devDependencies": {
"@defi-wonderland/smock": "^2.0.7",
"@eth-optimism/contracts-bedrock": "0.11.3", "@eth-optimism/contracts-bedrock": "0.11.3",
"@eth-optimism/core-utils": "^0.12.0", "@eth-optimism/core-utils": "^0.12.0",
"@eth-optimism/hardhat-deploy-config": "^0.2.5", "@eth-optimism/hardhat-deploy-config": "^0.2.5",
...@@ -65,8 +63,6 @@ ...@@ -65,8 +63,6 @@
"@rari-capital/solmate": "7.0.0-alpha.3", "@rari-capital/solmate": "7.0.0-alpha.3",
"@openzeppelin/contracts": "4.7.3", "@openzeppelin/contracts": "4.7.3",
"@openzeppelin/contracts-upgradeable": "4.7.3", "@openzeppelin/contracts-upgradeable": "4.7.3",
"@types/chai": "^4.2.18",
"@types/mocha": "^8.2.2",
"@types/node": "^17.0.21", "@types/node": "^17.0.21",
"@typechain/ethers-v5": "^10.1.0", "@typechain/ethers-v5": "^10.1.0",
"@typechain/hardhat": "^6.1.2", "@typechain/hardhat": "^6.1.2",
...@@ -82,7 +78,6 @@ ...@@ -82,7 +78,6 @@
"hardhat-deploy": "^0.11.10", "hardhat-deploy": "^0.11.10",
"hardhat-gas-reporter": "^1.0.8", "hardhat-gas-reporter": "^1.0.8",
"lint-staged": "11.0.0", "lint-staged": "11.0.0",
"mocha": "^10.0.0",
"mkdirp": "^1.0.4", "mkdirp": "^1.0.4",
"node-fetch": "^2.6.7", "node-fetch": "^2.6.7",
"prettier": "^2.8.0", "prettier": "^2.8.0",
......
import hre from 'hardhat'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { Contract } from 'ethers'
import { expect } from '../../setup'
import { deploy } from '../../helpers'
describe('AssetReceiver', () => {
const DEFAULT_TOKEN_ID = 0
const DEFAULT_AMOUNT = hre.ethers.constants.WeiPerEther
const DEFAULT_RECIPIENT = '0x' + '11'.repeat(20)
let signer1: SignerWithAddress
let signer2: SignerWithAddress
before('signer setup', async () => {
;[signer1, signer2] = await hre.ethers.getSigners()
})
let TestERC20: Contract
let TestERC721: Contract
let AssetReceiver: Contract
beforeEach('deploy contracts', async () => {
TestERC20 = await deploy('TestERC20', { signer: signer1 })
TestERC721 = await deploy('TestERC721', { signer: signer1 })
AssetReceiver = await deploy('AssetReceiver', {
signer: signer1,
args: [signer1.address],
})
})
beforeEach('balance setup', async () => {
await TestERC20.mint(signer1.address, hre.ethers.constants.MaxUint256)
await TestERC721.mint(signer1.address, DEFAULT_TOKEN_ID)
await hre.ethers.provider.send('hardhat_setBalance', [
DEFAULT_RECIPIENT,
'0x0',
])
})
describe('receive', () => {
it('should be able to receive ETH', async () => {
await expect(
signer1.sendTransaction({
to: AssetReceiver.address,
value: DEFAULT_AMOUNT,
})
).to.not.be.reverted
expect(
await hre.ethers.provider.getBalance(AssetReceiver.address)
).to.equal(DEFAULT_AMOUNT)
})
})
describe('withdrawETH(address)', () => {
describe('when called by authorized address', () => {
it('should withdraw all ETH in the contract', async () => {
await signer1.sendTransaction({
to: AssetReceiver.address,
value: DEFAULT_AMOUNT,
})
await expect(AssetReceiver['withdrawETH(address)'](DEFAULT_RECIPIENT))
.to.emit(AssetReceiver, 'WithdrewETH')
.withArgs(signer1.address, DEFAULT_RECIPIENT, DEFAULT_AMOUNT)
expect(
await hre.ethers.provider.getBalance(AssetReceiver.address)
).to.equal(0)
expect(
await hre.ethers.provider.getBalance(DEFAULT_RECIPIENT)
).to.equal(DEFAULT_AMOUNT)
})
})
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
AssetReceiver.connect(signer2)['withdrawETH(address)'](
signer2.address
)
).to.be.revertedWith('UNAUTHORIZED')
})
})
})
describe('withdrawETH(address,uint256)', () => {
describe('when called by authorized address', () => {
it('should withdraw the given amount of ETH', async () => {
await signer1.sendTransaction({
to: AssetReceiver.address,
value: DEFAULT_AMOUNT.mul(2),
})
await expect(
AssetReceiver['withdrawETH(address,uint256)'](
DEFAULT_RECIPIENT,
DEFAULT_AMOUNT
)
)
.to.emit(AssetReceiver, 'WithdrewETH')
.withArgs(signer1.address, DEFAULT_RECIPIENT, DEFAULT_AMOUNT)
expect(
await hre.ethers.provider.getBalance(AssetReceiver.address)
).to.equal(DEFAULT_AMOUNT)
expect(
await hre.ethers.provider.getBalance(DEFAULT_RECIPIENT)
).to.equal(DEFAULT_AMOUNT)
})
})
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
AssetReceiver.connect(signer2)['withdrawETH(address,uint256)'](
DEFAULT_RECIPIENT,
DEFAULT_AMOUNT
)
).to.be.revertedWith('UNAUTHORIZED')
})
})
})
describe('withdrawERC20(address,address)', () => {
describe('when called by authorized address', () => {
it('should withdraw all ERC20 balance held by the contract', async () => {
await TestERC20.transfer(AssetReceiver.address, DEFAULT_AMOUNT)
await expect(
AssetReceiver['withdrawERC20(address,address)'](
TestERC20.address,
DEFAULT_RECIPIENT
)
)
.to.emit(AssetReceiver, 'WithdrewERC20')
.withArgs(
signer1.address,
DEFAULT_RECIPIENT,
TestERC20.address,
DEFAULT_AMOUNT
)
expect(await TestERC20.balanceOf(DEFAULT_RECIPIENT)).to.equal(
DEFAULT_AMOUNT
)
})
})
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
AssetReceiver.connect(signer2)['withdrawERC20(address,address)'](
TestERC20.address,
DEFAULT_RECIPIENT
)
).to.be.revertedWith('UNAUTHORIZED')
})
})
})
describe('withdrawERC20(address,address,uint256)', () => {
describe('when called by authorized address', () => {
it('should withdraw the given ERC20 amount', async () => {
await TestERC20.transfer(AssetReceiver.address, DEFAULT_AMOUNT.mul(2))
await expect(
AssetReceiver['withdrawERC20(address,address,uint256)'](
TestERC20.address,
DEFAULT_RECIPIENT,
DEFAULT_AMOUNT
)
)
.to.emit(AssetReceiver, 'WithdrewERC20')
.withArgs(
signer1.address,
DEFAULT_RECIPIENT,
TestERC20.address,
DEFAULT_AMOUNT
)
expect(await TestERC20.balanceOf(DEFAULT_RECIPIENT)).to.equal(
DEFAULT_AMOUNT
)
})
})
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
AssetReceiver.connect(signer2)[
'withdrawERC20(address,address,uint256)'
](TestERC20.address, DEFAULT_RECIPIENT, DEFAULT_AMOUNT)
).to.be.revertedWith('UNAUTHORIZED')
})
})
})
describe('withdrawERC721', () => {
describe('when called by authorized address', () => {
it('should withdraw the token', async () => {
await TestERC721.transferFrom(
signer1.address,
AssetReceiver.address,
DEFAULT_TOKEN_ID
)
await expect(
AssetReceiver.withdrawERC721(
TestERC721.address,
DEFAULT_RECIPIENT,
DEFAULT_TOKEN_ID
)
)
.to.emit(AssetReceiver, 'WithdrewERC721')
.withArgs(
signer1.address,
DEFAULT_RECIPIENT,
TestERC721.address,
DEFAULT_TOKEN_ID
)
expect(await TestERC721.ownerOf(DEFAULT_TOKEN_ID)).to.equal(
DEFAULT_RECIPIENT
)
})
})
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
AssetReceiver.connect(signer2).withdrawERC721(
TestERC721.address,
DEFAULT_RECIPIENT,
DEFAULT_TOKEN_ID
)
).to.be.revertedWith('UNAUTHORIZED')
})
})
})
})
import hre from 'hardhat'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { Contract } from 'ethers'
import { expect } from '../../setup'
import { decodeSolidityRevert, deploy } from '../../helpers'
describe('Transactor', () => {
let signer1: SignerWithAddress
let signer2: SignerWithAddress
before('signer setup', async () => {
;[signer1, signer2] = await hre.ethers.getSigners()
})
let CallRecorder: Contract
let Reverter: Contract
let Transactor: Contract
beforeEach('deploy contracts', async () => {
CallRecorder = await deploy('CallRecorder')
Reverter = await deploy('Reverter')
Transactor = await deploy('Transactor', {
signer: signer1,
args: [signer1.address],
})
})
describe('CALL', () => {
describe('when called by authorized address', () => {
it('should do a call to the target contract', async () => {
const data = CallRecorder.interface.encodeFunctionData('record')
await Transactor.CALL(CallRecorder.address, data, 0, {
gasLimit: 2_000_000,
})
const call = await CallRecorder.lastCall()
expect(call.data).to.equal(data)
expect(call.sender).to.equal(Transactor.address)
})
it('should be able to call with value', async () => {
const data = CallRecorder.interface.encodeFunctionData('record')
const value = 69
await Transactor.CALL(CallRecorder.address, data, value, {
gasLimit: 2_000_000,
value,
})
const call = await CallRecorder.lastCall()
expect(call.value).to.equal(value)
})
})
describe('when called by not authorized address', () => {
it('should be reverted', async () => {
const data = CallRecorder.interface.encodeFunctionData('record')
await expect(
Transactor.connect(signer2).CALL(CallRecorder.address, data, 0, {
gasLimit: 2_000_000,
})
).to.be.revertedWith('UNAUTHORIZED')
})
})
})
describe('DELEGATECALL', () => {
describe('when called by authorized address', () => {
it('should do a delegatecall to the target contract', async () => {
const data = Reverter.interface.encodeFunctionData('doRevert')
const ret = await Transactor.callStatic.DELEGATECALL(
Reverter.address,
data,
{
gasLimit: 2_000_000,
}
)
expect(ret[0]).to.equal(false)
expect(decodeSolidityRevert(ret[1])).to.deep.equal('Reverter reverted')
})
})
describe('when called by not authorized address', () => {
it('should be reverted', async () => {
const data = Reverter.interface.encodeFunctionData('doRevert')
await expect(
Transactor.connect(signer2).DELEGATECALL(Reverter.address, data, {
gasLimit: 2_000_000,
})
).to.be.revertedWith('UNAUTHORIZED')
})
})
})
})
import hre from 'hardhat'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { Contract } from 'ethers'
import { toRpcHexString } from '@eth-optimism/core-utils'
import { expect } from '../../../setup'
import { deploy } from '../../../helpers'
describe('Drippie', () => {
const DEFAULT_DRIP_NAME = 'drippity drip drip'
const DEFAULT_DRIP_CONFIG = {
interval: hre.ethers.BigNumber.from(100),
dripcheck: '', // Gets added in setup
checkparams: '0x',
actions: [
{
target: '0x' + '11'.repeat(20),
data: '0x',
value: hre.ethers.BigNumber.from(1),
},
],
}
let signer1: SignerWithAddress
let signer2: SignerWithAddress
before('signer setup', async () => {
;[signer1, signer2] = await hre.ethers.getSigners()
})
before('deploy default dripcheck', async () => {
DEFAULT_DRIP_CONFIG.dripcheck = (await deploy('CheckTrue')).address
})
let SimpleStorage: Contract
let Drippie: Contract
beforeEach('deploy contracts', async () => {
SimpleStorage = await deploy('SimpleStorage')
Drippie = await deploy('Drippie', {
signer: signer1,
args: [signer1.address],
})
})
beforeEach('balance setup', async () => {
await hre.ethers.provider.send('hardhat_setBalance', [
Drippie.address,
toRpcHexString(DEFAULT_DRIP_CONFIG.actions[0].value.mul(100000)),
])
await hre.ethers.provider.send('hardhat_setBalance', [
DEFAULT_DRIP_CONFIG.actions[0].target,
'0x0',
])
})
describe('create', () => {
describe('when called by authorized address', () => {
it('should create a drip with the given name', async () => {
await expect(
Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
).to.emit(Drippie, 'DripCreated')
const drip = await Drippie.drips(DEFAULT_DRIP_NAME)
expect(drip.status).to.equal(1) // PAUSED
expect(drip.last).to.deep.equal(hre.ethers.BigNumber.from(0))
expect(drip.config.interval).to.deep.equal(DEFAULT_DRIP_CONFIG.interval)
expect(drip.config.dripcheck).to.deep.equal(
DEFAULT_DRIP_CONFIG.dripcheck
)
expect(drip.config.checkparams).to.deep.equal(
DEFAULT_DRIP_CONFIG.checkparams
)
expect(drip.config.actions[0][0]).to.deep.equal(
DEFAULT_DRIP_CONFIG.actions[0].target
)
expect(drip.config.actions[0][1]).to.deep.equal(
DEFAULT_DRIP_CONFIG.actions[0].data
)
expect(drip.config.actions[0][2]).to.deep.equal(
DEFAULT_DRIP_CONFIG.actions[0].value
)
})
it('should not be able to create the same drip twice', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await expect(
Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
).to.be.revertedWith('Drippie: drip with that name already exists')
})
})
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
Drippie.connect(signer2).create(
DEFAULT_DRIP_NAME,
DEFAULT_DRIP_CONFIG
)
).to.be.revertedWith('UNAUTHORIZED')
})
})
})
describe('status', () => {
describe('when called by authorized address', () => {
it('should allow setting between PAUSED and ACTIVE', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
expect((await Drippie.drips(DEFAULT_DRIP_NAME)).status).to.equal(1) // PAUSED
await Drippie.status(DEFAULT_DRIP_NAME, 2) // ACTIVE
expect((await Drippie.drips(DEFAULT_DRIP_NAME)).status).to.equal(2) // ACTIVE
await Drippie.status(DEFAULT_DRIP_NAME, 1) // PAUSED
expect((await Drippie.drips(DEFAULT_DRIP_NAME)).status).to.equal(1) // PAUSED
})
it('should not allow setting status to NONE', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await expect(Drippie.status(DEFAULT_DRIP_NAME, 0)).to.be.revertedWith(
'Drippie: drip status can never be set back to NONE after creation'
)
})
it('should not allow setting status to same status as before', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await expect(Drippie.status(DEFAULT_DRIP_NAME, 1)).to.be.revertedWith(
'Drippie: cannot set drip status to the same status as its current status'
)
})
it('should allow setting status to ARCHIVED if PAUSED', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await Drippie.status(DEFAULT_DRIP_NAME, 3) // ARCHIVED
expect((await Drippie.drips(DEFAULT_DRIP_NAME)).status).to.equal(3) // ARCHIVED
})
it('should not allow setting status to ARCHIVED if ACTIVE', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await Drippie.status(DEFAULT_DRIP_NAME, 2) // ACTIVE
await expect(Drippie.status(DEFAULT_DRIP_NAME, 3)).to.be.revertedWith(
'Drippie: drip must first be paused before being archived'
)
})
it('should not allow setting status to PAUSED if ARCHIVED', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await Drippie.status(DEFAULT_DRIP_NAME, 3) // ARCHIVED
await expect(Drippie.status(DEFAULT_DRIP_NAME, 2)).to.be.revertedWith(
'Drippie: drip with that name has been archived'
)
})
it('should not allow setting status to ACTIVE if ARCHIVED', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await Drippie.status(DEFAULT_DRIP_NAME, 3) // ARCHIVED
await expect(Drippie.status(DEFAULT_DRIP_NAME, 1)).to.be.revertedWith(
'Drippie: drip with that name has been archived'
)
})
it('should revert if the drip does not exist yet', async () => {
await expect(Drippie.status(DEFAULT_DRIP_NAME, 1)).to.be.revertedWith(
'Drippie: drip with that name does not exist'
)
})
})
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
Drippie.connect(signer2).status(DEFAULT_DRIP_NAME, 1)
).to.be.revertedWith('UNAUTHORIZED')
})
})
})
describe('drip', () => {
it('should drip the amount', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await Drippie.status(DEFAULT_DRIP_NAME, 2) // ACTIVE
await expect(Drippie.drip(DEFAULT_DRIP_NAME)).to.emit(
Drippie,
'DripExecuted'
)
expect(
await signer1.provider.getBalance(DEFAULT_DRIP_CONFIG.actions[0].target)
).to.equal(DEFAULT_DRIP_CONFIG.actions[0].value)
})
it('should be able to trigger one function', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, {
...DEFAULT_DRIP_CONFIG,
actions: [
{
target: SimpleStorage.address,
data: SimpleStorage.interface.encodeFunctionData('set', [
'0x' + '33'.repeat(32),
'0x' + '44'.repeat(32),
]),
value: hre.ethers.BigNumber.from(0),
},
],
})
await Drippie.status(DEFAULT_DRIP_NAME, 2) // ACTIVE
await Drippie.drip(DEFAULT_DRIP_NAME)
expect(await SimpleStorage.get('0x' + '33'.repeat(32))).to.equal(
'0x' + '44'.repeat(32)
)
})
it('should be able to trigger two functions', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, {
...DEFAULT_DRIP_CONFIG,
actions: [
{
target: SimpleStorage.address,
data: SimpleStorage.interface.encodeFunctionData('set', [
'0x' + '33'.repeat(32),
'0x' + '44'.repeat(32),
]),
value: hre.ethers.BigNumber.from(0),
},
{
target: SimpleStorage.address,
data: SimpleStorage.interface.encodeFunctionData('set', [
'0x' + '44'.repeat(32),
'0x' + '55'.repeat(32),
]),
value: hre.ethers.BigNumber.from(0),
},
],
})
await Drippie.status(DEFAULT_DRIP_NAME, 2) // ACTIVE
await Drippie.drip(DEFAULT_DRIP_NAME)
expect(await SimpleStorage.get('0x' + '33'.repeat(32))).to.equal(
'0x' + '44'.repeat(32)
)
expect(await SimpleStorage.get('0x' + '44'.repeat(32))).to.equal(
'0x' + '55'.repeat(32)
)
})
it('should revert if dripping twice in one interval', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await Drippie.status(DEFAULT_DRIP_NAME, 2) // ACTIVE
await Drippie.drip(DEFAULT_DRIP_NAME)
await expect(Drippie.drip(DEFAULT_DRIP_NAME)).to.be.revertedWith(
'Drippie: drip interval has not elapsed'
)
await hre.ethers.provider.send('evm_increaseTime', [
DEFAULT_DRIP_CONFIG.interval.add(1).toHexString(),
])
await expect(Drippie.drip(DEFAULT_DRIP_NAME)).to.not.be.reverted
})
it('should revert when the drip does not exist', async () => {
await expect(Drippie.drip(DEFAULT_DRIP_NAME)).to.be.revertedWith(
'Drippie: selected drip does not exist or is not currently active'
)
})
it('should revert when the drip is not active', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await expect(Drippie.drip(DEFAULT_DRIP_NAME)).to.be.revertedWith(
'Drippie: selected drip does not exist or is not currently active'
)
})
})
})
import hre from 'hardhat'
import { Contract } from 'ethers'
import { toRpcHexString } from '@eth-optimism/core-utils'
import { expect } from '../../../../setup'
import { deploy } from '../../../../helpers'
import { encodeDripCheckParams } from '../../../../../src'
describe('CheckBalanceHigh', () => {
const RECIPIENT = '0x' + '11'.repeat(20)
const THRESHOLD = 100
let CheckBalanceHigh: Contract
before(async () => {
CheckBalanceHigh = await deploy('CheckBalanceHigh')
})
describe('check', () => {
it('should return true when balance is above threshold', async () => {
await hre.ethers.provider.send('hardhat_setBalance', [
RECIPIENT,
toRpcHexString(THRESHOLD + 1),
])
expect(
await CheckBalanceHigh.check(
encodeDripCheckParams(CheckBalanceHigh.interface, {
target: RECIPIENT,
threshold: THRESHOLD,
})
)
).to.equal(true)
})
it('should return false when balance is below threshold', async () => {
await hre.ethers.provider.send('hardhat_setBalance', [
RECIPIENT,
toRpcHexString(THRESHOLD - 1),
])
expect(
await CheckBalanceHigh.check(
encodeDripCheckParams(CheckBalanceHigh.interface, {
target: RECIPIENT,
threshold: THRESHOLD,
})
)
).to.equal(false)
})
})
})
import hre from 'hardhat'
import { Contract } from 'ethers'
import { toRpcHexString } from '@eth-optimism/core-utils'
import { expect } from '../../../../setup'
import { deploy } from '../../../../helpers'
import { encodeDripCheckParams } from '../../../../../src'
describe('CheckBalanceLow', () => {
const RECIPIENT = '0x' + '11'.repeat(20)
const THRESHOLD = 100
let CheckBalanceLow: Contract
before(async () => {
CheckBalanceLow = await deploy('CheckBalanceLow')
})
describe('check', () => {
it('should return true when balance is below threshold', async () => {
await hre.ethers.provider.send('hardhat_setBalance', [
RECIPIENT,
toRpcHexString(THRESHOLD - 1),
])
expect(
await CheckBalanceLow.check(
encodeDripCheckParams(CheckBalanceLow.interface, {
target: RECIPIENT,
threshold: THRESHOLD,
})
)
).to.equal(true)
})
it('should return false when balance is above threshold', async () => {
await hre.ethers.provider.send('hardhat_setBalance', [
RECIPIENT,
toRpcHexString(THRESHOLD + 1),
])
expect(
await CheckBalanceLow.check(
encodeDripCheckParams(CheckBalanceLow.interface, {
target: RECIPIENT,
threshold: THRESHOLD,
})
)
).to.equal(false)
})
})
})
import { Contract } from 'ethers'
import { smock, FakeContract } from '@defi-wonderland/smock'
import { expect } from '../../../../setup'
import { deploy } from '../../../../helpers'
import { encodeDripCheckParams } from '../../../../../src'
describe('CheckGelatoLow', () => {
const RECIPIENT = '0x' + '11'.repeat(20)
const THRESHOLD = 100
let CheckGelatoLow: Contract
let FakeGelatoTresury: FakeContract<Contract>
before(async () => {
CheckGelatoLow = await deploy('CheckGelatoLow')
FakeGelatoTresury = await smock.fake('IGelatoTreasury')
})
describe('check', () => {
it('should return true when balance is below threshold', async () => {
FakeGelatoTresury.userTokenBalance.returns(THRESHOLD - 1)
expect(
await CheckGelatoLow.check(
encodeDripCheckParams(CheckGelatoLow.interface, {
treasury: FakeGelatoTresury.address,
threshold: THRESHOLD,
recipient: RECIPIENT,
})
)
).to.equal(true)
})
it('should return false when balance is above threshold', async () => {
FakeGelatoTresury.userTokenBalance.returns(THRESHOLD + 1)
expect(
await CheckGelatoLow.check(
encodeDripCheckParams(CheckGelatoLow.interface, {
treasury: FakeGelatoTresury.address,
threshold: THRESHOLD,
recipient: RECIPIENT,
})
)
).to.equal(false)
})
})
})
import { Contract } from 'ethers'
import { expect } from '../../../../setup'
import { deploy } from '../../../../helpers'
describe('CheckTrue', () => {
let CheckTrue: Contract
before(async () => {
CheckTrue = await deploy('CheckTrue')
})
describe('check', () => {
it('should return true', async () => {
expect(await CheckTrue.check('0x')).to.equal(true)
})
})
})
export const NON_NULL_BYTES32 = '0x' + '11'.repeat(32)
export const NON_ZERO_ADDRESS = '0x' + '11'.repeat(20)
import hre from 'hardhat'
export const deploy = async (
name: string,
opts?: {
args?: any[]
signer?: any
}
) => {
const factory = await hre.ethers.getContractFactory(name, opts?.signer)
return factory.deploy(...(opts?.args || []))
}
export * from './deploy'
export * from './solidity'
export * from './constants'
import { ethers } from 'ethers'
export const decodeSolidityRevert = (revert: string) => {
const iface = new ethers.utils.Interface([
{
inputs: [
{
internalType: 'string',
name: 'message',
type: 'string',
},
],
name: 'Error',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
])
return iface.decodeFunctionData('Error', revert)[0]
}
/* External Imports */
import chai = require('chai')
import Mocha from 'mocha'
import { solidity } from 'ethereum-waffle'
chai.use(solidity)
const should = chai.should()
const expect = chai.expect
export { should, expect, Mocha }
...@@ -474,6 +474,7 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL ...@@ -474,6 +474,7 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL
"error forwarding RPC batch", "error forwarding RPC batch",
"batch_size", len(elems), "batch_size", len(elems),
"backend_group", group, "backend_group", group,
"req_id", GetReqID(ctx),
"err", err, "err", err,
) )
res = nil res = nil
...@@ -631,7 +632,7 @@ func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error { ...@@ -631,7 +632,7 @@ func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error {
return ErrInvalidParams(err.Error()) return ErrInvalidParams(err.Error())
} }
ok, err := s.senderLim.Take(ctx, msg.From().Hex()) ok, err := s.senderLim.Take(ctx, fmt.Sprintf("%s:%d", msg.From().Hex(), tx.Nonce()))
if err != nil { if err != nil {
log.Error("error taking from sender limiter", "err", err, "req_id", GetReqID(ctx)) log.Error("error taking from sender limiter", "err", err, "req_id", GetReqID(ctx))
return ErrInternal return ErrInternal
......
...@@ -27,9 +27,10 @@ Our aim is to design a protocol specification that is: ...@@ -27,9 +27,10 @@ Our aim is to design a protocol specification that is:
- **Fast:** When users send transactions, they get reliable confirmations with low-latency. - **Fast:** When users send transactions, they get reliable confirmations with low-latency.
For example when swapping on Uniswap you should see that your transaction succeed in less than 2 For example when swapping on Uniswap you should see that your transaction succeed in less than 2
seconds. seconds.
- **Scalable:** It should be possible to handle an enormous number of transactions per second which - **Scalable:** It should be possible to handle an enormous number of transactions
will enable us to charge low fees. V1.0 will enable us to scale up to and even past the gas limit per second which will enable the system to charge low fees.
on L1. Later iterations will scale much further. V1.0 will enable Optimism to scale up to and even past the gas limit on L1.
Later iterations should scale much further.
- **Modular:** Our designs will use modularity to reduce complexity and enable parallel - **Modular:** Our designs will use modularity to reduce complexity and enable parallel
contributions. Coming up with good conceptual frameworks & composable atoms of software enables us contributions. Coming up with good conceptual frameworks & composable atoms of software enables us
to build extremely complex software even when any one person cannot hold that much in their brain. to build extremely complex software even when any one person cannot hold that much in their brain.
...@@ -41,8 +42,9 @@ Our aim is to design a protocol specification that is: ...@@ -41,8 +42,9 @@ Our aim is to design a protocol specification that is:
our software to avoid creating a system no one wants to use. our software to avoid creating a system no one wants to use.
- **Clear and Readable:** The specs we write are written to be read. So tight feedback loop with the - **Clear and Readable:** The specs we write are written to be read. So tight feedback loop with the
systems team consuming the spec is also key! systems team consuming the spec is also key!
- **Secure:** This is self-evident. We cannot lose money and in a system where even a bit of - **Secure:** This is self-evident.
downtime can result in loss of funds this means everything we build must be incredibly secure. User’s assets are at stake. Every component of the system must be incredibly secure.
- **Decentralizable:** Everything we build must have a clear path towards decentralization. Today - **Decentralizable:** Optimism must be designed to avail itself of the security and
Optimism relies on OptimismPBC to function, but eventually it will be managed by a DAO and even in censorship-resistant guarantees achieved by a decentralized system.
that decentralized future our system must thrive. Currently centralized components of the system should have a clear path towards decentralization.
Already decentralized components of the system should be protected and preserved.
...@@ -48,8 +48,10 @@ Deposited transactions MUST never be consumed from the transaction pool. ...@@ -48,8 +48,10 @@ Deposited transactions MUST never be consumed from the transaction pool.
## Engine API ## Engine API
<!--
*Note: the [Engine API][l1-api-spec] is in alpha, `v1.0.0-alpha.5`. *Note: the [Engine API][l1-api-spec] is in alpha, `v1.0.0-alpha.5`.
There may be subtle tweaks, beta starts in a few weeks* There may be subtle tweaks, beta starts in a few weeks*
-->
### `engine_forkchoiceUpdatedV1` ### `engine_forkchoiceUpdatedV1`
......
...@@ -91,9 +91,11 @@ At the heart of the network are users (us!). Users can: ...@@ -91,9 +91,11 @@ At the heart of the network are users (us!). Users can:
### Sequencers ### Sequencers
The sequencer is the primary block producer. There may be one sequencer **or** many using a consensus protocol. For The sequencer is the primary block producer.
1.0.0, there is just one sequencer. In general, specifications may use "the sequencer" to be a stand-in term for the There may be one sequencer **or** many using a consensus protocol.
consensus protocol operated by multiple sequencers. For 1.0.0, there is just one sequencer (currently operated under the oversight of the Optimism Foundation).
In general, specifications may use "the sequencer" to be a stand-in term
for the consensus protocol operated by multiple sequencers.
The sequencer: The sequencer:
...@@ -125,21 +127,21 @@ provide context when diving into any particular component specification. ...@@ -125,21 +127,21 @@ provide context when diving into any particular component specification.
### Depositing and Sending Transactions ### Depositing and Sending Transactions
Users will often begin their L2 journey by depositing ETH from L1. Once they have ETH to pay fees, they'll start Users will often begin their L2 journey by depositing ETH from L1.
sending transactions on L2. The following diagram demonstrates this interaction and all key Optimism Once they have ETH to pay fees, they'll start sending transactions on L2.
components which are utilized: The following diagram demonstrates this interaction and all key Optimism components which are or should be utilized:
![Diagram of Depositing and Sending Transactions](./assets/sequencer-handling-deposits-and-transactions.svg) ![Diagram of Depositing and Sending Transactions](./assets/sequencer-handling-deposits-and-transactions.svg)
Links to components mentioned in this diagram: Links to components mentioned in this diagram:
- Batch Inbox (WIP) <!-- - Batch Inbox (WIP) -->
- [Rollup Node](./rollup-node.md) - [Rollup Node](./rollup-node.md)
- [Execution Engine](./exec-engine.md) - [Execution Engine](./exec-engine.md)
- Sequencer Batch Submitter (WIP) <!-- - Sequencer Batch Submitter (WIP) -->
- [L2 Output Oracle](./proposals.md#l2-output-oracle-smart-contract) - [L2 Output Oracle](./proposals.md#l2-output-oracle-smart-contract)
- [L2 Output Submitter](./proposals.md#proposing-l2-output-commitments) - [L2 Output Submitter](./proposals.md#proposing-l2-output-commitments)
- Fault Proof VM (WIP) <!-- - Fault Proof VM (WIP) -->
### Withdrawing ### Withdrawing
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
## Overview ## Overview
[Predeployed smart contracts](./glossary.md#predeployed-contract-predeploy] exist on Optimism [Predeployed smart contracts](./glossary.md#predeployed-contract-predeploy) exist on Optimism
at predetermined addresses in the genesis state. They are similar to precompiles but instead run at predetermined addresses in the genesis state. They are similar to precompiles but instead run
directly in the EVM instead of running native code outside of the EVM. directly in the EVM instead of running native code outside of the EVM.
...@@ -108,12 +108,11 @@ permissionlessly removed from the L2 supply by calling the `burn()` function. ...@@ -108,12 +108,11 @@ permissionlessly removed from the L2 supply by calling the `burn()` function.
Address: `0x4200000000000000000000000000000000000002` Address: `0x4200000000000000000000000000000000000002`
The `DeployerWhitelist` is a predeploy that was used to provide additional The `DeployerWhitelist` is a predeploy that was used to provide additional safety
safety during the initial phases of Optimism. It is owned by the during the initial phases of Optimism.
Optimism foundation and defines the accounts that are allowed to deploy contracts to the It previously defined the accounts that are allowed to deploy contracts to the network.
network.
Arbitrary contract deployment has been enabled and it is not possible to turn Arbitrary contract deployment was subsequently enabled and it is not possible to turn
off. In the legacy system, this contract was hooked into `CREATE` and off. In the legacy system, this contract was hooked into `CREATE` and
`CREATE2` to ensure that the deployer was allowlisted. `CREATE2` to ensure that the deployer was allowlisted.
...@@ -263,9 +262,9 @@ have the ability to upgrade any of the other predeploy contracts. ...@@ -263,9 +262,9 @@ have the ability to upgrade any of the other predeploy contracts.
Address: `0x4200000000000000000000000000000000000011` Address: `0x4200000000000000000000000000000000000011`
The `SequencerFeeVault` accumulates any transaction tips and is the value of The `SequencerFeeVault` accumulates any transaction priority fee and is the value of
`block.coinbase`. When enough fees accumulate in this account, they can be `block.coinbase`.
permissionlessly withdrawn to an immutable L1 address. When enough fees accumulate in this account, they can be withdrawn to an immutable L1 address.
To change the L1 address that fees are withdrawn to, the contract must be To change the L1 address that fees are withdrawn to, the contract must be
upgraded by changing its proxy's implementation key. upgraded by changing its proxy's implementation key.
...@@ -300,7 +299,7 @@ Address: `0x4200000000000000000000000000000000000019` ...@@ -300,7 +299,7 @@ Address: `0x4200000000000000000000000000000000000019`
The `BaseFeeVault` predeploy receives the basefees on L2. The basefee is not The `BaseFeeVault` predeploy receives the basefees on L2. The basefee is not
burnt on L2 like it is on L1. Once the contract has received a certain amount burnt on L2 like it is on L1. Once the contract has received a certain amount
of fees, the ETH can be permissionlessly withdrawn to an immutable address on of fees, the ETH can be withdrawn to an immutable address on
L1. L1.
## L1FeeVault ## L1FeeVault
...@@ -311,4 +310,4 @@ Address: `0x420000000000000000000000000000000000001a` ...@@ -311,4 +310,4 @@ Address: `0x420000000000000000000000000000000000001a`
The `L1FeeVault` predeploy receives the L1 portion of the transaction fees. The `L1FeeVault` predeploy receives the L1 portion of the transaction fees.
Once the contract has received a certain amount of fees, the ETH can be Once the contract has received a certain amount of fees, the ETH can be
permissionlessly withdrawn to an immutable address on L1. withdrawn to an immutable address on L1.
...@@ -26,7 +26,8 @@ proving any piece of data captured by the outputs. ...@@ -26,7 +26,8 @@ proving any piece of data captured by the outputs.
Proposers submit the output roots to L1 and can be contested with a fault proof, Proposers submit the output roots to L1 and can be contested with a fault proof,
with a bond at stake if the proof is wrong. with a bond at stake if the proof is wrong.
_Note_: Although fault proof construction and verification [is implemented in Cannon][cannon], _Note_: Fault proofs on Optimism are not fully specified at this time. Although fault proof
construction and verification [is implemented in Cannon][cannon],
the fault proof game specification and integration of a output-root challenger into the [rollup-node][g-rollup-node] the fault proof game specification and integration of a output-root challenger into the [rollup-node][g-rollup-node]
are part of later specification milestones. are part of later specification milestones.
...@@ -50,7 +51,7 @@ The proposer may also delete multiple output roots by calling the `deleteL2Outpu ...@@ -50,7 +51,7 @@ The proposer may also delete multiple output roots by calling the `deleteL2Outpu
index of the first output to delete, this will also delete all subsequent outputs. index of the first output to delete, this will also delete all subsequent outputs.
> **Note regarding future work:** In the initial version of the system, the proposer will be the same entity as the > **Note regarding future work:** In the initial version of the system, the proposer will be the same entity as the
> sequencer, which is a trusted role. In the future proposers will need to submit a bond in order to post L2 output > sequencer, which is a trusted role. In the future proposers may need to submit a bond in order to post L2 output
> roots, and some or all of this bond may be taken in the event of a faulty proposal. > roots, and some or all of this bond may be taken in the event of a faulty proposal.
## L2 Output Commitment Construction ## L2 Output Commitment Construction
......
...@@ -55,8 +55,8 @@ For a complete specification of the L2 block derivation, refer to the [L2 block ...@@ -55,8 +55,8 @@ For a complete specification of the L2 block derivation, refer to the [L2 block
## L2 Output RPC method ## L2 Output RPC method
The Rollup node has its own RPC method, `optimism_outputAtBlock` which returns the The Rollup node has its own RPC method, `optimism_outputAtBlock` which returns a 32
a 32 byte hash corresponding to the [L2 output root](./proposals.md#l2-output-commitment-construction). byte hash corresponding to the [L2 output root](./proposals.md#l2-output-commitment-construction).
[SSZ]: https://github.com/ethereum/consensus-specs/blob/dev/ssz/simple-serialize.md [SSZ]: https://github.com/ethereum/consensus-specs/blob/dev/ssz/simple-serialize.md
......
...@@ -55,7 +55,7 @@ We first describe the end to end flow of initiating and finalizing a withdrawal: ...@@ -55,7 +55,7 @@ We first describe the end to end flow of initiating and finalizing a withdrawal:
### On L2 ### On L2
An L2 account sends a withdrawal message (and possibly also ETH) to the `L2ToL1MessagePasser` predeploy contract. An L2 account sends a withdrawal message (and possibly also ETH) to the `L2ToL1MessagePasser` predeploy contract.
This is a very simple contract that stores the a hash of the withdrawal data. This is a very simple contract that stores the hash of the withdrawal data.
### On L1 ### On L1
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment