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

Merge branch 'develop' into refcell/rlp-style-fixes

parents 6cf9d616 a7bd0b11
......@@ -25,4 +25,4 @@
"eslint.format.enable": true,
"editorconfig.generateAuto": false,
"files.trimTrailingWhitespace": true
}
}
\ No newline at end of file
......@@ -3,7 +3,6 @@ package op_challenger
import (
"context"
"fmt"
"time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/config"
......@@ -31,37 +30,20 @@ func Main(ctx context.Context, logger log.Logger, cfg *config.Config) error {
return fmt.Errorf("failed to bind the fault dispute game contract: %w", err)
}
loader := fault.NewLoader(logger, contract)
responder, err := fault.NewFaultResponder(logger, txMgr, cfg.GameAddress)
gameLogger := logger.New("game", cfg.GameAddress)
loader := fault.NewLoader(contract)
responder, err := fault.NewFaultResponder(gameLogger, txMgr, cfg.GameAddress)
if err != nil {
return fmt.Errorf("failed to create the responder: %w", err)
}
trace := fault.NewAlphabetProvider(cfg.AlphabetTrace, uint64(cfg.GameDepth))
agent := fault.NewAgent(loader, cfg.GameDepth, trace, responder, cfg.AgreeWithProposedOutput, logger)
agent := fault.NewAgent(loader, cfg.GameDepth, trace, responder, cfg.AgreeWithProposedOutput, gameLogger)
caller, err := fault.NewFaultCallerFromBindings(cfg.GameAddress, client, logger)
caller, err := fault.NewFaultCallerFromBindings(cfg.GameAddress, client, gameLogger)
if err != nil {
return fmt.Errorf("failed to bind the fault contract: %w", err)
}
logger.Info("Fault game started")
for {
logger.Info("Performing action")
_ = agent.Act(ctx)
status, _ := caller.GetGameStatus(ctx)
if status != 0 {
caller.LogGameStatus(ctx)
return nil
} else {
caller.LogGameInfo(ctx)
}
select {
case <-time.After(300 * time.Millisecond):
// Continue
case <-ctx.Done():
return ctx.Err()
}
}
return fault.MonitorGame(ctx, gameLogger, cfg.AgreeWithProposedOutput, agent, caller)
}
......@@ -17,8 +17,8 @@ type Agent struct {
log log.Logger
}
func NewAgent(loader Loader, maxDepth int, trace TraceProvider, responder Responder, agreeWithProposedOutput bool, log log.Logger) Agent {
return Agent{
func NewAgent(loader Loader, maxDepth int, trace TraceProvider, responder Responder, agreeWithProposedOutput bool, log log.Logger) *Agent {
return &Agent{
solver: NewSolver(maxDepth, trace),
loader: loader,
responder: responder,
......@@ -35,12 +35,11 @@ func (a *Agent) Act(ctx context.Context) error {
}
game, err := a.newGameFromContracts(ctx)
if err != nil {
a.log.Error("Failed to create new game", "err", err)
return err
return fmt.Errorf("create game from contracts: %w", err)
}
// Create counter claims
for _, claim := range game.Claims() {
if err := a.move(ctx, claim, game); err != nil {
if err := a.move(ctx, claim, game); err != nil && !errors.Is(err, ErrGameDepthReached) {
log.Error("Failed to move", "err", err)
}
}
......@@ -57,11 +56,13 @@ func (a *Agent) Act(ctx context.Context) error {
// and returns true if the game resolves successfully.
func (a *Agent) tryResolve(ctx context.Context) bool {
if a.responder.CanResolve(ctx) {
a.log.Info("Resolving game")
err := a.responder.Resolve(ctx)
if err != nil {
return true
a.log.Error("Failed to resolve the game", "err", err)
return false
}
a.log.Error("failed to resolve the game", "err", err)
return true
}
return false
}
......@@ -86,8 +87,7 @@ func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) {
func (a *Agent) move(ctx context.Context, claim Claim, game Game) error {
nextMove, err := a.solver.NextMove(claim, game.AgreeWithClaimLevel(claim))
if err != nil {
a.log.Warn("Failed to execute the next move", "err", err)
return err
return fmt.Errorf("execute next move: %w", err)
}
if nextMove == nil {
a.log.Debug("No next move")
......@@ -98,7 +98,7 @@ func (a *Agent) move(ctx context.Context, claim Claim, game Game) error {
"value", move.Value, "trace_index", move.TraceIndex(a.maxDepth),
"parent_value", claim.Value, "parent_trace_index", claim.TraceIndex(a.maxDepth))
if game.IsDuplicate(move) {
log.Debug("Duplicate move")
log.Debug("Skipping duplicate move")
return nil
}
log.Info("Performing move")
......@@ -113,20 +113,19 @@ func (a *Agent) step(ctx context.Context, claim Claim, game Game) error {
agreeWithClaimLevel := game.AgreeWithClaimLevel(claim)
if agreeWithClaimLevel {
a.log.Warn("Agree with leaf claim, skipping step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
a.log.Debug("Agree with leaf claim, skipping step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
return nil
}
if claim.Countered {
a.log.Info("Claim already stepped on", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
a.log.Debug("Step already executed against claim", "depth", claim.Depth(), "index_at_depth", claim.IndexAtDepth(), "value", claim.Value)
return nil
}
a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
step, err := a.solver.AttemptStep(claim, agreeWithClaimLevel)
if err != nil {
a.log.Warn("Failed to get a step", "err", err)
return err
return fmt.Errorf("attempt step: %w", err)
}
a.log.Info("Performing step", "is_attack", step.IsAttack,
......
......@@ -19,15 +19,13 @@ type FaultDisputeGameCaller interface {
type FaultCaller struct {
FaultDisputeGameCaller
log log.Logger
fdgAddr common.Address
log log.Logger
}
func NewFaultCaller(fdgAddr common.Address, caller FaultDisputeGameCaller, log log.Logger) *FaultCaller {
func NewFaultCaller(caller FaultDisputeGameCaller, log log.Logger) *FaultCaller {
return &FaultCaller{
caller,
log,
fdgAddr,
}
}
......@@ -39,7 +37,6 @@ func NewFaultCallerFromBindings(fdgAddr common.Address, client *ethclient.Client
return &FaultCaller{
caller,
log,
fdgAddr,
}, nil
}
......@@ -55,24 +52,16 @@ func (fc *FaultCaller) LogGameInfo(ctx context.Context) {
fc.log.Error("failed to get claim count", "err", err)
return
}
fc.log.Info("Game info", "addr", fc.fdgAddr, "claims", claimLen, "status", GameStatusString(status))
fc.log.Info("Game info", "claims", claimLen, "status", GameStatusString(status))
}
// GetGameStatus returns the current game status.
// 0: In Progress
// 1: Challenger Won
// 2: Defender Won
func (fc *FaultCaller) GetGameStatus(ctx context.Context) (uint8, error) {
return fc.Status(&bind.CallOpts{Context: ctx})
}
func (fc *FaultCaller) LogGameStatus(ctx context.Context) {
status, err := fc.GetGameStatus(ctx)
if err != nil {
fc.log.Error("failed to get game status", "err", err)
return
}
fc.log.Info("Game status", "status", GameStatusString(status))
func (fc *FaultCaller) GetGameStatus(ctx context.Context) (GameStatus, error) {
status, err := fc.Status(&bind.CallOpts{Context: ctx})
return GameStatus(status), err
}
// GetClaimDataLength returns the number of claims in the game.
......@@ -90,13 +79,13 @@ func (fc *FaultCaller) LogClaimDataLength(ctx context.Context) {
}
// GameStatusString returns the current game status as a string.
func GameStatusString(status uint8) string {
func GameStatusString(status GameStatus) string {
switch status {
case 0:
case GameStatusInProgress:
return "In Progress"
case 1:
case GameStatusChallengerWon:
return "Challenger Won"
case 2:
case GameStatusDefenderWon:
return "Defender Won"
default:
return "Unknown"
......
......@@ -7,13 +7,11 @@ import (
"testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
var (
testAddr = common.HexToAddress("0x1234567890123456789012345678901234567890")
errMock = errors.New("mock error")
errMock = errors.New("mock error")
)
type mockFaultDisputeGameCaller struct {
......@@ -42,7 +40,7 @@ func TestFaultCaller_GetGameStatus(t *testing.T) {
tests := []struct {
name string
caller FaultDisputeGameCaller
expectedStatus uint8
expectedStatus GameStatus
expectedErr error
}{
{
......@@ -50,7 +48,7 @@ func TestFaultCaller_GetGameStatus(t *testing.T) {
caller: &mockFaultDisputeGameCaller{
status: 1,
},
expectedStatus: 1,
expectedStatus: GameStatusChallengerWon,
expectedErr: nil,
},
{
......@@ -65,7 +63,7 @@ func TestFaultCaller_GetGameStatus(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
fc := NewFaultCaller(testAddr, test.caller, nil)
fc := NewFaultCaller(test.caller, nil)
status, err := fc.GetGameStatus(context.Background())
require.Equal(t, test.expectedStatus, status)
require.Equal(t, test.expectedErr, err)
......@@ -100,7 +98,7 @@ func TestFaultCaller_GetClaimDataLength(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
fc := NewFaultCaller(testAddr, test.caller, nil)
fc := NewFaultCaller(test.caller, nil)
claimDataLen, err := fc.GetClaimDataLength(context.Background())
require.Equal(t, test.expectedClaimDataLen, claimDataLen)
require.Equal(t, test.expectedErr, err)
......
......@@ -5,7 +5,6 @@ import (
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/log"
)
// ClaimFetcher is a minimal interface around [bindings.FaultDisputeGameCaller].
......@@ -28,14 +27,12 @@ type Loader interface {
// loader pulls in fault dispute game claim data periodically and over subscriptions.
type loader struct {
log log.Logger
claimFetcher ClaimFetcher
}
// NewLoader creates a new [loader].
func NewLoader(log log.Logger, claimFetcher ClaimFetcher) *loader {
func NewLoader(claimFetcher ClaimFetcher) *loader {
return &loader{
log: log,
claimFetcher: claimFetcher,
}
}
......
......@@ -6,9 +6,7 @@ import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
......@@ -91,10 +89,9 @@ func (m *mockClaimFetcher) ClaimDataLen(opts *bind.CallOpts) (*big.Int, error) {
// TestLoader_FetchClaims_Succeeds tests [loader.FetchClaims].
func TestLoader_FetchClaims_Succeeds(t *testing.T) {
log := testlog.Logger(t, log.LvlError)
mockClaimFetcher := newMockClaimFetcher()
expectedClaims := mockClaimFetcher.returnClaims
loader := NewLoader(log, mockClaimFetcher)
loader := NewLoader(mockClaimFetcher)
claims, err := loader.FetchClaims(context.Background())
require.NoError(t, err)
require.ElementsMatch(t, []Claim{
......@@ -143,10 +140,9 @@ func TestLoader_FetchClaims_Succeeds(t *testing.T) {
// TestLoader_FetchClaims_ClaimDataErrors tests [loader.FetchClaims]
// when the claim fetcher [ClaimData] function call errors.
func TestLoader_FetchClaims_ClaimDataErrors(t *testing.T) {
log := testlog.Logger(t, log.LvlError)
mockClaimFetcher := newMockClaimFetcher()
mockClaimFetcher.claimDataError = true
loader := NewLoader(log, mockClaimFetcher)
loader := NewLoader(mockClaimFetcher)
claims, err := loader.FetchClaims(context.Background())
require.ErrorIs(t, err, mockClaimDataError)
require.Empty(t, claims)
......@@ -155,10 +151,9 @@ func TestLoader_FetchClaims_ClaimDataErrors(t *testing.T) {
// TestLoader_FetchClaims_ClaimLenErrors tests [loader.FetchClaims]
// when the claim fetcher [ClaimDataLen] function call errors.
func TestLoader_FetchClaims_ClaimLenErrors(t *testing.T) {
log := testlog.Logger(t, log.LvlError)
mockClaimFetcher := newMockClaimFetcher()
mockClaimFetcher.claimLenError = true
loader := NewLoader(log, mockClaimFetcher)
loader := NewLoader(mockClaimFetcher)
claims, err := loader.FetchClaims(context.Background())
require.ErrorIs(t, err, mockClaimLenError)
require.Empty(t, claims)
......
package fault
import (
"context"
"time"
"github.com/ethereum/go-ethereum/log"
)
type GameInfo interface {
GetGameStatus(context.Context) (GameStatus, error)
LogGameInfo(ctx context.Context)
}
type Actor interface {
Act(ctx context.Context) error
}
func MonitorGame(ctx context.Context, logger log.Logger, agreeWithProposedOutput bool, actor Actor, caller GameInfo) error {
logger.Info("Monitoring fault dispute game", "agreeWithOutput", agreeWithProposedOutput)
for {
done := progressGame(ctx, logger, agreeWithProposedOutput, actor, caller)
if done {
return nil
}
select {
case <-time.After(300 * time.Millisecond):
// Continue
case <-ctx.Done():
return ctx.Err()
}
}
}
// progressGame checks the current state of the game, and attempts to progress it by performing moves, steps or resolving
// Returns true if the game is complete or false if it needs to be monitored further
func progressGame(ctx context.Context, logger log.Logger, agreeWithProposedOutput bool, actor Actor, caller GameInfo) bool {
logger.Trace("Checking if actions are required")
if err := actor.Act(ctx); err != nil {
logger.Error("Error when acting on game", "err", err)
}
if status, err := caller.GetGameStatus(ctx); err != nil {
logger.Warn("Unable to retrieve game status", "err", err)
} else if status != 0 {
var expectedStatus GameStatus
if agreeWithProposedOutput {
expectedStatus = GameStatusChallengerWon
} else {
expectedStatus = GameStatusDefenderWon
}
if expectedStatus == status {
logger.Info("Game won", "status", GameStatusString(status))
} else {
logger.Error("Game lost", "status", GameStatusString(status))
}
return true
} else {
caller.LogGameInfo(ctx)
}
return false
}
package fault
import (
"context"
"errors"
"testing"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
func TestMonitorExitsWhenContextDone(t *testing.T) {
logger := testlog.Logger(t, log.LvlDebug)
actor := &stubActor{}
gameInfo := &stubGameInfo{}
ctx, cancel := context.WithCancel(context.Background())
cancel()
err := MonitorGame(ctx, logger, true, actor, gameInfo)
require.ErrorIs(t, err, context.Canceled)
}
func TestProgressGameAndLogState(t *testing.T) {
logger, _, actor, gameInfo := setupProgressGameTest(t)
done := progressGame(context.Background(), logger, true, actor, gameInfo)
require.False(t, done, "should not be done")
require.Equal(t, 1, actor.callCount, "should perform next actions")
require.Equal(t, 1, gameInfo.logCount, "should log latest game state")
}
func TestProgressGame_LogErrorFromAct(t *testing.T) {
logger, handler, actor, gameInfo := setupProgressGameTest(t)
actor.err = errors.New("Boom")
done := progressGame(context.Background(), logger, true, actor, gameInfo)
require.False(t, done, "should not be done")
require.Equal(t, 1, actor.callCount, "should perform next actions")
require.Equal(t, 1, gameInfo.logCount, "should log latest game state")
errLog := handler.FindLog(log.LvlError, "Error when acting on game")
require.NotNil(t, errLog, "should log error")
require.Equal(t, actor.err, errLog.GetContextValue("err"))
}
func TestProgressGame_LogErrorWhenGameLost(t *testing.T) {
tests := []struct {
name string
status GameStatus
agreeWithOutput bool
logLevel log.Lvl
logMsg string
statusText string
}{
{
name: "GameLostAsDefender",
status: GameStatusChallengerWon,
agreeWithOutput: false,
logLevel: log.LvlError,
logMsg: "Game lost",
statusText: "Challenger Won",
},
{
name: "GameLostAsChallenger",
status: GameStatusDefenderWon,
agreeWithOutput: true,
logLevel: log.LvlError,
logMsg: "Game lost",
statusText: "Defender Won",
},
{
name: "GameWonAsDefender",
status: GameStatusDefenderWon,
agreeWithOutput: false,
logLevel: log.LvlInfo,
logMsg: "Game won",
statusText: "Defender Won",
},
{
name: "GameWonAsChallenger",
status: GameStatusChallengerWon,
agreeWithOutput: true,
logLevel: log.LvlInfo,
logMsg: "Game won",
statusText: "Challenger Won",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
logger, handler, actor, gameInfo := setupProgressGameTest(t)
gameInfo.status = test.status
done := progressGame(context.Background(), logger, test.agreeWithOutput, actor, gameInfo)
require.True(t, done, "should be done")
require.Equal(t, 0, gameInfo.logCount, "should not log latest game state")
errLog := handler.FindLog(test.logLevel, test.logMsg)
require.NotNil(t, errLog, "should log game result")
require.Equal(t, test.statusText, errLog.GetContextValue("status"))
})
}
}
func setupProgressGameTest(t *testing.T) (log.Logger, *testlog.CapturingHandler, *stubActor, *stubGameInfo) {
logger := testlog.Logger(t, log.LvlDebug)
handler := &testlog.CapturingHandler{
Delegate: logger.GetHandler(),
}
logger.SetHandler(handler)
actor := &stubActor{}
gameInfo := &stubGameInfo{}
return logger, handler, actor, gameInfo
}
type stubActor struct {
callCount int
err error
}
func (a *stubActor) Act(ctx context.Context) error {
a.callCount++
return a.err
}
type stubGameInfo struct {
status GameStatus
err error
logCount int
}
func (s *stubGameInfo) GetGameStatus(ctx context.Context) (GameStatus, error) {
return s.status, s.err
}
func (s *stubGameInfo) LogGameInfo(ctx context.Context) {
s.logCount++
}
......@@ -123,9 +123,9 @@ func (r *faultResponder) sendTxAndWait(ctx context.Context, txData []byte) error
return err
}
if receipt.Status == types.ReceiptStatusFailed {
r.log.Error("responder tx successfully published but reverted", "tx_hash", receipt.TxHash)
r.log.Error("Responder tx successfully published but reverted", "tx_hash", receipt.TxHash)
} else {
r.log.Info("responder tx successfully published", "tx_hash", receipt.TxHash)
r.log.Debug("Responder tx successfully published", "tx_hash", receipt.TxHash)
}
return nil
}
......
......@@ -2,10 +2,15 @@ package fault
import (
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
)
var (
ErrGameDepthReached = errors.New("game depth reached")
)
// Solver uses a [TraceProvider] to determine the moves to make in a dispute game.
type Solver struct {
TraceProvider
......@@ -54,7 +59,7 @@ func (s *Solver) handleMiddle(claim Claim) (*Claim, error) {
return nil, err
}
if claim.Depth() == s.gameDepth {
return nil, errors.New("game depth reached")
return nil, ErrGameDepthReached
}
if claimCorrect {
return s.defend(claim)
......@@ -113,7 +118,7 @@ func (s *Solver) attack(claim Claim) (*Claim, error) {
position := claim.Attack()
value, err := s.traceAtPosition(position)
if err != nil {
return nil, err
return nil, fmt.Errorf("attack claim: %w", err)
}
return &Claim{
ClaimData: ClaimData{Value: value, Position: position},
......@@ -127,7 +132,7 @@ func (s *Solver) defend(claim Claim) (*Claim, error) {
position := claim.Defend()
value, err := s.traceAtPosition(position)
if err != nil {
return nil, err
return nil, fmt.Errorf("defend claim: %w", err)
}
return &Claim{
ClaimData: ClaimData{Value: value, Position: position},
......
......@@ -11,6 +11,14 @@ var (
ErrIndexTooLarge = errors.New("index is larger than the maximum index")
)
type GameStatus uint8
const (
GameStatusInProgress GameStatus = iota
GameStatusChallengerWon
GameStatusDefenderWon
)
// StepCallData encapsulates the data needed to perform a step.
type StepCallData struct {
ClaimIndex uint64
......
package testlog
import (
"github.com/ethereum/go-ethereum/log"
)
// CapturingHandler provides a log handler that captures all log records and optionally forwards them to a delegate.
// Note that it is not thread safe.
type CapturingHandler struct {
Delegate log.Handler
Logs []*log.Record
}
func (c *CapturingHandler) Log(r *log.Record) error {
c.Logs = append(c.Logs, r)
if c.Delegate != nil {
return c.Delegate.Log(r)
}
return nil
}
func (c *CapturingHandler) Clear() {
c.Logs = nil
}
func (c *CapturingHandler) FindLog(lvl log.Lvl, msg string) *HelperRecord {
for _, record := range c.Logs {
if record.Lvl == lvl && record.Msg == msg {
return &HelperRecord{record}
}
}
return nil
}
type HelperRecord struct {
*log.Record
}
func (h *HelperRecord) GetContextValue(name string) any {
for i := 0; i < len(h.Ctx); i += 2 {
if h.Ctx[i] == name {
return h.Ctx[i+1]
}
}
return nil
}
var _ log.Handler = (*CapturingHandler)(nil)
......@@ -209,7 +209,7 @@ func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (*
Data: candidate.TxData,
}
m.l.Info("creating tx", "to", rawTx.To, "from", m.cfg.From)
m.l.Info("Creating tx", "to", rawTx.To, "from", m.cfg.From)
// If the gas limit is set, we can use that as the gas
if candidate.GasLimit != 0 {
......@@ -333,7 +333,7 @@ func (m *SimpleTxManager) sendTx(ctx context.Context, tx *types.Transaction) (*t
// for the transaction.
func (m *SimpleTxManager) publishAndWaitForTx(ctx context.Context, tx *types.Transaction, sendState *SendState, receiptChan chan *types.Receipt) {
log := m.l.New("hash", tx.Hash(), "nonce", tx.Nonce(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap())
log.Info("publishing transaction")
log.Info("Publishing transaction")
cCtx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout)
defer cancel()
......
......@@ -103,6 +103,7 @@ export class WithdrawalMonitor extends BaseServiceV2<Options, Metrics, State> {
l2SignerOrProvider: this.options.l2RpcProvider,
l1ChainId: await getChainId(this.options.l1RpcProvider),
l2ChainId: await getChainId(this.options.l2RpcProvider),
bedrock: true,
})
// Not detected by default.
......
AdminFaucetAuthModuleTest:test_adminProof_verify_succeeds() (gas: 57577)
AdminFaucetAuthModuleTest:test_adminProof_verify_succeeds() (gas: 57573)
AdminFaucetAuthModuleTest:test_nonAdminProof_verify_succeeds() (gas: 59050)
AdminFaucetAuthModuleTest:test_proofWithWrongId_verify_succeeds() (gas: 60673)
AssetReceiverTest:test_constructor_succeeds() (gas: 9696)
......@@ -70,14 +70,14 @@ Drippie_Test:test_status_unauthorized_reverts() (gas: 167344)
Drippie_Test:test_trigger_oneFunction_succeeds() (gas: 338143)
Drippie_Test:test_trigger_twoFunctions_succeeds() (gas: 491870)
Drippie_Test:test_twice_inOneInterval_reverts() (gas: 303767)
FaucetTest:test_authAdmin_drip_succeeds() (gas: 366111)
FaucetTest:test_drip_afterTimeout_succeeds() (gas: 447899)
FaucetTest:test_drip_beforeTimeout_reverts() (gas: 378888)
FaucetTest:test_drip_disabledModule_reverts() (gas: 352405)
FaucetTest:test_drip_emitsEvent_succeeds() (gas: 369165)
FaucetTest:test_drip_githubSendsCorrectAmount_succeeds() (gas: 366611)
FaucetTest:test_drip_optimistNftSendsCorrectAmount_succeeds() (gas: 366555)
FaucetTest:test_drip_preventsReplayAttacks_succeeds() (gas: 369218)
FaucetTest:test_authAdmin_drip_succeeds() (gas: 366107)
FaucetTest:test_drip_afterTimeout_succeeds() (gas: 447891)
FaucetTest:test_drip_beforeTimeout_reverts() (gas: 378884)
FaucetTest:test_drip_disabledModule_reverts() (gas: 352401)
FaucetTest:test_drip_emitsEvent_succeeds() (gas: 369161)
FaucetTest:test_drip_githubSendsCorrectAmount_succeeds() (gas: 366607)
FaucetTest:test_drip_optimistNftSendsCorrectAmount_succeeds() (gas: 366551)
FaucetTest:test_drip_preventsReplayAttacks_succeeds() (gas: 369214)
FaucetTest:test_initialize_succeeds() (gas: 7626)
FaucetTest:test_nonAdmin_drip_fails() (gas: 262520)
FaucetTest:test_receive_succeeds() (gas: 17401)
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// A representation of an empty/uninitialized UID.
......@@ -14,18 +13,14 @@ error InvalidLength();
error InvalidSignature();
error NotFound();
/**
* @dev A struct representing EIP712 signature data.
*/
/// @dev A struct representing EIP712 signature data.
struct EIP712Signature {
uint8 v; // The recovery ID.
bytes32 r; // The x-coordinate of the nonce R.
bytes32 s; // The signature data.
}
/**
* @dev A struct representing a single attestation.
*/
/// @dev A struct representing a single attestation.
struct Attestation {
bytes32 uid; // A unique identifier of the attestation.
bytes32 schema; // The unique identifier of the schema.
......@@ -42,26 +37,18 @@ struct Attestation {
// Maximum upgrade forward-compatibility storage gap.
uint32 constant MAX_GAP = 50;
/**
* @dev A helper function to work with unchecked iterators in loops.
*
* @param i The index to increment.
*
* @return j The incremented index.
*/
/// @dev A helper function to work with unchecked iterators in loops.
/// @param i The index to increment.
/// @return j The incremented index.
function uncheckedInc(uint256 i) pure returns (uint256 j) {
unchecked {
j = i + 1;
}
}
/**
* @dev A helper function that converts a string to a bytes32.
*
* @param str The string to convert.
*
* @return The converted bytes32.
*/
/// @dev A helper function that converts a string to a bytes32.
/// @param str The string to convert.
/// @return The converted bytes32.
function stringToBytes32(string memory str) pure returns (bytes32) {
bytes32 result;
......@@ -72,13 +59,9 @@ function stringToBytes32(string memory str) pure returns (bytes32) {
return result;
}
/**
* @dev A helper function that converts a bytes32 to a string.
*
* @param data The bytes32 data to convert.
*
* @return The converted string.
*/
/// @dev A helper function that converts a bytes32 to a string.
/// @param data The bytes32 data to convert.
/// @return The converted string.
function bytes32ToString(bytes32 data) pure returns (string memory) {
bytes memory byteArray = new bytes(32);
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { ISchemaResolver } from "./resolver/ISchemaResolver.sol";
/**
* @title A struct representing a record for a submitted schema.
*/
/// @title A struct representing a record for a submitted schema.
struct SchemaRecord {
bytes32 uid; // The unique identifier of the schema.
ISchemaResolver resolver; // Optional schema resolver.
......@@ -17,31 +14,20 @@ struct SchemaRecord {
/// @title ISchemaRegistry
/// @notice The interface of global attestation schemas for the Ethereum Attestation Service protocol.
interface ISchemaRegistry {
/**
* @dev Emitted when a new schema has been registered
*
* @param uid The schema UID.
* @param registerer The address of the account used to register the schema.
*/
/// @dev Emitted when a new schema has been registered
/// @param uid The schema UID.
/// @param registerer The address of the account used to register the schema.
event Registered(bytes32 indexed uid, address registerer);
/**
* @dev Submits and reserves a new schema
*
* @param schema The schema data schema.
* @param resolver An optional schema resolver.
* @param revocable Whether the schema allows revocations explicitly.
*
* @return The UID of the new schema.
*/
/// @dev Submits and reserves a new schema
/// @param schema The schema data schema.
/// @param resolver An optional schema resolver.
/// @param revocable Whether the schema allows revocations explicitly.
/// @return The UID of the new schema.
function register(string calldata schema, ISchemaResolver resolver, bool revocable) external returns (bytes32);
/**
* @dev Returns an existing schema by UID
*
* @param uid The UID of the schema to retrieve.
*
* @return The schema data members.
*/
/// @dev Returns an existing schema by UID
/// @param uid The UID of the schema to retrieve.
/// @return The schema data members.
function getSchema(bytes32 uid) external view returns (SchemaRecord memory);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import { Semver } from "../universal/Semver.sol";
......@@ -22,14 +21,10 @@ contract SchemaRegistry is ISchemaRegistry, Semver {
// Upgrade forward-compatibility storage gap
uint256[MAX_GAP - 1] private __gap;
/**
* @dev Creates a new SchemaRegistry instance.
*/
/// @dev Creates a new SchemaRegistry instance.
constructor() Semver(1, 0, 0) {}
/**
* @inheritdoc ISchemaRegistry
*/
/// @inheritdoc ISchemaRegistry
function register(string calldata schema, ISchemaResolver resolver, bool revocable) external returns (bytes32) {
SchemaRecord memory schemaRecord = SchemaRecord({
uid: EMPTY_UID,
......@@ -51,20 +46,14 @@ contract SchemaRegistry is ISchemaRegistry, Semver {
return uid;
}
/**
* @inheritdoc ISchemaRegistry
*/
/// @inheritdoc ISchemaRegistry
function getSchema(bytes32 uid) external view returns (SchemaRecord memory) {
return _registry[uid];
}
/**
* @dev Calculates a UID for a given schema.
*
* @param schemaRecord The input schema.
*
* @return schema UID.
*/
/// @dev Calculates a UID for a given schema.
/// @param schemaRecord The input schema.
/// @return schema UID.
function _getUID(SchemaRecord memory schemaRecord) private pure returns (bytes32) {
return keccak256(abi.encodePacked(schemaRecord.schema, schemaRecord.resolver, schemaRecord.revocable));
}
......
......@@ -36,59 +36,44 @@ abstract contract EIP712Verifier is EIP712 {
// Upgrade forward-compatibility storage gap
uint256[MAX_GAP - 1] private __gap;
/**
* @dev Creates a new EIP712Verifier instance.
*
* @param version The current major version of the signing domain
*/
/// @dev Creates a new EIP712Verifier instance.
/// @param version The current major version of the signing domain
constructor(string memory name, string memory version) EIP712(name, version) {
_name = stringToBytes32(name);
}
/**
* @dev Returns the domain separator used in the encoding of the signatures for attest, and revoke.
*/
/// @notice Returns the domain separator used in the encoding of the signatures for attest, and revoke.
function getDomainSeparator() external view returns (bytes32) {
return _domainSeparatorV4();
}
/**
* @dev Returns the current nonce per-account.
*
* @param account The requested account.
*
* @return The current nonce.
*/
/// @notice Returns the current nonce per-account.
/// @param account The requested account.
/// @return The current nonce.
function getNonce(address account) external view returns (uint256) {
return _nonces[account];
}
/**
* Returns the EIP712 type hash for the attest function.
*/
/// @notice Returns the EIP712 type hash for the attest function.
/// @return The EIP712 attest function type hash.
function getAttestTypeHash() external pure returns (bytes32) {
return ATTEST_TYPEHASH;
}
/**
* Returns the EIP712 type hash for the revoke function.
*/
/// @notice Returns the EIP712 type hash for the revoke function.
/// @return hash_ The EIP712 revoke function type hash.
function getRevokeTypeHash() external pure returns (bytes32) {
return REVOKE_TYPEHASH;
}
/**
* Returns the EIP712 name.
*/
/// @notice Returns the EIP712 name.
/// @return The EIP712 name.
function getName() external view returns (string memory) {
return bytes32ToString(_name);
}
/**
* @dev Verifies delegated attestation request.
*
* @param request The arguments of the delegated attestation request.
*/
/// @notice Verifies delegated attestation request.
/// @param request The arguments of the delegated attestation request.
function _verifyAttest(DelegatedAttestationRequest memory request) internal {
AttestationRequestData memory data = request.data;
EIP712Signature memory signature = request.signature;
......@@ -118,11 +103,8 @@ abstract contract EIP712Verifier is EIP712 {
}
}
/**
* @dev Verifies delegated revocation request.
*
* @param request The arguments of the delegated revocation request.
*/
/// @notice Verifies delegated revocation request.
/// @param request The arguments of the delegated revocation request.
function _verifyRevoke(DelegatedRevocationRequest memory request) internal {
RevocationRequestData memory data = request.data;
EIP712Signature memory signature = request.signature;
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Attestation } from "../Common.sol";
......@@ -7,50 +6,33 @@ import { Attestation } from "../Common.sol";
/// @title ISchemaResolver
/// @notice The interface of an optional schema resolver.
interface ISchemaResolver {
/**
* @dev Returns whether the resolver supports ETH transfers.
*/
/// @notice Checks if the resolve can be sent ETH.
/// @return Whether the resolver supports ETH transfers.
function isPayable() external pure returns (bool);
/**
* @dev Processes an attestation and verifies whether it's valid.
*
* @param attestation The new attestation.
*
* @return Whether the attestation is valid.
*/
/// @notice Processes an attestation and verifies whether it's valid.
/// @param attestation The new attestation.
/// @return Whether the attestation is valid.
function attest(Attestation calldata attestation) external payable returns (bool);
/**
* @dev Processes multiple attestations and verifies whether they are valid.
*
* @param attestations The new attestations.
* @param values Explicit ETH amounts which were sent with each attestation.
*
* @return Whether all the attestations are valid.
*/
/// @notice Processes multiple attestations and verifies whether they are valid.
/// @param attestations The new attestations.
/// @param values Explicit ETH amounts which were sent with each attestation.
/// @return Whether all the attestations are valid.
function multiAttest(
Attestation[] calldata attestations,
uint256[] calldata values
) external payable returns (bool);
/**
* @dev Processes an attestation revocation and verifies if it can be revoked.
*
* @param attestation The existing attestation to be revoked.
*
* @return Whether the attestation can be revoked.
*/
/// @notice Processes an attestation revocation and verifies if it can be revoked.
/// @param attestation The existing attestation to be revoked.
/// @return Whether the attestation can be revoked.
function revoke(Attestation calldata attestation) external payable returns (bool);
/**
* @dev Processes revocation of multiple attestation and verifies they can be revoked.
*
* @param attestations The existing attestations to be revoked.
* @param values Explicit ETH amounts which were sent with each revocation.
*
* @return Whether the attestations can be revoked.
*/
/// @notice Processes revocation of multiple attestation and verifies they can be revoked.
/// @param attestations The existing attestations to be revoked.
/// @param values Explicit ETH amounts which were sent with each revocation.
/// @return Whether the attestations can be revoked.
function multiRevoke(
Attestation[] calldata attestations,
uint256[] calldata values
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import { Semver } from "../../universal/Semver.sol";
......@@ -19,11 +18,8 @@ abstract contract SchemaResolver is ISchemaResolver, Semver {
// The global EAS contract.
IEAS internal immutable _eas;
/**
* @dev Creates a new resolver.
*
* @param eas The address of the global EAS contract.
*/
/// @dev Creates a new resolver.
/// @param eas The address of the global EAS contract.
constructor(IEAS eas) Semver(1, 0, 0) {
if (address(eas) == address(0)) {
revert InvalidEAS();
......@@ -32,41 +28,31 @@ abstract contract SchemaResolver is ISchemaResolver, Semver {
_eas = eas;
}
/**
* @dev Ensures that only the EAS contract can make this call.
*/
/// @dev Ensures that only the EAS contract can make this call.
modifier onlyEAS() {
_onlyEAS();
_;
}
/**
* @inheritdoc ISchemaResolver
*/
/// @inheritdoc ISchemaResolver
function isPayable() public pure virtual returns (bool) {
return false;
}
/**
* @dev ETH callback.
*/
/// @dev ETH callback.
receive() external payable virtual {
if (!isPayable()) {
revert NotPayable();
}
}
/**
* @inheritdoc ISchemaResolver
*/
/// @inheritdoc ISchemaResolver
function attest(Attestation calldata attestation) external payable onlyEAS returns (bool) {
return onAttest(attestation, msg.value);
}
/**
* @inheritdoc ISchemaResolver
*/
/// @inheritdoc ISchemaResolver
function multiAttest(
Attestation[] calldata attestations,
uint256[] calldata values
......@@ -100,16 +86,12 @@ abstract contract SchemaResolver is ISchemaResolver, Semver {
return true;
}
/**
* @inheritdoc ISchemaResolver
*/
/// @inheritdoc ISchemaResolver
function revoke(Attestation calldata attestation) external payable onlyEAS returns (bool) {
return onRevoke(attestation, msg.value);
}
/**
* @inheritdoc ISchemaResolver
*/
/// @inheritdoc ISchemaResolver
function multiRevoke(
Attestation[] calldata attestations,
uint256[] calldata values
......@@ -143,35 +125,25 @@ abstract contract SchemaResolver is ISchemaResolver, Semver {
return true;
}
/**
* @dev A resolver callback that should be implemented by child contracts.
*
* @param attestation The new attestation.
* @param value An explicit ETH amount that was sent to the resolver. Please note that this value is verified in
* both attest() and multiAttest() callbacks EAS-only callbacks and that in case of multi attestations, it'll
* usually hold that msg.value != value, since msg.value aggregated the sent ETH amounts for all the attestations
* in the batch.
*
* @return Whether the attestation is valid.
*/
/// @notice A resolver callback that should be implemented by child contracts.
/// @param attestation The new attestation.
/// @param value An explicit ETH amount that was sent to the resolver. Please note that this value is verified in
/// both attest() and multiAttest() callbacks EAS-only callbacks and that in case of multi attestations, it'll
/// usually hold that msg.value != value, since msg.value aggregated the sent ETH amounts for all the attestations
/// in the batch.
/// @return Whether the attestation is valid.
function onAttest(Attestation calldata attestation, uint256 value) internal virtual returns (bool);
/**
* @dev Processes an attestation revocation and verifies if it can be revoked.
*
* @param attestation The existing attestation to be revoked.
* @param value An explicit ETH amount that was sent to the resolver. Please note that this value is verified in
* both revoke() and multiRevoke() callbacks EAS-only callbacks and that in case of multi attestations, it'll
* usually hold that msg.value != value, since msg.value aggregated the sent ETH amounts for all the attestations
* in the batch.
*
* @return Whether the attestation can be revoked.
*/
/// @notice Processes an attestation revocation and verifies if it can be revoked.
/// @param attestation The existing attestation to be revoked.
/// @param value An explicit ETH amount that was sent to the resolver. Please note that this value is verified in
/// both revoke() and multiRevoke() callbacks EAS-only callbacks and that in case of multi attestations, it'll
/// usually hold that msg.value != value, since msg.value aggregated the sent ETH amounts for all the attestations
/// in the batch.
/// @return Whether the attestation can be revoked.
function onRevoke(Attestation calldata attestation, uint256 value) internal virtual returns (bool);
/**
* @dev Ensures that only the EAS contract can make this call.
*/
/// @notice Ensures that only the EAS contract can make this call.
function _onlyEAS() private view {
if (msg.sender != address(_eas)) {
revert AccessDenied();
......
......@@ -7,12 +7,8 @@ interface IDripCheck {
// possible to easily encode parameters on the client side. Solidity does not support generics
// so it's not possible to do this with explicit typing.
/**
* @notice Checks whether a drip should be executable.
*
* @param _params Encoded parameters for the drip check.
*
* @return Whether the drip should be executed.
*/
function check(bytes memory _params) external view returns (bool);
/// @notice Checks whether a drip should be executable.
/// @param _params Encoded parameters for the drip check.
/// @return execute_ Whether the drip should be executed.
function check(bytes memory _params) external view returns (bool execute_);
}
......@@ -3,30 +3,23 @@ pragma solidity 0.8.15;
import { IDripCheck } from "../IDripCheck.sol";
/**
* @title CheckBalanceHigh
* @notice DripCheck for checking if an account's balance is above a given threshold.
*/
/// @title CheckBalanceHigh
/// @notice DripCheck for checking if an account's balance is above a given threshold.
contract CheckBalanceHigh is IDripCheck {
struct Params {
address target;
uint256 threshold;
}
/**
* @notice External event used to help client-side tooling encode parameters.
*
* @param params Parameters to encode.
*/
/// @notice External event used to help client-side tooling encode parameters.
/// @param params Parameters to encode.
event _EventToExposeStructInABI__Params(Params params);
/**
* @inheritdoc IDripCheck
*/
function check(bytes memory _params) external view returns (bool) {
/// @inheritdoc IDripCheck
function check(bytes memory _params) external view returns (bool execute_) {
Params memory params = abi.decode(_params, (Params));
// Check target balance is above threshold.
return params.target.balance > params.threshold;
execute_ = params.target.balance > params.threshold;
}
}
......@@ -3,30 +3,23 @@ pragma solidity 0.8.15;
import { IDripCheck } from "../IDripCheck.sol";
/**
* @title CheckBalanceLow
* @notice DripCheck for checking if an account's balance is below a given threshold.
*/
/// @title CheckBalanceLow
/// @notice DripCheck for checking if an account's balance is below a given threshold.
contract CheckBalanceLow is IDripCheck {
struct Params {
address target;
uint256 threshold;
}
/**
* @notice External event used to help client-side tooling encode parameters.
*
* @param params Parameters to encode.
*/
/// @notice External event used to help client-side tooling encode parameters.
/// @param params Parameters to encode.
event _EventToExposeStructInABI__Params(Params params);
/**
* @inheritdoc IDripCheck
*/
function check(bytes memory _params) external view returns (bool) {
/// @inheritdoc IDripCheck
function check(bytes memory _params) external view returns (bool execute_) {
Params memory params = abi.decode(_params, (Params));
// Check target ETH balance is below threshold.
return params.target.balance < params.threshold;
execute_ = params.target.balance < params.threshold;
}
}
......@@ -7,10 +7,8 @@ interface IGelatoTreasury {
function userTokenBalance(address _user, address _token) external view returns (uint256);
}
/**
* @title CheckGelatoLow
* @notice DripCheck for checking if an account's Gelato ETH balance is below some threshold.
*/
/// @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;
......@@ -18,25 +16,21 @@ contract CheckGelatoLow is IDripCheck {
address recipient;
}
/**
* @notice External event used to help client-side tooling encode parameters.
*
* @param params Parameters to encode.
*/
/// @notice External event used to help client-side tooling encode parameters.
/// @param params Parameters to encode.
event _EventToExposeStructInABI__Params(Params params);
/**
* @inheritdoc IDripCheck
*/
function check(bytes memory _params) external view returns (bool) {
/// @inheritdoc IDripCheck
function check(bytes memory _params) external view returns (bool execute_) {
Params memory params = abi.decode(_params, (Params));
// Check GelatoTreasury ETH balance is below threshold.
return
execute_ =
IGelatoTreasury(params.treasury).userTokenBalance(
params.recipient,
// Gelato represents ETH as 0xeeeee....eeeee
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
) < params.threshold;
) <
params.threshold;
}
}
......@@ -3,15 +3,11 @@ pragma solidity 0.8.15;
import { IDripCheck } from "../IDripCheck.sol";
/**
* @title CheckTrue
* @notice DripCheck that always returns true.
*/
/// @title CheckTrue
/// @notice DripCheck that always returns true.
contract CheckTrue is IDripCheck {
/**
* @inheritdoc IDripCheck
*/
function check(bytes memory) external pure returns (bool) {
return true;
/// @inheritdoc IDripCheck
function check(bytes memory) external pure returns (bool execute_) {
execute_ = true;
}
}
......@@ -3,32 +3,23 @@ pragma solidity 0.8.15;
import { IFaucetAuthModule } from "./authmodules/IFaucetAuthModule.sol";
/**
* @title SafeSend
* @notice Sends ETH to a recipient account without triggering any code.
*/
/// @title SafeSend
/// @notice Sends ETH to a recipient account without triggering any code.
contract SafeSend {
/**
* @param _recipient Account to send ETH to.
*/
/// @param _recipient Account to send ETH to.
constructor(address payable _recipient) payable {
selfdestruct(_recipient);
}
}
/**
* @title Faucet
* @notice Faucet contract that drips ETH to users.
*/
/// @title Faucet
/// @notice Faucet contract that drips ETH to users.
contract Faucet {
/**
* @notice Emitted on each drip.
*
* @param authModule The type of authentication that was used for verifying the drip.
* @param userId The id of the user that requested the drip.
* @param amount The amount of funds sent.
* @param recipient The recipient of the drip.
*/
/// @notice Emitted on each drip.
/// @param authModule The type of authentication that was used for verifying the drip.
/// @param userId The id of the user that requested the drip.
/// @param amount The amount of funds sent.
/// @param recipient The recipient of the drip.
event Drip(
string indexed authModule,
bytes32 indexed userId,
......@@ -36,26 +27,20 @@ contract Faucet {
address indexed recipient
);
/**
* @notice Parameters for a drip.
*/
/// @notice Parameters for a drip.
struct DripParameters {
address payable recipient;
bytes32 nonce;
}
/**
* @notice Parameters for authentication.
*/
/// @notice Parameters for authentication.
struct AuthParameters {
IFaucetAuthModule module;
bytes32 id;
bytes proof;
}
/**
* @notice Configuration for an authentication module.
*/
/// @notice Configuration for an authentication module.
struct ModuleConfig {
string name;
bool enabled;
......@@ -63,74 +48,51 @@ contract Faucet {
uint256 amount;
}
/**
* @notice Admin address that can configure the faucet.
*/
/// @notice Admin address that can configure the faucet.
address public immutable ADMIN;
/**
* @notice Mapping of authentication modules to their configurations.
*/
/// @notice Mapping of authentication modules to their configurations.
mapping(IFaucetAuthModule => ModuleConfig) public modules;
/**
* @notice Mapping of authentication IDs to the next timestamp at which they can be used.
*/
/// @notice Mapping of authentication IDs to the next timestamp at which they can be used.
mapping(IFaucetAuthModule => mapping(bytes32 => uint256)) public timeouts;
/**
* @notice Maps from id to nonces to whether or not they have been used.
*/
/// @notice Maps from id to nonces to whether or not they have been used.
mapping(bytes32 => mapping(bytes32 => bool)) public nonces;
/**
* @notice Modifier that makes a function admin priviledged.
*/
/// @notice Modifier that makes a function admin priviledged.
modifier priviledged() {
require(msg.sender == ADMIN, "Faucet: function can only be called by admin");
_;
}
/**
* @param _admin Admin address that can configure the faucet.
*/
/// @param _admin Admin address that can configure the faucet.
constructor(address _admin) {
ADMIN = _admin;
}
/**
* @notice Allows users to donate ETH to this contract.
*/
/// @notice Allows users to donate ETH to this contract.
receive() external payable {
// Thank you!
}
/**
* @notice Allows the admin to withdraw funds.
*
* @param _recipient Address to receive the funds.
* @param _amount Amount of ETH in wei to withdraw.
*/
/// @notice Allows the admin to withdraw funds.
/// @param _recipient Address to receive the funds.
/// @param _amount Amount of ETH in wei to withdraw.
function withdraw(address payable _recipient, uint256 _amount) public priviledged {
new SafeSend{ value: _amount }(_recipient);
}
/**
* @notice Allows the admin to configure an authentication module.
*
* @param _module Authentication module to configure.
* @param _config Configuration to set for the module.
*/
/// @notice Allows the admin to configure an authentication module.
/// @param _module Authentication module to configure.
/// @param _config Configuration to set for the module.
function configure(IFaucetAuthModule _module, ModuleConfig memory _config) public priviledged {
modules[_module] = _config;
}
/**
* @notice Drips ETH to a recipient account.
*
* @param _params Drip parameters.
* @param _auth Authentication parameters.
*/
/// @notice Drips ETH to a recipient account.
/// @param _params Drip parameters.
/// @param _auth Authentication parameters.
function drip(DripParameters memory _params, AuthParameters memory _auth) public {
// Grab the module config once.
ModuleConfig memory config = modules[_auth.module];
......
......@@ -6,41 +6,30 @@ import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/Sig
import { IFaucetAuthModule } from "./IFaucetAuthModule.sol";
import { Faucet } from "../Faucet.sol";
/**
* @title AdminFaucetAuthModule
* @notice FaucetAuthModule that allows an admin to sign off on a given faucet drip. Takes an admin
* as the constructor argument.
*/
/// @title AdminFaucetAuthModule
/// @notice FaucetAuthModule that allows an admin to sign off on a given faucet drip. Takes an admin
/// as the constructor argument.
contract AdminFaucetAuthModule is IFaucetAuthModule, EIP712 {
/**
* @notice Admin address that can sign off on drips.
*/
/// @notice Admin address that can sign off on drips.
address public immutable ADMIN;
/**
* @notice EIP712 typehash for the Proof type.
*/
/// @notice EIP712 typehash for the Proof type.
bytes32 public constant PROOF_TYPEHASH =
keccak256("Proof(address recipient,bytes32 nonce,bytes32 id)");
/**
* @notice Struct that represents a proof that verifies the admin.
*
* @custom:field recipient Address that will be receiving the faucet funds.
* @custom:field nonce Pseudorandom nonce to prevent replay attacks.
* @custom:field id id for the user requesting the faucet funds.
*/
/// @notice Struct that represents a proof that verifies the admin.
/// @custom:field recipient Address that will be receiving the faucet funds.
/// @custom:field nonce Pseudorandom nonce to prevent replay attacks.
/// @custom:field id id for the user requesting the faucet funds.
struct Proof {
address recipient;
bytes32 nonce;
bytes32 id;
}
/**
* @param _admin Admin address that can sign off on drips.
* @param _name Contract name.
* @param _version The current major version of the signing domain.
*/
/// @param _admin Admin address that can sign off on drips.
/// @param _name Contract name.
/// @param _version The current major version of the signing domain.
constructor(
address _admin,
string memory _name,
......@@ -49,22 +38,19 @@ contract AdminFaucetAuthModule is IFaucetAuthModule, EIP712 {
ADMIN = _admin;
}
/**
* @inheritdoc IFaucetAuthModule
*/
/// @inheritdoc IFaucetAuthModule
function verify(
Faucet.DripParameters memory _params,
bytes32 _id,
bytes memory _proof
) external view returns (bool) {
) external view returns (bool valid_) {
// Generate a EIP712 typed data hash to compare against the proof.
return
SignatureChecker.isValidSignatureNow(
ADMIN,
_hashTypedDataV4(
keccak256(abi.encode(PROOF_TYPEHASH, _params.recipient, _params.nonce, _id))
),
_proof
);
valid_ = SignatureChecker.isValidSignatureNow(
ADMIN,
_hashTypedDataV4(
keccak256(abi.encode(PROOF_TYPEHASH, _params.recipient, _params.nonce, _id))
),
_proof
);
}
}
......@@ -3,18 +3,14 @@ pragma solidity 0.8.15;
import { Faucet } from "../Faucet.sol";
/**
* @title IFaucetAuthModule
* @notice Interface for faucet authentication modules.
*/
/// @title IFaucetAuthModule
/// @notice Interface for faucet authentication modules.
interface IFaucetAuthModule {
/**
* @notice Verifies that the given drip parameters are valid.
*
* @param _params Drip parameters to verify.
* @param _id Authentication ID to verify.
* @param _proof Authentication proof to verify.
*/
/// @notice Verifies that the given drip parameters are valid.
/// @param _params Drip parameters to verify.
/// @param _id Authentication ID to verify.
/// @param _proof Authentication proof to verify.
/// @return valid_ True if the drip parameters are valid.
function verify(
Faucet.DripParameters memory _params,
bytes32 _id,
......
......@@ -3,39 +3,29 @@ pragma solidity 0.8.15;
import { Semver } from "../../universal/Semver.sol";
/**
* @title AttestationStation
* @author Optimism Collective
* @author Gitcoin
* @notice Where attestations live.
*/
/// @title AttestationStation
/// @author Optimism Collective
/// @author Gitcoin
/// @notice Where attestations live.
contract AttestationStation is Semver {
/**
* @notice Struct representing data that is being attested.
*
* @custom:field about Address for which the attestation is about.
* @custom:field key A bytes32 key for the attestation.
* @custom:field val The attestation as arbitrary bytes.
*/
/// @notice Struct representing data that is being attested.
/// @custom:field about Address for which the attestation is about.
/// @custom:field key A bytes32 key for the attestation.
/// @custom:field val The attestation as arbitrary bytes.
struct AttestationData {
address about;
bytes32 key;
bytes val;
}
/**
* @notice Maps addresses to attestations. Creator => About => Key => Value.
*/
/// @notice Maps addresses to attestations. Creator => About => Key => Value.
mapping(address => mapping(address => mapping(bytes32 => bytes))) public attestations;
/**
* @notice Emitted when Attestation is created.
*
* @param creator Address that made the attestation.
* @param about Address attestation is about.
* @param key Key of the attestation.
* @param val Value of the attestation.
*/
/// @notice Emitted when Attestation is created.
/// @param creator Address that made the attestation.
/// @param about Address attestation is about.
/// @param key Key of the attestation.
/// @param val Value of the attestation.
event AttestationCreated(
address indexed creator,
address indexed about,
......@@ -43,18 +33,13 @@ contract AttestationStation is Semver {
bytes val
);
/**
* @custom:semver 1.1.0
*/
/// @custom:semver 1.1.0
constructor() Semver(1, 1, 0) {}
/**
* @notice Allows anyone to create an attestation.
*
* @param _about Address that the attestation is about.
* @param _key A key used to namespace the attestation.
* @param _val An arbitrary value stored as part of the attestation.
*/
/// @notice Allows anyone to create an attestation.
/// @param _about Address that the attestation is about.
/// @param _key A key used to namespace the attestation.
/// @param _val An arbitrary value stored as part of the attestation.
function attest(
address _about,
bytes32 _key,
......@@ -65,11 +50,8 @@ contract AttestationStation is Semver {
emit AttestationCreated(msg.sender, _about, _key, _val);
}
/**
* @notice Allows anyone to create attestations.
*
* @param _attestations An array of AttestationData structs.
*/
/// @notice Allows anyone to create attestations.
/// @param _attestations An array of AttestationData structs.
function attest(AttestationData[] calldata _attestations) external {
uint256 length = _attestations.length;
for (uint256 i = 0; i < length; ) {
......
......@@ -9,41 +9,29 @@ import { AttestationStation } from "./AttestationStation.sol";
import { OptimistAllowlist } from "./OptimistAllowlist.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
/**
* @author Optimism Collective
* @author Gitcoin
* @title Optimist
* @notice A Soul Bound Token for real humans only(tm).
*/
/// @author Optimism Collective
/// @author Gitcoin
/// @title Optimist
/// @notice A Soul Bound Token for real humans only(tm).
contract Optimist is ERC721BurnableUpgradeable, Semver {
/**
* @notice Attestation key used by the attestor to attest the baseURI.
*/
/// @notice Attestation key used by the attestor to attest the baseURI.
bytes32 public constant BASE_URI_ATTESTATION_KEY = bytes32("optimist.base-uri");
/**
* @notice Attestor who attests to baseURI.
*/
/// @notice Attestor who attests to baseURI.
address public immutable BASE_URI_ATTESTOR;
/**
* @notice Address of the AttestationStation contract.
*/
/// @notice Address of the AttestationStation contract.
AttestationStation public immutable ATTESTATION_STATION;
/**
* @notice Address of the OptimistAllowlist contract.
*/
/// @notice Address of the OptimistAllowlist contract.
OptimistAllowlist public immutable OPTIMIST_ALLOWLIST;
/**
* @custom:semver 2.0.0
* @param _name Token name.
* @param _symbol Token symbol.
* @param _baseURIAttestor Address of the baseURI attestor.
* @param _attestationStation Address of the AttestationStation contract.
* @param _optimistAllowlist Address of the OptimistAllowlist contract
*/
/// @custom:semver 2.0.0
/// @param _name Token name.
/// @param _symbol Token symbol.
/// @param _baseURIAttestor Address of the baseURI attestor.
/// @param _attestationStation Address of the AttestationStation contract.
/// @param _optimistAllowlist Address of the OptimistAllowlist contract
constructor(
string memory _name,
string memory _symbol,
......@@ -57,109 +45,81 @@ contract Optimist is ERC721BurnableUpgradeable, Semver {
initialize(_name, _symbol);
}
/**
* @notice Initializes the Optimist contract.
*
* @param _name Token name.
* @param _symbol Token symbol.
*/
/// @notice Initializes the Optimist contract.
/// @param _name Token name.
/// @param _symbol Token symbol.
function initialize(string memory _name, string memory _symbol) public initializer {
__ERC721_init(_name, _symbol);
__ERC721Burnable_init();
}
/**
* @notice Allows an address to mint an Optimist NFT. Token ID is the uint256 representation
* of the recipient's address. Recipients must be permitted to mint, eventually anyone
* will be able to mint. One token per address.
*
* @param _recipient Address of the token recipient.
*/
/// @notice Allows an address to mint an Optimist NFT. Token ID is the uint256 representation
/// of the recipient's address. Recipients must be permitted to mint, eventually anyone
/// will be able to mint. One token per address.
/// @param _recipient Address of the token recipient.
function mint(address _recipient) public {
require(isOnAllowList(_recipient), "Optimist: address is not on allowList");
_safeMint(_recipient, tokenIdOfAddress(_recipient));
}
/**
* @notice Returns the baseURI for all tokens.
*
* @return BaseURI for all tokens.
*/
function baseURI() public view returns (string memory) {
return
string(
abi.encodePacked(
ATTESTATION_STATION.attestations(
BASE_URI_ATTESTOR,
address(this),
bytes32("optimist.base-uri")
)
/// @notice Returns the baseURI for all tokens.
/// @return uri_ BaseURI for all tokens.
function baseURI() public view returns (string memory uri_) {
uri_ = string(
abi.encodePacked(
ATTESTATION_STATION.attestations(
BASE_URI_ATTESTOR,
address(this),
bytes32("optimist.base-uri")
)
);
)
);
}
/**
* @notice Returns the token URI for a given token by ID
*
* @param _tokenId Token ID to query.
* @return Token URI for the given token by ID.
*/
function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) {
return
string(
abi.encodePacked(
baseURI(),
"/",
// Properly format the token ID as a 20 byte hex string (address).
Strings.toHexString(_tokenId, 20),
".json"
)
);
/// @notice Returns the token URI for a given token by ID
/// @param _tokenId Token ID to query.
/// @return uri_ Token URI for the given token by ID.
function tokenURI(uint256 _tokenId) public view virtual override returns (string memory uri_) {
uri_ = string(
abi.encodePacked(
baseURI(),
"/",
// Properly format the token ID as a 20 byte hex string (address).
Strings.toHexString(_tokenId, 20),
".json"
)
);
}
/**
* @notice Checks OptimistAllowlist to determine whether a given address is allowed to mint
* the Optimist NFT. Since the Optimist NFT will also be used as part of the
* Citizens House, mints are currently restricted. Eventually anyone will be able
* to mint.
*
* @return Whether or not the address is allowed to mint yet.
*/
function isOnAllowList(address _recipient) public view returns (bool) {
return OPTIMIST_ALLOWLIST.isAllowedToMint(_recipient);
/// @notice Checks OptimistAllowlist to determine whether a given address is allowed to mint
/// the Optimist NFT. Since the Optimist NFT will also be used as part of the
/// Citizens House, mints are currently restricted. Eventually anyone will be able
/// to mint.
/// @return allowed_ Whether or not the address is allowed to mint yet.
function isOnAllowList(address _recipient) public view returns (bool allowed_) {
allowed_ = OPTIMIST_ALLOWLIST.isAllowedToMint(_recipient);
}
/**
* @notice Returns the token ID for the token owned by a given address. This is the uint256
* representation of the given address.
*
* @return Token ID for the token owned by the given address.
*/
/// @notice Returns the token ID for the token owned by a given address. This is the uint256
/// representation of the given address.
/// @return Token ID for the token owned by the given address.
function tokenIdOfAddress(address _owner) public pure returns (uint256) {
return uint256(uint160(_owner));
}
/**
* @notice Disabled for the Optimist NFT (Soul Bound Token).
*/
/// @notice Disabled for the Optimist NFT (Soul Bound Token).
function approve(address, uint256) public pure override {
revert("Optimist: soul bound token");
}
/**
* @notice Disabled for the Optimist NFT (Soul Bound Token).
*/
/// @notice Disabled for the Optimist NFT (Soul Bound Token).
function setApprovalForAll(address, bool) public virtual override {
revert("Optimist: soul bound token");
}
/**
* @notice Prevents transfers of the Optimist NFT (Soul Bound Token).
*
* @param _from Address of the token sender.
* @param _to Address of the token recipient.
*/
/// @notice Prevents transfers of the Optimist NFT (Soul Bound Token).
/// @param _from Address of the token sender.
/// @param _to Address of the token recipient.
function _beforeTokenTransfer(
address _from,
address _to,
......
......@@ -5,54 +5,37 @@ import { Semver } from "../../universal/Semver.sol";
import { AttestationStation } from "./AttestationStation.sol";
import { OptimistConstants } from "./libraries/OptimistConstants.sol";
/**
* @title OptimistAllowlist
* @notice Source of truth for whether an address is able to mint an Optimist NFT.
isAllowedToMint function checks various signals to return boolean value for whether an
address is eligible or not.
*/
/// @title OptimistAllowlist
/// @notice Source of truth for whether an address is able to mint an Optimist NFT.
/// isAllowedToMint function checks various signals to return boolean value
/// for whether an address is eligible or not.
contract OptimistAllowlist is Semver {
/**
* @notice Attestation key used by the AllowlistAttestor to manually add addresses to the
* allowlist.
*/
/// @notice Attestation key used by the AllowlistAttestor to manually add addresses to the
/// allowlist.
bytes32 public constant OPTIMIST_CAN_MINT_ATTESTATION_KEY = bytes32("optimist.can-mint");
/**
* @notice Attestation key used by Coinbase to issue attestations for Quest participants.
*/
/// @notice Attestation key used by Coinbase to issue attestations for Quest participants.
bytes32 public constant COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY =
bytes32("coinbase.quest-eligible");
/**
* @notice Address of the AttestationStation contract.
*/
/// @notice Address of the AttestationStation contract.
AttestationStation public immutable ATTESTATION_STATION;
/**
* @notice Attestor that issues 'optimist.can-mint' attestations.
*/
/// @notice Attestor that issues 'optimist.can-mint' attestations.
address public immutable ALLOWLIST_ATTESTOR;
/**
* @notice Attestor that issues 'coinbase.quest-eligible' attestations.
*/
/// @notice Attestor that issues 'coinbase.quest-eligible' attestations.
address public immutable COINBASE_QUEST_ATTESTOR;
/**
* @notice Address of OptimistInviter contract that issues 'optimist.can-mint-from-invite'
* attestations.
*/
/// @notice Address of OptimistInviter contract that issues 'optimist.can-mint-from-invite'
/// attestations.
address public immutable OPTIMIST_INVITER;
/**
* @custom:semver 1.0.0
*
* @param _attestationStation Address of the AttestationStation contract.
* @param _allowlistAttestor Address of the allowlist attestor.
* @param _coinbaseQuestAttestor Address of the Coinbase Quest attestor.
* @param _optimistInviter Address of the OptimistInviter contract.
*/
/// @custom:semver 1.0.0
/// @param _attestationStation Address of the AttestationStation contract.
/// @param _allowlistAttestor Address of the allowlist attestor.
/// @param _coinbaseQuestAttestor Address of the Coinbase Quest attestor.
/// @param _optimistInviter Address of the OptimistInviter contract.
constructor(
AttestationStation _attestationStation,
address _allowlistAttestor,
......@@ -65,95 +48,83 @@ contract OptimistAllowlist is Semver {
OPTIMIST_INVITER = _optimistInviter;
}
/**
* @notice Checks whether a given address is allowed to mint the Optimist NFT yet. Since the
* Optimist NFT will also be used as part of the Citizens House, mints are currently
* restricted. Eventually anyone will be able to mint.
*
* Currently, address is allowed to mint if it satisfies any of the following:
* 1) Has a valid 'optimist.can-mint' attestation from the allowlist attestor.
* 2) Has a valid 'coinbase.quest-eligible' attestation from Coinbase Quest attestor
* 3) Has a valid 'optimist.can-mint-from-invite' attestation from the OptimistInviter
* contract.
*
* @param _claimer Address to check.
*
* @return Whether or not the address is allowed to mint yet.
*/
function isAllowedToMint(address _claimer) public view returns (bool) {
return
/// @notice Checks whether a given address is allowed to mint the Optimist NFT yet. Since the
/// Optimist NFT will also be used as part of the Citizens House, mints are currently
/// restricted. Eventually anyone will be able to mint.
/// Currently, address is allowed to mint if it satisfies any of the following:
/// 1) Has a valid 'optimist.can-mint' attestation from the allowlist attestor.
/// 2) Has a valid 'coinbase.quest-eligible' attestation from Coinbase Quest attestor
/// 3) Has a valid 'optimist.can-mint-from-invite' attestation from the OptimistInviter
/// contract.
/// @param _claimer Address to check.
/// @return allowed_ Whether or not the address is allowed to mint yet.
function isAllowedToMint(address _claimer) public view returns (bool allowed_) {
allowed_ =
_hasAttestationFromAllowlistAttestor(_claimer) ||
_hasAttestationFromCoinbaseQuestAttestor(_claimer) ||
_hasAttestationFromOptimistInviter(_claimer);
}
/**
* @notice Checks whether an address has a valid 'optimist.can-mint' attestation from the
* allowlist attestor.
*
* @param _claimer Address to check.
*
* @return Whether or not the address has a valid attestation.
*/
function _hasAttestationFromAllowlistAttestor(address _claimer) internal view returns (bool) {
/// @notice Checks whether an address has a valid 'optimist.can-mint' attestation from the
/// allowlist attestor.
/// @param _claimer Address to check.
/// @return valid_ Whether or not the address has a valid attestation.
function _hasAttestationFromAllowlistAttestor(address _claimer)
internal
view
returns (bool valid_)
{
// Expected attestation value is bytes32("true")
return
_hasValidAttestation(ALLOWLIST_ATTESTOR, _claimer, OPTIMIST_CAN_MINT_ATTESTATION_KEY);
valid_ = _hasValidAttestation(
ALLOWLIST_ATTESTOR,
_claimer,
OPTIMIST_CAN_MINT_ATTESTATION_KEY
);
}
/**
* @notice Checks whether an address has a valid attestation from the Coinbase attestor.
*
* @param _claimer Address to check.
*
* @return Whether or not the address has a valid attestation.
*/
/// @notice Checks whether an address has a valid attestation from the Coinbase attestor.
/// @param _claimer Address to check.
/// @return valid_ Whether or not the address has a valid attestation.
function _hasAttestationFromCoinbaseQuestAttestor(address _claimer)
internal
view
returns (bool)
returns (bool valid_)
{
// Expected attestation value is bytes32("true")
return
_hasValidAttestation(
COINBASE_QUEST_ATTESTOR,
_claimer,
COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY
);
valid_ = _hasValidAttestation(
COINBASE_QUEST_ATTESTOR,
_claimer,
COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY
);
}
/**
* @notice Checks whether an address has a valid attestation from the OptimistInviter contract.
*
* @param _claimer Address to check.
*
* @return Whether or not the address has a valid attestation.
*/
function _hasAttestationFromOptimistInviter(address _claimer) internal view returns (bool) {
/// @notice Checks whether an address has a valid attestation from the OptimistInviter contract.
/// @param _claimer Address to check.
/// @return valid_ Whether or not the address has a valid attestation.
function _hasAttestationFromOptimistInviter(address _claimer)
internal
view
returns (bool valid_)
{
// Expected attestation value is the inviter's address
return
_hasValidAttestation(
OPTIMIST_INVITER,
_claimer,
OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY
);
valid_ = _hasValidAttestation(
OPTIMIST_INVITER,
_claimer,
OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY
);
}
/**
* @notice Checks whether an address has a valid truthy attestation.
* Any attestation val other than bytes32("") is considered truthy.
*
* @param _creator Address that made the attestation.
* @param _about Address attestation is about.
* @param _key Key of the attestation.
*
* @return Whether or not the address has a valid truthy attestation.
*/
/// @notice Checks whether an address has a valid truthy attestation.
/// Any attestation val other than bytes32("") is considered truthy.
/// @param _creator Address that made the attestation.
/// @param _about Address attestation is about.
/// @param _key Key of the attestation.
/// @return valid_ Whether or not the address has a valid truthy attestation.
function _hasValidAttestation(
address _creator,
address _about,
bytes32 _key
) internal view returns (bool) {
return ATTESTATION_STATION.attestations(_creator, _about, _key).length > 0;
) internal view returns (bool valid_) {
valid_ = ATTESTATION_STATION.attestations(_creator, _about, _key).length > 0;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
/**
* @title OptimistConstants
* @notice Library for storing Optimist related constants that are shared in multiple contracts.
*/
/// @title OptimistConstants
/// @notice Library for storing Optimist related constants that are shared in multiple contracts.
library OptimistConstants {
/**
* @notice Attestation key issued by OptimistInviter allowing the attested account to mint.
*/
/// @notice Attestation key issued by OptimistInviter allowing the attested account to mint.
bytes32 internal constant OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY =
bytes32("optimist.can-mint-from-invite");
}
......@@ -5,9 +5,7 @@ import { Test } from "forge-std/Test.sol";
import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol";
contract AddressAliasHelper_applyAndUndo_Test is Test {
/**
* @notice Tests that applying and then undoing an alias results in the original address.
*/
/// @notice Tests that applying and then undoing an alias results in the original address.
function testFuzz_applyAndUndo_succeeds(address _address) external {
address aliased = AddressAliasHelper.applyL1ToL2Alias(_address);
address unaliased = AddressAliasHelper.undoL1ToL2Alias(aliased);
......
//SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
......@@ -6,41 +6,32 @@ import { AdminFaucetAuthModule } from "../periphery/faucet/authmodules/AdminFauc
import { Faucet } from "../periphery/faucet/Faucet.sol";
import { FaucetHelper } from "./Helpers.sol";
/**
* @title AdminFaucetAuthModuleTest
* @notice Tests the AdminFaucetAuthModule contract.
*/
/// @title AdminFaucetAuthModuleTest
/// @notice Tests the AdminFaucetAuthModule contract.
contract AdminFaucetAuthModuleTest is Test {
/**
* @notice The admin of the `AdminFaucetAuthModule` contract.
*/
/// @notice The admin of the `AdminFaucetAuthModule` contract.
address internal admin;
/**
* @notice Private key of the `admin`.
*/
/// @notice Private key of the `admin`.
uint256 internal adminKey;
/**
* @notice Not an admin of the `AdminFaucetAuthModule` contract.
*/
/// @notice Not an admin of the `AdminFaucetAuthModule` contract.
address internal nonAdmin;
/**
* @notice Private key of the `nonAdmin`.
*/
/// @notice Private key of the `nonAdmin`.
uint256 internal nonAdminKey;
/**
* @notice An instance of the `AdminFaucetAuthModule` contract.
*/
/// @notice An instance of the `AdminFaucetAuthModule` contract.
AdminFaucetAuthModule internal adminFam;
/**
* @notice An instance of the `FaucetHelper` contract.
*/
/// @notice An instance of the `FaucetHelper` contract.
FaucetHelper internal faucetHelper;
string internal adminFamName = "AdminFAM";
string internal adminFamVersion = "1";
/**
* @notice Deploy the `AdminFaucetAuthModule` contract.
*/
/// @notice Deploy the `AdminFaucetAuthModule` contract.
function setUp() external {
adminKey = 0xB0B0B0B0;
admin = vm.addr(adminKey);
......@@ -53,10 +44,7 @@ contract AdminFaucetAuthModuleTest is Test {
faucetHelper = new FaucetHelper();
}
/**
* @notice Get signature as a bytes blob.
*
*/
/// @notice Get signature as a bytes blob.
function _getSignature(uint256 _signingPrivateKey, bytes32 _digest)
internal
pure
......@@ -68,11 +56,9 @@ contract AdminFaucetAuthModuleTest is Test {
return signature;
}
/**
* @notice Signs a proof with the given private key and returns the signature using
* the given EIP712 domain separator. This assumes that the issuer's address is the
* corresponding public key to _issuerPrivateKey.
*/
/// @notice Signs a proof with the given private key and returns the signature using
/// the given EIP712 domain separator. This assumes that the issuer's address is the
/// corresponding public key to _issuerPrivateKey.
function issueProofWithEIP712Domain(
uint256 _issuerPrivateKey,
bytes memory _eip712Name,
......@@ -101,9 +87,7 @@ contract AdminFaucetAuthModuleTest is Test {
);
}
/**
* @notice assert that verify returns true for valid proofs signed by admins.
*/
/// @notice Assert that verify returns true for valid proofs signed by admins.
function test_adminProof_verify_succeeds() external {
bytes32 nonce = faucetHelper.consumeNonce();
address fundsReceiver = makeAddr("fundsReceiver");
......@@ -129,9 +113,7 @@ contract AdminFaucetAuthModuleTest is Test {
);
}
/**
* @notice assert that verify returns false for proofs signed by nonadmins.
*/
/// @notice Assert that verify returns false for proofs signed by nonadmins.
function test_nonAdminProof_verify_succeeds() external {
bytes32 nonce = faucetHelper.consumeNonce();
address fundsReceiver = makeAddr("fundsReceiver");
......@@ -157,10 +139,8 @@ contract AdminFaucetAuthModuleTest is Test {
);
}
/**
* @notice assert that verify returns false for proofs where the id in the proof is different
* than the id in the call to verify.
*/
/// @notice Assert that verify returns false for proofs where the id in the proof is different
/// than the id in the call to verify.
function test_proofWithWrongId_verify_succeeds() external {
bytes32 nonce = faucetHelper.consumeNonce();
address fundsReceiver = makeAddr("fundsReceiver");
......
//SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
/* Testing utilities */
// Testing utilities
import { Test } from "forge-std/Test.sol";
import { TestERC20 } from "./Helpers.sol";
import { TestERC721 } from "./Helpers.sol";
......@@ -53,12 +53,12 @@ contract AssetReceiver_Initializer is Test {
}
contract AssetReceiverTest is AssetReceiver_Initializer {
// Tests if the owner was set correctly during deploy
/// @notice Tests if the owner was set correctly during deploy.
function test_constructor_succeeds() external {
assertEq(address(alice), assetReceiver.owner());
}
// Tests that receive works as inteded
/// @notice Tests that receive works as inteded.
function test_receive_succeeds() external {
// Check that contract balance is 0 initially
assertEq(address(assetReceiver).balance, 0);
......@@ -74,7 +74,8 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(address(assetReceiver).balance, 100);
}
// Tests withdrawETH function with only an address as argument, called by owner
/// @notice Tests withdrawETH function with only an address
/// as an argument, called by owner.
function test_withdrawETH_succeeds() external {
// Check contract initial balance
assertEq(address(assetReceiver).balance, 0);
......@@ -96,14 +97,14 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(address(alice).balance, 2 ether);
}
// withdrawETH should fail if called by non-owner
/// @notice withdrawETH should fail if called by non-owner.
function test_withdrawETH_unauthorized_reverts() external {
vm.deal(address(assetReceiver), 1 ether);
vm.expectRevert("UNAUTHORIZED");
assetReceiver.withdrawETH(payable(alice));
}
// Similar as withdrawETH but specify amount to withdraw
/// @notice Similar as withdrawETH but specify amount to withdraw.
function test_withdrawETHwithAmount_succeeds() external {
assertEq(address(assetReceiver).balance, 0);
......@@ -124,14 +125,14 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(address(alice).balance, 1.5 ether);
}
// withdrawETH with address and amount as arguments called by non-owner
/// @notice withdrawETH with address and amount as arguments called by non-owner.
function test_withdrawETHwithAmount_unauthorized_reverts() external {
vm.deal(address(assetReceiver), 1 ether);
vm.expectRevert("UNAUTHORIZED");
assetReceiver.withdrawETH(payable(alice), 0.5 ether);
}
// Test withdrawERC20 with token and address arguments, from owner
/// @notice Test withdrawERC20 with token and address arguments, from owner.
function test_withdrawERC20_succeeds() external {
// check balances before the call
assertEq(testERC20.balanceOf(address(assetReceiver)), 0);
......@@ -152,14 +153,14 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(testERC20.balanceOf(address(assetReceiver)), 0);
}
// Same as withdrawERC20 but call from non-owner
/// @notice Same as withdrawERC20 but call from non-owner.
function test_withdrawERC20_unauthorized_reverts() external {
deal(address(testERC20), address(assetReceiver), 100_000);
vm.expectRevert("UNAUTHORIZED");
assetReceiver.withdrawERC20(testERC20, alice);
}
// Similar as withdrawERC20 but specify amount to withdraw
/// @notice Similar as withdrawERC20 but specify amount to withdraw.
function test_withdrawERC20withAmount_succeeds() external {
// check balances before the call
assertEq(testERC20.balanceOf(address(assetReceiver)), 0);
......@@ -180,14 +181,14 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(testERC20.balanceOf(address(assetReceiver)), 50_000);
}
// Similar as withdrawERC20 with amount but call from non-owner
/// @notice Similar as withdrawERC20 with amount but call from non-owner.
function test_withdrawERC20withAmount_unauthorized_reverts() external {
deal(address(testERC20), address(assetReceiver), 100_000);
vm.expectRevert("UNAUTHORIZED");
assetReceiver.withdrawERC20(testERC20, alice, 50_000);
}
// Test withdrawERC721 from owner
/// @notice Test withdrawERC721 from owner.
function test_withdrawERC721_succeeds() external {
// Check owner of the token before calling withdrawERC721
assertEq(testERC721.ownerOf(DEFAULT_TOKEN_ID), alice);
......@@ -208,7 +209,7 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(testERC721.ownerOf(DEFAULT_TOKEN_ID), alice);
}
// Similar as withdrawERC721 but call from non-owner
/// @notice Similar as withdrawERC721 but call from non-owner.
function test_withdrawERC721_unauthorized_reverts() external {
vm.prank(alice);
testERC721.transferFrom(alice, address(assetReceiver), DEFAULT_TOKEN_ID);
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
/* Testing utilities */
// Testing utilities
import { Test } from "forge-std/Test.sol";
import { Vm } from "forge-std/Vm.sol";
import "./CommonTest.t.sol";
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
// Testing utilities
......
//SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { CheckBalanceHigh } from "../periphery/drippie/dripchecks/CheckBalanceHigh.sol";
/**
* @title CheckBalanceHighTest
* @notice Tests the CheckBalanceHigh contract via fuzzing both the success case
* and the failure case.
*/
/// @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.
*/
/// @notice An instance of the CheckBalanceHigh contract.
CheckBalanceHigh c;
/**
* @notice Deploy the `CheckTrue` contract.
*/
/// @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.
*/
/// @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,
......@@ -39,10 +31,8 @@ contract CheckBalanceHighTest is Test {
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.
*/
/// @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_lowBalance_fails(address _target, uint256 _threshold) external {
CheckBalanceHigh.Params memory p = CheckBalanceHigh.Params({
target: _target,
......
//SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { CheckBalanceLow } from "../periphery/drippie/dripchecks/CheckBalanceLow.sol";
/**
* @title CheckBalanceLowTest
* @notice Tests the CheckBalanceLow contract via fuzzing both the success case
* and the failure case.
*/
/// @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.
*/
/// @notice An instance of the CheckBalanceLow contract.
CheckBalanceLow c;
/**
* @notice Deploy the `CheckBalanceLow` contract.
*/
/// @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.
*/
/// @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,
......@@ -37,10 +29,8 @@ contract CheckBalanceLowTest is Test {
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.
*/
/// @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_highBalance_fails(address _target, uint256 _threshold) external {
CheckBalanceLow.Params memory p = CheckBalanceLow.Params({
target: _target,
......
//SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
......@@ -7,11 +7,9 @@ import {
IGelatoTreasury
} from "../periphery/drippie/dripchecks/CheckGelatoLow.sol";
/**
* @title MockGelatoTreasury
* @notice Mocks the Gelato treasury for testing purposes. Allows arbitrary
* setting of user balances.
*/
/// @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;
......@@ -28,39 +26,27 @@ contract MockGelatoTreasury is IGelatoTreasury {
}
}
/**
* @title CheckGelatoLowTest
* @notice Tests the CheckBalanceHigh contract via fuzzing both the success case
* and the failure case.
*/
/// @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.
*/
/// @notice An instance of the CheckGelatoLow contract.
CheckGelatoLow c;
/**
* @notice An instance of the MockGelatoTreasury contract.
*/
/// @notice An instance of the MockGelatoTreasury contract.
MockGelatoTreasury gelato;
/**
* @notice The account Gelato uses to represent ether
*/
/// @notice The account Gelato uses to represent ether
address internal constant eth = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/**
* @notice Deploy the `CheckGelatoLow` and `MockGelatoTreasury` contracts.
*/
/// @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.
*/
/// @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),
......@@ -73,11 +59,9 @@ contract CheckGelatoLowTest is Test {
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.
*/
/// @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),
......
//SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { CheckTrue } from "../periphery/drippie/dripchecks/CheckTrue.sol";
/**
* @title CheckTrueTest
* @notice Ensures that the CheckTrue DripCheck contract always returns true.
*/
/// @title CheckTrueTest
/// @notice Ensures that the CheckTrue DripCheck contract always returns true.
contract CheckTrueTest is Test {
/**
* @notice An instance of the CheckTrue contract.
*/
/// @notice An instance of the CheckTrue contract.
CheckTrue c;
/**
* @notice Deploy the `CheckTrue` contract.
*/
/// @notice Deploy the `CheckTrue` contract.
function setUp() external {
c = new CheckTrue();
}
/**
* @notice Fuzz the `check` function and assert that it always returns true.
*/
/// @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.15;
/* Testing utilities */
// Testing utilities
import { Test, StdUtils } from "forge-std/Test.sol";
import { L2OutputOracle } from "../L1/L2OutputOracle.sol";
import { L2ToL1MessagePasser } from "../L2/L2ToL1MessagePasser.sol";
......@@ -766,9 +766,7 @@ contract ConfigurableCaller {
event WhatHappened(bool success, bytes returndata);
/**
* @notice Call the configured target with the configured payload OR revert.
*/
/// @notice Call the configured target with the configured payload OR revert.
function call() external {
if (doRevert) {
revert("ConfigurableCaller: revert");
......@@ -787,31 +785,23 @@ contract ConfigurableCaller {
}
}
/**
* @notice Set whether or not to have `call` revert.
*/
/// @notice Set whether or not to have `call` revert.
function setDoRevert(bool _doRevert) external {
doRevert = _doRevert;
}
/**
* @notice Set the target for the call made in `call`.
*/
/// @notice Set the target for the call made in `call`.
function setTarget(address _target) external {
target = _target;
}
/**
* @notice Set the payload for the call made in `call`.
*/
/// @notice Set the payload for the call made in `call`.
function setPayload(bytes calldata _payload) external {
payload = _payload;
}
/**
* @notice Fallback function that reverts if `doRevert` is true.
* Otherwise, it does nothing.
*/
/// @notice Fallback function that reverts if `doRevert` is true.
/// Otherwise, it does nothing.
fallback() external {
if (doRevert) {
revert("ConfigurableCaller: revert");
......
//SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
......@@ -45,9 +45,7 @@ contract Faucet_Initializer is Test {
_initializeContracts();
}
/**
* @notice Instantiates a Faucet.
*/
/// @notice Instantiates a Faucet.
function _initializeContracts() internal {
faucet = new Faucet(faucetContractAdmin);
......@@ -76,10 +74,7 @@ contract Faucet_Initializer is Test {
vm.stopPrank();
}
/**
* @notice Get signature as a bytes blob.
*
*/
/// @notice Get signature as a bytes blob.
function _getSignature(uint256 _signingPrivateKey, bytes32 _digest)
internal
pure
......@@ -91,11 +86,9 @@ contract Faucet_Initializer is Test {
return signature;
}
/**
* @notice Signs a proof with the given private key and returns the signature using
* the given EIP712 domain separator. This assumes that the issuer's address is the
* corresponding public key to _issuerPrivateKey.
*/
/// @notice Signs a proof with the given private key and returns the signature using
/// the given EIP712 domain separator. This assumes that the issuer's address is the
/// corresponding public key to _issuerPrivateKey.
function issueProofWithEIP712Domain(
uint256 _issuerPrivateKey,
bytes memory _eip712Name,
......
//SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
/* Testing utilities */
// Testing utilities
import { Test } from "forge-std/Test.sol";
import { AttestationStation } from "../periphery/op-nft/AttestationStation.sol";
import { OptimistAllowlist } from "../periphery/op-nft/OptimistAllowlist.sol";
......@@ -106,10 +106,7 @@ contract OptimistAllowlist_Initializer is Test {
optimistInviter.claimInvite(claimer, claimableInvite, signature);
}
/**
* @notice Get signature as a bytes blob, since SignatureChecker takes arbitrary signature blobs.
*
*/
/// @notice Get signature as a bytes blob, since SignatureChecker takes arbitrary signature blobs.
function _getSignature(uint256 _signingPrivateKey, bytes32 _digest)
internal
pure
......@@ -149,42 +146,32 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer {
assertEq(optimistAllowlist.version(), "1.0.0");
}
/**
* @notice Base case, a account without any relevant attestations should not be able to mint.
*/
/// @notice Base case, a account without any relevant attestations should not be able to mint.
function test_isAllowedToMint_withoutAnyAttestations_fails() external {
assertFalse(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice After receiving a valid allowlist attestation, the account should be able to mint.
*/
/// @notice After receiving a valid allowlist attestation, the account should be able to mint.
function test_isAllowedToMint_fromAllowlistAttestor_succeeds() external {
attestAllowlist(bob);
assertTrue(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice After receiving a valid attestation from the Coinbase Quest attestor,
* the account should be able to mint.
*/
/// @notice After receiving a valid attestation from the Coinbase Quest attestor,
/// the account should be able to mint.
function test_isAllowedToMint_fromCoinbaseQuestAttestor_succeeds() external {
attestCoinbaseQuest(bob);
assertTrue(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice Account that received an attestation from the OptimistInviter contract by going
* through the claim invite flow should be able to mint.
*/
/// @notice Account that received an attestation from the OptimistInviter contract by going
/// through the claim invite flow should be able to mint.
function test_isAllowedToMint_fromInvite_succeeds() external {
inviteAndClaim(bob);
assertTrue(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice Attestation from the wrong allowlist attestor should not allow minting.
*/
/// @notice Attestation from the wrong allowlist attestor should not allow minting.
function test_isAllowedToMint_fromWrongAllowlistAttestor_fails() external {
// Ted is not the allowlist attestor
vm.prank(ted);
......@@ -196,9 +183,7 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer {
assertFalse(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice Coinbase quest attestation from wrong attestor should not allow minting.
*/
/// @notice Coinbase quest attestation from wrong attestor should not allow minting.
function test_isAllowedToMint_fromWrongCoinbaseQuestAttestor_fails() external {
// Ted is not the coinbase quest attestor
vm.prank(ted);
......@@ -210,10 +195,8 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer {
assertFalse(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice Claiming an invite on the non-official OptimistInviter contract should not allow
* minting.
*/
/// @notice Claiming an invite on the non-official OptimistInviter contract should not allow
/// minting.
function test_isAllowedToMint_fromWrongOptimistInviter_fails() external {
vm.prank(ted);
attestationStation.attest(
......@@ -224,9 +207,7 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer {
assertFalse(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice Having multiple signals, even if one is invalid, should still allow minting.
*/
/// @notice Having multiple signals, even if one is invalid, should still allow minting.
function test_isAllowedToMint_withMultipleAttestations_succeeds() external {
attestAllowlist(bob);
attestCoinbaseQuest(bob);
......@@ -246,9 +227,7 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer {
assertTrue(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice Having falsy attestation value should not allow minting.
*/
/// @notice Having falsy attestation value should not allow minting.
function test_isAllowedToMint_fromAllowlistAttestorWithFalsyValue_fails() external {
// First sends correct attestation
attestAllowlist(bob);
......@@ -264,9 +243,7 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer {
assertFalse(optimistAllowlist.isAllowedToMint(bob));
}
/**
* @notice Having falsy attestation value from Coinbase attestor should not allow minting.
*/
/// @notice Having falsy attestation value from Coinbase attestor should not allow minting.
function test_isAllowedToMint_fromCoinbaseQuestAttestorWithFalsyValue_fails() external {
// First sends correct attestation
attestAllowlist(bob);
......
......@@ -3,10 +3,8 @@ pragma solidity ^0.8.0;
import { Bytes32AddressLib } from "@rari-capital/solmate/src/utils/Bytes32AddressLib.sol";
/**
* @title LibRLP
* @notice Via https://github.com/Rari-Capital/solmate/issues/207.
*/
/// @title LibRLP
/// @notice Via https://github.com/Rari-Capital/solmate/issues/207.
library LibRLP {
using Bytes32AddressLib for bytes32;
......
# `AddressAliasHelper` Invariants
## Address aliases are always able to be undone.
**Test:** [`AddressAliasHelper.t.sol#L48`](../contracts/test/invariants/AddressAliasHelper.t.sol#L48)
**Test:** [`AddressAliasHelper.t.sol#L46`](../contracts/test/invariants/AddressAliasHelper.t.sol#L46)
Asserts that an address that has been aliased with `applyL1ToL2Alias` can always be unaliased with `undoL1ToL2Alias`.
Asserts that an address that has been aliased with `applyL1ToL2Alias` can always be unaliased with `undoL1ToL2Alias`.
\ No newline at end of file
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