Commit d4ebcd2b authored by smartcontracts's avatar smartcontracts Committed by GitHub

feat: introduce op-dripper (#13788)

parent e8969704
......@@ -23,9 +23,6 @@
[submodule "packages/contracts-bedrock/lib/lib-keccak"]
path = packages/contracts-bedrock/lib/lib-keccak
url = https://github.com/ethereum-optimism/lib-keccak
[submodule "packages/contracts-bedrock/lib/automate"]
path = packages/contracts-bedrock/lib/automate
url = https://github.com/gelatodigital/automate
[submodule "packages/contracts-bedrock/lib/openzeppelin-contracts-v5"]
path = packages/contracts-bedrock/lib/openzeppelin-contracts-v5
url = https://github.com/OpenZeppelin/openzeppelin-contracts
......
This diff is collapsed.
package main
import (
"context"
"os"
"github.com/ethereum-optimism/optimism/op-service/ctxinterrupt"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-dripper/dripper"
"github.com/ethereum-optimism/optimism/op-dripper/flags"
"github.com/ethereum-optimism/optimism/op-dripper/metrics"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/metrics/doc"
"github.com/ethereum/go-ethereum/log"
)
var (
Version = "v0.0.0"
GitCommit = ""
GitDate = ""
)
func main() {
oplog.SetupDefaults()
app := cli.NewApp()
app.Flags = cliapp.ProtectFlags(flags.Flags)
app.Version = opservice.FormatVersion(Version, GitCommit, GitDate, "")
app.Name = "op-dripper"
app.Usage = "Drippie Executor"
app.Description = "Service for executing Drippie drips"
app.Action = cliapp.LifecycleCmd(dripper.Main(Version))
app.Commands = []*cli.Command{
{
Name: "doc",
Subcommands: doc.NewSubcommands(metrics.NewMetrics("default")),
},
}
ctx := ctxinterrupt.WithSignalWaiterMain(context.Background())
err := app.RunContext(ctx, os.Args)
if err != nil {
log.Crit("Application failed", "message", err)
}
}
package dripper
import (
"errors"
"time"
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-dripper/flags"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
type CLIConfig struct {
L1EthRpc string
DrippieAddress string
PollInterval time.Duration
TxMgrConfig txmgr.CLIConfig
RPCConfig oprpc.CLIConfig
LogConfig oplog.CLIConfig
MetricsConfig opmetrics.CLIConfig
PprofConfig oppprof.CLIConfig
}
func (c *CLIConfig) Check() error {
if err := c.RPCConfig.Check(); err != nil {
return err
}
if err := c.MetricsConfig.Check(); err != nil {
return err
}
if err := c.PprofConfig.Check(); err != nil {
return err
}
if c.DrippieAddress == "" {
return errors.New("drippie address is required")
}
return nil
}
func NewConfig(ctx *cli.Context) *CLIConfig {
return &CLIConfig{
// Required Flags
L1EthRpc: ctx.String(flags.L1EthRpcFlag.Name),
DrippieAddress: ctx.String(flags.DrippieAddressFlag.Name),
PollInterval: ctx.Duration(flags.PollIntervalFlag.Name),
TxMgrConfig: txmgr.ReadCLIConfig(ctx),
// Optional Flags
RPCConfig: oprpc.ReadCLIConfig(ctx),
LogConfig: oplog.ReadCLIConfig(ctx),
MetricsConfig: opmetrics.ReadCLIConfig(ctx),
PprofConfig: oppprof.ReadCLIConfig(ctx),
}
}
package dripper
import (
"context"
"fmt"
"github.com/urfave/cli/v2"
"github.com/ethereum-optimism/optimism/op-dripper/flags"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
)
func Main(version string) cliapp.LifecycleAction {
return func(cliCtx *cli.Context, _ context.CancelCauseFunc) (cliapp.Lifecycle, error) {
if err := flags.CheckRequired(cliCtx); err != nil {
return nil, err
}
cfg := NewConfig(cliCtx)
if err := cfg.Check(); err != nil {
return nil, fmt.Errorf("invalid CLI flags: %w", err)
}
l := oplog.NewLogger(oplog.AppOut(cliCtx), cfg.LogConfig)
oplog.SetGlobalLogHandler(l.Handler())
opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, l)
l.Info("initializing executor")
return DripExecutorServiceFromCLIConfig(cliCtx.Context, version, cfg, l)
}
}
package dripper
import (
"context"
"errors"
"fmt"
"math/big"
"sync"
"time"
"github.com/ethereum-optimism/optimism/op-dripper/bindings"
"github.com/ethereum-optimism/optimism/op-dripper/metrics"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
type Client interface {
CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
}
type DrippieContract interface {
GetDripCount(*bind.CallOpts) (*big.Int, error)
Created(*bind.CallOpts, *big.Int) (string, error)
Executable(*bind.CallOpts, string) (bool, error)
}
type DriverSetup struct {
Log log.Logger
Metr metrics.Metricer
Cfg DripExecutorConfig
Txmgr txmgr.TxManager
Client Client
}
type DripExecutor struct {
DriverSetup
wg sync.WaitGroup
done chan struct{}
ctx context.Context
cancel context.CancelFunc
mutex sync.Mutex
running bool
drippieContract DrippieContract
drippieABI *abi.ABI
}
func NewDripExecutor(setup DriverSetup) (_ *DripExecutor, err error) {
ctx, cancel := context.WithCancel(context.Background())
// Ensure context does't leak.
defer func() {
if err != nil || recover() != nil {
cancel()
}
}()
if setup.Cfg.DrippieAddr == nil {
return nil, errors.New("drippie address is required")
}
return newDripExecutor(ctx, cancel, setup)
}
func newDripExecutor(ctx context.Context, cancel context.CancelFunc, setup DriverSetup) (*DripExecutor, error) {
drippieContract, err := bindings.NewDrippieCaller(*setup.Cfg.DrippieAddr, setup.Client)
if err != nil {
cancel()
return nil, fmt.Errorf("failed to create drippie at address %s: %w", setup.Cfg.DrippieAddr, err)
}
log.Info("connected to drippie", "address", setup.Cfg.DrippieAddr)
parsed, err := bindings.DrippieMetaData.GetAbi()
if err != nil {
cancel()
return nil, err
}
return &DripExecutor{
DriverSetup: setup,
done: make(chan struct{}),
ctx: ctx,
cancel: cancel,
drippieContract: drippieContract,
drippieABI: parsed,
}, nil
}
func (d *DripExecutor) Start() error {
d.Log.Info("starting executor")
d.mutex.Lock()
defer d.mutex.Unlock()
if d.running {
return errors.New("drip executor is already running")
}
d.running = true
d.wg.Add(1)
go d.loop()
d.Log.Info("started executor")
return nil
}
func (d *DripExecutor) Stop() error {
d.Log.Info("stopping executor")
d.mutex.Lock()
defer d.mutex.Unlock()
if !d.running {
return errors.New("drip executor is not running")
}
d.running = false
d.cancel()
close(d.done)
d.wg.Wait()
d.Log.Info("stopped executor")
return nil
}
func (d *DripExecutor) loop() {
defer d.wg.Done()
defer d.Log.Info("loop returning")
ctx := d.ctx
ticker := time.NewTicker(d.Cfg.PollInterval)
defer ticker.Stop()
//nolint:gosimple // This is an event loop that needs to handle multiple channels
for {
select {
case <-ticker.C:
// Prioritize quit signal
select {
case <-d.done:
return
default:
}
drips, err := d.fetchExecutableDrips(ctx)
if err != nil {
d.Log.Warn("failed to fetch executable drips", "error", err)
continue
}
for _, drip := range drips {
d.executeDrip(ctx, drip)
}
}
}
}
func (d *DripExecutor) fetchExecutableDrips(ctx context.Context) ([]string, error) {
// Get total number of drips
d.Log.Info("getting drip count")
count, err := d.drippieContract.GetDripCount(&bind.CallOpts{Context: ctx})
if err != nil {
return nil, fmt.Errorf("failed to get drip count: %w", err)
}
d.Log.Info("drip count", "count", count)
var executableDrips []string
// Iterate through all drips
for i := int64(0); i < count.Int64(); i++ {
// Get drip name at index
name, err := d.drippieContract.Created(&bind.CallOpts{Context: ctx}, big.NewInt(i))
if err != nil {
d.Log.Error("failed to get drip name", "index", i, "error", err)
continue
}
// Check if drip is executable
// Note: This call may revert if the drip is not executable
executable, err := d.drippieContract.Executable(&bind.CallOpts{Context: ctx}, name)
if err != nil {
// Log the error but continue with next drip
d.Log.Info("drip is not executable", "name", name, "error", err)
continue
}
if executable {
d.Log.Info("drip is executable", "name", name)
executableDrips = append(executableDrips, name)
} else {
d.Log.Info("drip is not executable", "name", name)
}
}
return executableDrips, nil
}
func (d *DripExecutor) executeDrip(ctx context.Context, name string) {
cCtx, cCancel := context.WithTimeout(ctx, 10*time.Minute)
defer cCancel()
if err := d.sendTransaction(cCtx, name); err != nil {
d.Log.Error("failed to send drip execution transaction", "name", name, "error", err)
return
}
d.Metr.RecordDripExecuted(name)
}
func (d *DripExecutor) sendTransaction(ctx context.Context, name string) error {
d.Log.Info("executing drip", "name", name)
data, err := d.executeDripTxData(name)
if err != nil {
return err
}
receipt, err := d.Txmgr.Send(ctx, txmgr.TxCandidate{
TxData: data,
To: d.Cfg.DrippieAddr,
GasLimit: 0,
})
if err != nil {
return err
}
if receipt.Status == types.ReceiptStatusFailed {
d.Log.Error("drip execution failed", "name", name, "tx_hash", receipt.TxHash)
} else {
d.Log.Info("drip executed", "name", name, "tx_hash", receipt.TxHash)
}
return nil
}
func (d *DripExecutor) executeDripTxData(name string) ([]byte, error) {
return executeDripTxData(d.drippieABI, name)
}
func executeDripTxData(abi *abi.ABI, name string) ([]byte, error) {
return abi.Pack(
"drip",
name)
}
package dripper
import (
"context"
"errors"
"fmt"
"io"
"sync/atomic"
"time"
"github.com/ethereum-optimism/optimism/op-dripper/metrics"
opservice "github.com/ethereum-optimism/optimism/op-service"
"github.com/ethereum-optimism/optimism/op-service/cliapp"
"github.com/ethereum-optimism/optimism/op-service/dial"
"github.com/ethereum-optimism/optimism/op-service/httputil"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
var ErrAlreadyStopped = errors.New("already stopped")
type DripExecutorDriver interface {
Start() error
Stop() error
}
type DripExecutorConfig struct {
PollInterval time.Duration
NetworkTimeout time.Duration
DrippieAddr *common.Address
}
type DripExecutorService struct {
Log log.Logger
Metrics metrics.Metricer
DripExecutorConfig
TxManager txmgr.TxManager
Client *ethclient.Client
driver *DripExecutor
Version string
pprofService *oppprof.Service
metricsSrv *httputil.HTTPServer
rpcServer *oprpc.Server
balanceMetricer io.Closer
stopped atomic.Bool
}
func DripExecutorServiceFromCLIConfig(ctx context.Context, version string, cfg *CLIConfig, log log.Logger) (*DripExecutorService, error) {
var ds DripExecutorService
if err := ds.initFromCLIConfig(ctx, version, cfg, log); err != nil {
return nil, errors.Join(err, ds.Stop(ctx))
}
return &ds, nil
}
func (ds *DripExecutorService) initFromCLIConfig(ctx context.Context, version string, cfg *CLIConfig, log log.Logger) error {
ds.Version = version
ds.Log = log
ds.initMetrics(cfg)
ds.PollInterval = cfg.PollInterval
ds.NetworkTimeout = cfg.TxMgrConfig.NetworkTimeout
ds.initDrippieAddress(cfg)
if err := ds.initRPCClients(ctx, cfg); err != nil {
return err
}
if err := ds.initTxManager(cfg); err != nil {
return fmt.Errorf("failed to init tx manager: %w", err)
}
if err := ds.initMetricsServer(cfg); err != nil {
return fmt.Errorf("failed to start metrics server: %w", err)
}
if err := ds.initPProf(cfg); err != nil {
return fmt.Errorf("failed to init pprof server: %w", err)
}
if err := ds.initDriver(); err != nil {
return fmt.Errorf("failed to init driver: %w", err)
}
if err := ds.initRPCServer(cfg); err != nil {
return fmt.Errorf("failed to start rpc server: %w", err)
}
ds.initBalanceMonitor(cfg)
ds.Metrics.RecordInfo(ds.Version)
ds.Metrics.RecordUp()
return nil
}
func (ds *DripExecutorService) initRPCClients(ctx context.Context, cfg *CLIConfig) error {
client, err := dial.DialEthClientWithTimeout(ctx, dial.DefaultDialTimeout, ds.Log, cfg.L1EthRpc)
if err != nil {
return fmt.Errorf("failed to dial rpc: %w", err)
}
ds.Client = client
return nil
}
func (ds *DripExecutorService) initMetrics(cfg *CLIConfig) {
if cfg.MetricsConfig.Enabled {
procName := "default"
ds.Metrics = metrics.NewMetrics(procName)
} else {
ds.Metrics = metrics.NoopMetrics
}
}
func (ds *DripExecutorService) initBalanceMonitor(cfg *CLIConfig) {
if cfg.MetricsConfig.Enabled {
ds.balanceMetricer = ds.Metrics.StartBalanceMetrics(ds.Log, ds.Client, ds.TxManager.From())
}
}
func (ds *DripExecutorService) initTxManager(cfg *CLIConfig) error {
txManager, err := txmgr.NewSimpleTxManager("dripper", ds.Log, ds.Metrics, cfg.TxMgrConfig)
if err != nil {
return err
}
ds.TxManager = txManager
return nil
}
func (ds *DripExecutorService) initPProf(cfg *CLIConfig) error {
ds.pprofService = oppprof.New(
cfg.PprofConfig.ListenEnabled,
cfg.PprofConfig.ListenAddr,
cfg.PprofConfig.ListenPort,
cfg.PprofConfig.ProfileType,
cfg.PprofConfig.ProfileDir,
cfg.PprofConfig.ProfileFilename,
)
if err := ds.pprofService.Start(); err != nil {
return fmt.Errorf("failed to start pprof service: %w", err)
}
return nil
}
func (ds *DripExecutorService) initMetricsServer(cfg *CLIConfig) error {
if !cfg.MetricsConfig.Enabled {
ds.Log.Info("metrics disabled")
return nil
}
m, ok := ds.Metrics.(opmetrics.RegistryMetricer)
if !ok {
return fmt.Errorf("metrics were enabled, but metricer %T does not expose registry for metrics-server", ds.Metrics)
}
ds.Log.Debug("starting metrics server", "addr", cfg.MetricsConfig.ListenAddr, "port", cfg.MetricsConfig.ListenPort)
metricsSrv, err := opmetrics.StartServer(m.Registry(), cfg.MetricsConfig.ListenAddr, cfg.MetricsConfig.ListenPort)
if err != nil {
return fmt.Errorf("failed to start metrics server: %w", err)
}
ds.Log.Info("started metrics server", "addr", metricsSrv.Addr())
ds.metricsSrv = metricsSrv
return nil
}
func (ds *DripExecutorService) initDrippieAddress(cfg *CLIConfig) {
drippieAddress, err := opservice.ParseAddress(cfg.DrippieAddress)
if err != nil {
return
}
ds.DrippieAddr = &drippieAddress
}
func (ds *DripExecutorService) initDriver() error {
driver, err := NewDripExecutor(DriverSetup{
Log: ds.Log,
Metr: ds.Metrics,
Cfg: ds.DripExecutorConfig,
Txmgr: ds.TxManager,
Client: ds.Client,
})
if err != nil {
return err
}
ds.driver = driver
return nil
}
func (ds *DripExecutorService) initRPCServer(cfg *CLIConfig) error {
server := oprpc.NewServer(
cfg.RPCConfig.ListenAddr,
cfg.RPCConfig.ListenPort,
ds.Version,
oprpc.WithLogger(ds.Log),
)
if cfg.RPCConfig.EnableAdmin {
server.AddAPI(ds.TxManager.API())
ds.Log.Info("admin rpc enabled")
}
ds.Log.Info("starting json-rpc server")
if err := server.Start(); err != nil {
return fmt.Errorf("unable to start rpc server: %w", err)
}
ds.rpcServer = server
return nil
}
func (ds *DripExecutorService) Start(ctx context.Context) error {
return ds.driver.Start()
}
func (ds *DripExecutorService) Stopped() bool {
return ds.stopped.Load()
}
func (ds *DripExecutorService) Kill() error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
return ds.Stop(ctx)
}
func (ds *DripExecutorService) Stop(ctx context.Context) error {
if ds.Stopped() {
return ErrAlreadyStopped
}
ds.Log.Info("stopping executor")
var result error
if ds.driver != nil {
if err := ds.driver.Stop(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to stop executor: %w", err))
}
}
if ds.rpcServer != nil {
if err := ds.rpcServer.Stop(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to stop rpc server: %w", err))
}
}
if ds.pprofService != nil {
if err := ds.pprofService.Stop(ctx); err != nil {
result = errors.Join(result, fmt.Errorf("failed to stop pprof server: %w", err))
}
}
if ds.balanceMetricer != nil {
if err := ds.balanceMetricer.Close(); err != nil {
result = errors.Join(result, fmt.Errorf("failed to close balance metricer: %w", err))
}
}
if ds.TxManager != nil {
ds.TxManager.Close()
}
if ds.metricsSrv != nil {
if err := ds.metricsSrv.Stop(ctx); err != nil {
result = errors.Join(result, fmt.Errorf("failed to stop metrics server: %w", err))
}
}
if ds.Client != nil {
ds.Client.Close()
}
if result == nil {
ds.stopped.Store(true)
ds.Log.Info("stopped executor")
}
return result
}
var _ cliapp.Lifecycle = (*DripExecutorService)(nil)
func (ds *DripExecutorService) Driver() DripExecutorDriver {
return ds.driver
}
package flags
import (
"fmt"
"time"
"github.com/urfave/cli/v2"
opservice "github.com/ethereum-optimism/optimism/op-service"
oplog "github.com/ethereum-optimism/optimism/op-service/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
const EnvVarPrefix = "OP_DRIPPER"
func prefixEnvVars(name string) []string {
return opservice.PrefixEnvVar(EnvVarPrefix, name)
}
var (
// Required Flags
L1EthRpcFlag = &cli.StringFlag{
Name: "l1-eth-rpc",
Usage: "The RPC URL for the L1 Ethereum chain to drip",
EnvVars: prefixEnvVars("L1_ETH_RPC"),
Required: true,
}
DrippieAddressFlag = &cli.StringFlag{
Name: "drippie-address",
Usage: "The address of the drippie contract",
EnvVars: prefixEnvVars("DRIPPIE_ADDRESS"),
Required: true,
}
// Optional Flags
PollIntervalFlag = &cli.DurationFlag{
Name: "poll-interval",
Usage: "How frequently to poll L2 for new blocks (legacy L2OO)",
Value: 12 * time.Second,
EnvVars: prefixEnvVars("POLL_INTERVAL"),
}
)
var requiredFlags = []cli.Flag{
L1EthRpcFlag,
DrippieAddressFlag,
}
var optionalFlags = []cli.Flag{
PollIntervalFlag,
}
func init() {
optionalFlags = append(optionalFlags, oprpc.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, opmetrics.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, oppprof.CLIFlags(EnvVarPrefix)...)
optionalFlags = append(optionalFlags, txmgr.CLIFlags(EnvVarPrefix)...)
Flags = append(requiredFlags, optionalFlags...)
}
var Flags []cli.Flag
func CheckRequired(ctx *cli.Context) error {
for _, f := range requiredFlags {
if !ctx.IsSet(f.Names()[0]) {
return fmt.Errorf("flag %s is required", f.Names()[0])
}
}
return nil
}
import '../justfiles/go.just'
# Build ldflags string
_LDFLAGSSTRING := "'" + trim(
"-X main.GitCommit=" + GITCOMMIT + " " + \
"-X main.GitDate=" + GITDATE + " " + \
"-X main.Version=" + VERSION + " " + \
"") + "'"
BINARY := "./bin/op-dripper"
# Build op-dripper binary
op-dripper: (go_build BINARY "./cmd" "-ldflags" _LDFLAGSSTRING)
# Clean build artifacts
clean:
rm -f {{BINARY}}
# Run tests
test: (go_test "./...")
package metrics
import (
"io"
"github.com/prometheus/client_golang/prometheus"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
txmetrics "github.com/ethereum-optimism/optimism/op-service/txmgr/metrics"
)
const Namespace = "op_dripper"
var _ opmetrics.RegistryMetricer = (*Metrics)(nil)
type Metricer interface {
RecordInfo(version string)
RecordUp()
opmetrics.RefMetricer
opmetrics.RPCMetricer
txmetrics.TxMetricer
StartBalanceMetrics(l log.Logger, client *ethclient.Client, account common.Address) io.Closer
RecordDripExecuted(name string)
}
type Metrics struct {
ns string
registry *prometheus.Registry
factory opmetrics.Factory
opmetrics.RefMetrics
opmetrics.RPCMetrics
txmetrics.TxMetrics
info prometheus.GaugeVec
drips prometheus.GaugeVec
up prometheus.Gauge
}
var _ Metricer = (*Metrics)(nil)
func NewMetrics(procName string) *Metrics {
if procName == "" {
procName = "default"
}
ns := Namespace + "_" + procName
registry := opmetrics.NewRegistry()
factory := opmetrics.With(registry)
return &Metrics{
ns: ns,
registry: registry,
factory: factory,
RefMetrics: opmetrics.MakeRefMetrics(ns, factory),
RPCMetrics: opmetrics.MakeRPCMetrics(ns, factory),
TxMetrics: txmetrics.MakeTxMetrics(ns, factory),
info: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns,
Name: "info",
Help: "Information about the dripper",
}, []string{
"version",
}),
drips: *factory.NewGaugeVec(prometheus.GaugeOpts{
Namespace: ns,
Name: "drips",
Help: "Drips executed",
}, []string{
"name",
}),
up: factory.NewGauge(prometheus.GaugeOpts{
Namespace: ns,
Name: "up",
Help: "1 if the op-dripper has finished starting up",
}),
}
}
func (m *Metrics) Registry() *prometheus.Registry {
return m.registry
}
func (m *Metrics) StartBalanceMetrics(l log.Logger, client *ethclient.Client, account common.Address) io.Closer {
return opmetrics.LaunchBalanceMetrics(l, m.registry, m.ns, client, account)
}
func (m *Metrics) RecordInfo(version string) {
m.info.WithLabelValues(version).Set(1)
}
func (m *Metrics) RecordUp() {
prometheus.MustRegister()
m.up.Set(1)
}
func (m *Metrics) RecordDripExecuted(name string) {
m.drips.WithLabelValues(name).Inc()
}
func (m *Metrics) Document() []opmetrics.DocumentedMetric {
return m.factory.Document()
}
package metrics
import (
"io"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics"
txmetrics "github.com/ethereum-optimism/optimism/op-service/txmgr/metrics"
)
type noopMetrics struct {
opmetrics.NoopRefMetrics
txmetrics.NoopTxMetrics
opmetrics.NoopRPCMetrics
}
var NoopMetrics Metricer = new(noopMetrics)
func (*noopMetrics) RecordInfo(version string) {}
func (*noopMetrics) RecordUp() {}
func (*noopMetrics) RecordDripExecuted(name string) {}
func (*noopMetrics) StartBalanceMetrics(log.Logger, *ethclient.Client, common.Address) io.Closer {
return nil
}
......@@ -10,11 +10,6 @@
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
......
......@@ -10,11 +10,6 @@
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
......
......@@ -10,11 +10,6 @@
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
......
......@@ -10,11 +10,6 @@
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
......
......@@ -10,11 +10,6 @@
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
......
......@@ -10,11 +10,6 @@
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
......
......@@ -10,11 +10,6 @@
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
......
......@@ -10,11 +10,6 @@
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
......
......@@ -10,11 +10,6 @@
"faucetAdminDripV1Interval": 86400,
"faucetAdminDripV1Threshold": 100000000000000000,
"faucetAdminDripV1Value": 1000000000000000000,
"faucetGelatoTreasury": "0x644CB00854EDC55FE8CCC9c1967BABb22F08Ad2f",
"faucetGelatoRecipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF",
"faucetGelatoBalanceV1DripInterval": 86400,
"faucetGelatoBalanceV1Value": 1000000000000000000,
"faucetGelatoThreshold": 100000000000000000,
"faucetOnchainAuthModuleAdmin": "0xFe44Ae787A632c45ACea658492dDBebE39f002aC",
"faucetOnchainAuthModuleTtl": 86400,
"faucetOnchainAuthModuleAmount": 1000000000000000000,
......
{
"create2DeploymentSalt": "0.0.7",
"create2DeploymentSalt": "0.0.8",
"gelatoAutomateContract": "0x2A6C106ae13B558BB9E2Ec64Bd2f1f7BEFF3A5E0",
"operationsDrippieOwner": "0xCd438409d5Cac9D2E076Ac7Bd0Bf2377E99BB6e4",
"operationsDrippieOwner": "0xEdc8e780ff786F2DE335FC859349d43392D037a3",
"faucetDrippieOwner": "0x10ab157483dd308f8B38aCF2ad823dfD255F56b5",
"opChainAdminWalletDripValue": 1000000000000000000,
......
{
"drippie": "0xd6F935Bd272BEE05bD64096D82970482EF16D64b",
"gelato": "0x859E31b3848Ec384012EECc72C5c49821008296C",
"__comment": "Addresses of dripcheck contracts to be used in drips",
"dripchecks": [
......@@ -8,10 +7,6 @@
"00__name": "CheckBalanceLow",
"01__address": "0xaF8C77CfeB57620c4D9dCC81df75a1F0Da7064Af"
},
{
"00__name": "CheckGelatoLow",
"01__address": "0x0fF11AfAC4146a0bABf7F9F042a22C8053a54674"
},
{
"00__name": "CheckSecrets",
"01__address": "0x32c1e36E733913388076D7c3055300072814bF2A"
......
{
"drippie": "0xd6F935Bd272BEE05bD64096D82970482EF16D64b",
"gelato": "0x859E31b3848Ec384012EECc72C5c49821008296C",
"__comment": "Addresses of dripcheck contracts to be used in drips",
"dripchecks": [
......@@ -8,10 +7,6 @@
"00__name": "CheckBalanceLow",
"01__address": "0xaF8C77CfeB57620c4D9dCC81df75a1F0Da7064Af"
},
{
"00__name": "CheckGelatoLow",
"01__address": "0x0fF11AfAC4146a0bABf7F9F042a22C8053a54674"
},
{
"00__name": "CheckSecrets",
"01__address": "0x32c1e36E733913388076D7c3055300072814bF2A"
......@@ -62,19 +57,6 @@
"04__value": 1000000000000000000,
"05__interval": 86400,
"06__data": ""
},
{
"00__name": "gelato_v1",
"01__dripcheck": "CheckGelatoLow",
"02__checkparams": {
"00__treasury": "0x7506C12a824d73D9b08564d5Afc22c949434755e",
"01__threshold": 100000000000000000,
"02__recipient": "0x0E9b4649eB0760A4F01646636E032D68cFDe58FF"
},
"03__recipient": "0x7506C12a824d73D9b08564d5Afc22c949434755e",
"04__value": 1000000000000000000,
"05__interval": 86400,
"06__data": "0x0000000000000000000000000e9b4649eb0760a4f01646636e032d68cfde58ff"
}
]
}
{
"drippie": "0xa0fF2a54AdC3fB33c44a141E67d194CF249258cb",
"gelato": "0x2A6C106ae13B558BB9E2Ec64Bd2f1f7BEFF3A5E0",
"__comment": "Addresses of dripcheck contracts to be used in drips",
"dripchecks": [
......@@ -8,10 +7,6 @@
"00__name": "CheckBalanceLow",
"01__address": "0xaF8C77CfeB57620c4D9dCC81df75a1F0Da7064Af"
},
{
"00__name": "CheckGelatoLow",
"01__address": "0x0fF11AfAC4146a0bABf7F9F042a22C8053a54674"
},
{
"00__name": "CheckSecrets",
"01__address": "0x32c1e36E733913388076D7c3055300072814bF2A"
......@@ -62,19 +57,6 @@
"04__value": 100000000000000000000,
"05__interval": 3600,
"06__data": ""
},
{
"00__name": "gelato_v3",
"01__dripcheck": "CheckGelatoLow",
"02__checkparams": {
"00__treasury": "0x7506C12a824d73D9b08564d5Afc22c949434755e",
"01__threshold": 1000000000000000000,
"02__recipient": "0xCd438409d5Cac9D2E076Ac7Bd0Bf2377E99BB6e4"
},
"03__recipient": "0x7506C12a824d73D9b08564d5Afc22c949434755e",
"04__value": 1000000000000000000,
"05__interval": 86400,
"06__data": "0x0000000000000000000000000Cd438409d5Cac9D2E076Ac7Bd0Bf2377E99BB6e4"
}
]
}
{
"drippie": "0x7ECe0FdeA734B9E77dDd04362c312562924857F6",
"__comment": "Addresses of dripcheck contracts to be used in drips",
"dripchecks": [
{
"00__name": "CheckBalanceLow",
"01__address": "0xaF8C77CfeB57620c4D9dCC81df75a1F0Da7064Af"
},
{
"00__name": "CheckSecrets",
"01__address": "0x32c1e36E733913388076D7c3055300072814bF2A"
},
{
"00__name": "CheckTrue",
"01__address": "0xcBCb3896Ddec35d91901768733C5d3738e10509F"
}
],
"__comment": "Prefix is used to namespace drips so that drip management can be handled modularly",
"prefix": "operations",
"__comment": "Object attributes below are prefixed with numbers because of how foundry parses JSON into structs in alphabetical order",
"drips": [
{
"00__name": "test_v2",
"01__dripcheck": "CheckBalanceLow",
"02__checkparams": {
"01__target": "0x68108902De3A5031197a6eB3b74b3b033e8E8e4d",
"02__threshold": 10000000000000000000
},
"03__recipient": "0x68108902De3A5031197a6eB3b74b3b033e8E8e4d",
"04__value": 10000000000000000,
"05__interval": 1,
"06__data": ""
},
{
"00__name": "test_2_v1",
"01__dripcheck": "CheckBalanceLow",
"02__checkparams": {
"01__target": "0x68108902De3A5031197a6eB3b74b3b033e8E8e4d",
"02__threshold": 10000000000000000000
},
"03__recipient": "0x68108902De3A5031197a6eB3b74b3b033e8E8e4d",
"04__value": 1000000000000000,
"05__interval": 20,
"06__data": ""
}
]
}
......@@ -39,7 +39,6 @@ remappings = [
'ds-test/=lib/forge-std/lib/ds-test/src',
'safe-contracts/=lib/safe-contracts/contracts',
'kontrol-cheatcodes/=lib/kontrol-cheatcodes/src',
'gelato/=lib/automate/contracts',
'interfaces/=interfaces'
]
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title IGelatoTreasury
/// @notice Interface for the GelatoTreasury contract.
interface IGelatoTreasury {
function totalDepositedAmount(address _user, address _token) external view returns (uint256);
function totalWithdrawnAmount(address _user, address _token) external view returns (uint256);
}
Subproject commit 0117585fea20ff0cd24fd17bf74a6debaa4d57d2
......@@ -13,7 +13,6 @@ import { ProxyAdmin } from "src/universal/ProxyAdmin.sol";
import { Proxy } from "src/universal/Proxy.sol";
import { Faucet } from "src/periphery/faucet/Faucet.sol";
import { Drippie } from "src/periphery/drippie/Drippie.sol";
import { CheckGelatoLow } from "src/periphery/drippie/dripchecks/CheckGelatoLow.sol";
import { CheckBalanceLow } from "src/periphery/drippie/dripchecks/CheckBalanceLow.sol";
import { CheckTrue } from "src/periphery/drippie/dripchecks/CheckTrue.sol";
import { CheckSecrets } from "src/periphery/drippie/dripchecks/CheckSecrets.sol";
......@@ -35,6 +34,7 @@ contract DeployPeriphery is Script {
/// @notice Sets up the deployment script.
function setUp() public {
vm.allowCheatcodes(address(artifacts));
vm.etch(address(artifacts), vm.getDeployedCode("Artifacts.s.sol:Artifacts"));
artifacts.setUp();
......@@ -50,7 +50,6 @@ contract DeployPeriphery is Script {
if (cfg.deployDripchecks()) {
deployCheckTrue();
deployCheckBalanceLow();
deployCheckGelatoLow();
deployCheckSecrets();
}
......@@ -191,15 +190,6 @@ contract DeployPeriphery is Script {
});
}
/// @notice Deploy CheckGelatoLow contract.
function deployCheckGelatoLow() public broadcast returns (address addr_) {
addr_ = _deployCreate2({
_name: "CheckGelatoLow",
_creationCode: type(CheckGelatoLow).creationCode,
_constructorParams: hex""
});
}
/// @notice Deploy CheckSecrets contract.
function deployCheckSecrets() public broadcast returns (address addr_) {
addr_ = _deployCreate2({
......
......@@ -15,9 +15,6 @@ contract PeripheryDeployConfig is Script {
// General configuration.
string public create2DeploymentSalt;
// Configuration for Gelato.
address public gelatoAutomateContract;
// Configuration for standard operations Drippie contract.
address public operationsDrippieOwner;
......@@ -50,9 +47,6 @@ contract PeripheryDeployConfig is Script {
// General configuration.
create2DeploymentSalt = stdJson.readString(_json, "$.create2DeploymentSalt");
// Configuration for Gelato.
gelatoAutomateContract = stdJson.readAddress(_json, "$.gelatoAutomateContract");
// Configuration for the standard operations Drippie contract.
operationsDrippieOwner = stdJson.readAddress(_json, "$.operationsDrippieOwner");
......
......@@ -5,11 +5,8 @@ import { Script } from "forge-std/Script.sol";
import { console2 as console } from "forge-std/console2.sol";
import { stdJson } from "forge-std/StdJson.sol";
import { IAutomate as IGelato } from "gelato/interfaces/IAutomate.sol";
import { Drippie } from "src/periphery/drippie/Drippie.sol";
import { CheckBalanceLow } from "src/periphery/drippie/dripchecks/CheckBalanceLow.sol";
import { CheckGelatoLow } from "src/periphery/drippie/dripchecks/CheckGelatoLow.sol";
import { CheckSecrets } from "src/periphery/drippie/dripchecks/CheckSecrets.sol";
/// @title DrippieConfig
......@@ -47,9 +44,6 @@ contract DrippieConfig is Script {
/// @notice Drippie contract.
Drippie public drippie;
/// @notice Gelato automation contract.
IGelato public gelato;
/// @notice Prefix for the configuration file.
string public prefix;
......@@ -76,9 +70,6 @@ contract DrippieConfig is Script {
// Load the Drippie contract address.
drippie = Drippie(payable(stdJson.readAddress(_json, "$.drippie")));
// Load the Gelato contract address.
gelato = IGelato(stdJson.readAddress(_json, "$.gelato"));
// Load the prefix.
prefix = stdJson.readString(_json, "$.prefix");
......@@ -120,8 +111,6 @@ contract DrippieConfig is Script {
console.log("DrippieConfig: attempting to decode check parameters for %s", fullname);
if (strcmp(dripcheck, "CheckBalanceLow")) {
abi.decode(checkparams, (CheckBalanceLow.Params));
} else if (strcmp(dripcheck, "CheckGelatoLow")) {
abi.decode(checkparams, (CheckGelatoLow.Params));
} else if (strcmp(dripcheck, "CheckSecrets")) {
abi.decode(checkparams, (CheckSecrets.Params));
} else if (strcmp(dripcheck, "CheckTrue")) {
......
......@@ -6,11 +6,6 @@ import { Script } from "forge-std/Script.sol";
import { LibString } from "@solady/utils/LibString.sol";
import { IAutomate as IGelato } from "gelato/interfaces/IAutomate.sol";
import { LibDataTypes as GelatoDataTypes } from "gelato/libraries/LibDataTypes.sol";
import { LibTaskId as GelatoTaskId } from "gelato/libraries/LibTaskId.sol";
import { GelatoBytes } from "gelato/vendor/gelato/GelatoBytes.sol";
import { Config } from "scripts/libraries/Config.sol";
import { DrippieConfig } from "scripts/periphery/drippie/DrippieConfig.s.sol";
......@@ -20,15 +15,6 @@ import { IDripCheck } from "src/periphery/drippie/IDripCheck.sol";
/// @title ManageDrippie
/// @notice Script for managing drips in the Drippie contract.
contract ManageDrippie is Script {
/// @notice Struct that contains the data for a Gelato task.
struct GelatoTaskData {
address taskCreator;
address execAddress;
bytes execData;
GelatoDataTypes.ModuleData moduleData;
address feeToken;
}
/// @notice Drippie configuration.
DrippieConfig public cfg;
......@@ -68,12 +54,6 @@ contract ManageDrippie is Script {
console.log("ManageDrippie: pausing drip for %s", name);
cfg.drippie().status(name, Drippie.DripStatus.PAUSED);
}
// Cancel the Gelato task if it's active.
if (_isGelatoDripTaskActive(cfg.gelato(), cfg.drippie(), name)) {
console.log("ManageDrippie: pausing Gelato task for %s", name);
_pauseGelatoDripTask(cfg.gelato(), cfg.drippie(), name);
}
}
}
}
......@@ -86,7 +66,6 @@ contract ManageDrippie is Script {
Drippie.DripAction[] memory actions = new Drippie.DripAction[](1);
actions[0] = Drippie.DripAction({ target: payable(drip.recipient), data: drip.data, value: drip.value });
_installDrip({
_gelato: cfg.gelato(),
_drippie: cfg.drippie(),
_name: drip.name,
_config: Drippie.DripConfig({
......@@ -100,126 +79,14 @@ contract ManageDrippie is Script {
}
}
/// @notice Generates the data for a Gelato task that would trigger a drip.
/// @param _drippie The drippie contract.
/// @param _name The name of the drip.
/// @return taskData_ Gelato task data.
function _makeGelatoDripTaskData(
Drippie _drippie,
string memory _name
)
internal
view
returns (GelatoTaskData memory taskData_)
{
// Get the drip interval.
uint256 dripInterval = _drippie.getDripInterval(_name);
// Set up module types.
GelatoDataTypes.Module[] memory modules = new GelatoDataTypes.Module[](2);
modules[0] = GelatoDataTypes.Module.PROXY;
modules[1] = GelatoDataTypes.Module.TRIGGER;
// Interval is in milliseconds, so we should be multiplying by 1000.
// We then want to attempt to trigger the drip 10x per interval, so we divide by 10.
// Total multiplier is then 1000 / 10 = 100.
uint128 interval = uint128(dripInterval) * 100;
// Create arguments for the PROXY and TRIGGER modules.
bytes[] memory args = new bytes[](2);
args[0] = abi.encode(_name);
args[1] = abi.encode(uint128(GelatoDataTypes.TriggerType.TIME), abi.encode(uint128(0), interval));
// Create the task data.
taskData_ = GelatoTaskData({
taskCreator: msg.sender,
execAddress: address(_drippie),
execData: abi.encodeCall(Drippie.drip, (_name)),
moduleData: GelatoDataTypes.ModuleData({ modules: modules, args: args }),
feeToken: address(0)
});
}
/// @notice Starts a gelato drip task.
/// @param _gelato The gelato contract.
/// @param _drippie The drippie contract.
/// @param _name The name of the drip being triggered.
function _startGelatoDripTask(IGelato _gelato, Drippie _drippie, string memory _name) internal {
GelatoTaskData memory taskData = _makeGelatoDripTaskData({ _drippie: _drippie, _name: _name });
_gelato.createTask({
execAddress: taskData.execAddress,
execData: taskData.execData,
moduleData: taskData.moduleData,
feeToken: taskData.feeToken
});
}
/// @notice Determines if a gelato drip task is active or not.
/// @param _gelato The gelato contract.
/// @param _drippie The drippie contract.
/// @param _name The name of the drip being triggered.
/// @return active_ True if the task is active, false otherwise.
function _isGelatoDripTaskActive(
IGelato _gelato,
Drippie _drippie,
string memory _name
)
internal
view
returns (bool active_)
{
GelatoTaskData memory taskData = _makeGelatoDripTaskData({ _drippie: _drippie, _name: _name });
bytes32 taskId = GelatoTaskId.getTaskId({
taskCreator: taskData.taskCreator,
execAddress: taskData.execAddress,
execSelector: GelatoBytes.memorySliceSelector(taskData.execData),
moduleData: taskData.moduleData,
feeToken: taskData.feeToken
});
// Iterate over the task IDs to see if the task is active.
bytes32[] memory taskIds = _gelato.getTaskIdsByUser(taskData.taskCreator);
for (uint256 i = 0; i < taskIds.length; i++) {
if (taskIds[i] == taskId) {
active_ = true;
}
}
}
/// @notice Pauses a gelato drip task.
/// @param _gelato The gelato contract.
/// @param _drippie The drippie contract.
/// @param _name The name of the drip being triggered.
function _pauseGelatoDripTask(IGelato _gelato, Drippie _drippie, string memory _name) internal {
GelatoTaskData memory taskData = _makeGelatoDripTaskData({ _drippie: _drippie, _name: _name });
_gelato.cancelTask(
GelatoTaskId.getTaskId({
taskCreator: taskData.taskCreator,
execAddress: taskData.execAddress,
execSelector: GelatoBytes.memorySliceSelector(taskData.execData),
moduleData: taskData.moduleData,
feeToken: taskData.feeToken
})
);
}
/// @notice Installs a drip in the drippie contract.
/// @param _gelato The gelato contract.
/// @param _drippie The drippie contract.
/// @param _name The name of the drip.
/// @param _config The configuration of the drip.
function _installDrip(
IGelato _gelato,
Drippie _drippie,
string memory _name,
Drippie.DripConfig memory _config
)
internal
{
function _installDrip(Drippie _drippie, string memory _name, Drippie.DripConfig memory _config) internal {
if (_drippie.getDripStatus(_name) == Drippie.DripStatus.NONE) {
console.log("installing %s", _name);
_drippie.create(_name, _config);
_startGelatoDripTask(_gelato, _drippie, _name);
console.log("%s installed successfully", _name);
} else {
console.log("%s already installed", _name);
......
[
{
"inputs": [
{
"internalType": "bytes",
"name": "_params",
"type": "bytes"
}
],
"name": "check",
"outputs": [
{
"internalType": "bool",
"name": "execute_",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"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"
}
]
\ No newline at end of file
[
{
"bytes": "32",
"label": "name",
"offset": 0,
"slot": "0",
"type": "string"
}
]
\ No newline at end of file
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
// Interfaces
import { IGelatoTreasury } from "interfaces/vendor/IGelatoTreasury.sol";
import { IDripCheck } from "src/periphery/drippie/IDripCheck.sol";
/// @title CheckGelatoLow
/// @notice DripCheck for checking if an account's Gelato ETH balance is below some threshold.
contract CheckGelatoLow is IDripCheck {
struct Params {
address treasury;
uint256 threshold;
address recipient;
}
/// @notice External event used to help client-side tooling encode parameters.
/// @param params Parameters to encode.
event _EventToExposeStructInABI__Params(Params params);
/// @inheritdoc IDripCheck
string public name = "CheckGelatoLow";
/// @inheritdoc IDripCheck
function check(bytes memory _params) external view returns (bool execute_) {
Params memory params = abi.decode(_params, (Params));
// Gelato represents ETH as 0xeeeee....eeeee.
address eth = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
// Get the total deposited amount.
uint256 deposited = IGelatoTreasury(params.treasury).totalDepositedAmount(params.recipient, eth);
// Get the total withdrawn amount.
uint256 withdrawn = IGelatoTreasury(params.treasury).totalWithdrawnAmount(params.recipient, eth);
// Figure out the current balance.
uint256 balance = deposited - withdrawn;
// Check if the balance is below the threshold.
execute_ = balance < params.threshold;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { CheckGelatoLow, IGelatoTreasury } from "src/periphery/drippie/dripchecks/CheckGelatoLow.sol";
/// @title MockGelatoTreasury
/// @notice Mocks the Gelato treasury for testing purposes. Allows arbitrary setting of balances.
contract MockGelatoTreasury is IGelatoTreasury {
mapping(address => mapping(address => uint256)) private totalDeposited;
mapping(address => mapping(address => uint256)) private totalWithdrawn;
function totalDepositedAmount(address _user, address _token) external view override returns (uint256) {
return totalDeposited[_token][_user];
}
function totalWithdrawnAmount(address _user, address _token) external view override returns (uint256) {
return totalWithdrawn[_token][_user];
}
function setTotalDepositedAmount(address _user, address _token, uint256 _amount) external {
totalDeposited[_token][_user] = _amount;
}
}
/// @title CheckGelatoLowTest
/// @notice Tests the CheckGelatoLow contract via fuzzing both the success and failure cases.
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 Test that the `name` function returns the correct value.
function test_name_succeeds() external view {
assertEq(c.name(), "CheckGelatoLow");
}
/// @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 view {
CheckGelatoLow.Params memory p =
CheckGelatoLow.Params({ treasury: address(gelato), threshold: _threshold, recipient: _recipient });
vm.assume(
gelato.totalDepositedAmount(_recipient, eth) - gelato.totalWithdrawnAmount(_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_highBalance_fails(uint256 _threshold, address _recipient) external {
CheckGelatoLow.Params memory p =
CheckGelatoLow.Params({ treasury: address(gelato), threshold: _threshold, recipient: _recipient });
gelato.setTotalDepositedAmount(_recipient, eth, _threshold);
assertEq(c.check(abi.encode(p)), false);
}
}
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