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 @@ ...@@ -25,4 +25,4 @@
"eslint.format.enable": true, "eslint.format.enable": true,
"editorconfig.generateAuto": false, "editorconfig.generateAuto": false,
"files.trimTrailingWhitespace": true "files.trimTrailingWhitespace": true
} }
\ No newline at end of file
...@@ -3,7 +3,6 @@ package op_challenger ...@@ -3,7 +3,6 @@ package op_challenger
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
...@@ -31,37 +30,20 @@ func Main(ctx context.Context, logger log.Logger, cfg *config.Config) error { ...@@ -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) return fmt.Errorf("failed to bind the fault dispute game contract: %w", err)
} }
loader := fault.NewLoader(logger, contract) gameLogger := logger.New("game", cfg.GameAddress)
responder, err := fault.NewFaultResponder(logger, txMgr, cfg.GameAddress) loader := fault.NewLoader(contract)
responder, err := fault.NewFaultResponder(gameLogger, txMgr, cfg.GameAddress)
if err != nil { if err != nil {
return fmt.Errorf("failed to create the responder: %w", err) return fmt.Errorf("failed to create the responder: %w", err)
} }
trace := fault.NewAlphabetProvider(cfg.AlphabetTrace, uint64(cfg.GameDepth)) 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 { if err != nil {
return fmt.Errorf("failed to bind the fault contract: %w", err) return fmt.Errorf("failed to bind the fault contract: %w", err)
} }
logger.Info("Fault game started") return fault.MonitorGame(ctx, gameLogger, cfg.AgreeWithProposedOutput, agent, caller)
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()
}
}
} }
...@@ -17,8 +17,8 @@ type Agent struct { ...@@ -17,8 +17,8 @@ type Agent struct {
log log.Logger log log.Logger
} }
func NewAgent(loader Loader, maxDepth int, trace TraceProvider, responder Responder, agreeWithProposedOutput bool, log log.Logger) Agent { func NewAgent(loader Loader, maxDepth int, trace TraceProvider, responder Responder, agreeWithProposedOutput bool, log log.Logger) *Agent {
return Agent{ return &Agent{
solver: NewSolver(maxDepth, trace), solver: NewSolver(maxDepth, trace),
loader: loader, loader: loader,
responder: responder, responder: responder,
...@@ -35,12 +35,11 @@ func (a *Agent) Act(ctx context.Context) error { ...@@ -35,12 +35,11 @@ func (a *Agent) Act(ctx context.Context) error {
} }
game, err := a.newGameFromContracts(ctx) game, err := a.newGameFromContracts(ctx)
if err != nil { if err != nil {
a.log.Error("Failed to create new game", "err", err) return fmt.Errorf("create game from contracts: %w", err)
return err
} }
// Create counter claims // Create counter claims
for _, claim := range game.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) log.Error("Failed to move", "err", err)
} }
} }
...@@ -57,11 +56,13 @@ func (a *Agent) Act(ctx context.Context) error { ...@@ -57,11 +56,13 @@ func (a *Agent) Act(ctx context.Context) error {
// and returns true if the game resolves successfully. // and returns true if the game resolves successfully.
func (a *Agent) tryResolve(ctx context.Context) bool { func (a *Agent) tryResolve(ctx context.Context) bool {
if a.responder.CanResolve(ctx) { if a.responder.CanResolve(ctx) {
a.log.Info("Resolving game")
err := a.responder.Resolve(ctx) err := a.responder.Resolve(ctx)
if err != nil { 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 return false
} }
...@@ -86,8 +87,7 @@ func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) { ...@@ -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 { func (a *Agent) move(ctx context.Context, claim Claim, game Game) error {
nextMove, err := a.solver.NextMove(claim, game.AgreeWithClaimLevel(claim)) nextMove, err := a.solver.NextMove(claim, game.AgreeWithClaimLevel(claim))
if err != nil { if err != nil {
a.log.Warn("Failed to execute the next move", "err", err) return fmt.Errorf("execute next move: %w", err)
return err
} }
if nextMove == nil { if nextMove == nil {
a.log.Debug("No next move") a.log.Debug("No next move")
...@@ -98,7 +98,7 @@ func (a *Agent) move(ctx context.Context, claim Claim, game Game) error { ...@@ -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), "value", move.Value, "trace_index", move.TraceIndex(a.maxDepth),
"parent_value", claim.Value, "parent_trace_index", claim.TraceIndex(a.maxDepth)) "parent_value", claim.Value, "parent_trace_index", claim.TraceIndex(a.maxDepth))
if game.IsDuplicate(move) { if game.IsDuplicate(move) {
log.Debug("Duplicate move") log.Debug("Skipping duplicate move")
return nil return nil
} }
log.Info("Performing move") log.Info("Performing move")
...@@ -113,20 +113,19 @@ func (a *Agent) step(ctx context.Context, claim Claim, game Game) error { ...@@ -113,20 +113,19 @@ func (a *Agent) step(ctx context.Context, claim Claim, game Game) error {
agreeWithClaimLevel := game.AgreeWithClaimLevel(claim) agreeWithClaimLevel := game.AgreeWithClaimLevel(claim)
if agreeWithClaimLevel { 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 return nil
} }
if claim.Countered { 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 return nil
} }
a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth) a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
step, err := a.solver.AttemptStep(claim, agreeWithClaimLevel) step, err := a.solver.AttemptStep(claim, agreeWithClaimLevel)
if err != nil { if err != nil {
a.log.Warn("Failed to get a step", "err", err) return fmt.Errorf("attempt step: %w", err)
return err
} }
a.log.Info("Performing step", "is_attack", step.IsAttack, a.log.Info("Performing step", "is_attack", step.IsAttack,
......
...@@ -19,15 +19,13 @@ type FaultDisputeGameCaller interface { ...@@ -19,15 +19,13 @@ type FaultDisputeGameCaller interface {
type FaultCaller struct { type FaultCaller struct {
FaultDisputeGameCaller FaultDisputeGameCaller
log log.Logger log log.Logger
fdgAddr common.Address
} }
func NewFaultCaller(fdgAddr common.Address, caller FaultDisputeGameCaller, log log.Logger) *FaultCaller { func NewFaultCaller(caller FaultDisputeGameCaller, log log.Logger) *FaultCaller {
return &FaultCaller{ return &FaultCaller{
caller, caller,
log, log,
fdgAddr,
} }
} }
...@@ -39,7 +37,6 @@ func NewFaultCallerFromBindings(fdgAddr common.Address, client *ethclient.Client ...@@ -39,7 +37,6 @@ func NewFaultCallerFromBindings(fdgAddr common.Address, client *ethclient.Client
return &FaultCaller{ return &FaultCaller{
caller, caller,
log, log,
fdgAddr,
}, nil }, nil
} }
...@@ -55,24 +52,16 @@ func (fc *FaultCaller) LogGameInfo(ctx context.Context) { ...@@ -55,24 +52,16 @@ func (fc *FaultCaller) LogGameInfo(ctx context.Context) {
fc.log.Error("failed to get claim count", "err", err) fc.log.Error("failed to get claim count", "err", err)
return 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. // GetGameStatus returns the current game status.
// 0: In Progress // 0: In Progress
// 1: Challenger Won // 1: Challenger Won
// 2: Defender Won // 2: Defender Won
func (fc *FaultCaller) GetGameStatus(ctx context.Context) (uint8, error) { func (fc *FaultCaller) GetGameStatus(ctx context.Context) (GameStatus, error) {
return fc.Status(&bind.CallOpts{Context: ctx}) status, err := fc.Status(&bind.CallOpts{Context: ctx})
} return GameStatus(status), err
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))
} }
// GetClaimDataLength returns the number of claims in the game. // GetClaimDataLength returns the number of claims in the game.
...@@ -90,13 +79,13 @@ func (fc *FaultCaller) LogClaimDataLength(ctx context.Context) { ...@@ -90,13 +79,13 @@ func (fc *FaultCaller) LogClaimDataLength(ctx context.Context) {
} }
// GameStatusString returns the current game status as a string. // GameStatusString returns the current game status as a string.
func GameStatusString(status uint8) string { func GameStatusString(status GameStatus) string {
switch status { switch status {
case 0: case GameStatusInProgress:
return "In Progress" return "In Progress"
case 1: case GameStatusChallengerWon:
return "Challenger Won" return "Challenger Won"
case 2: case GameStatusDefenderWon:
return "Defender Won" return "Defender Won"
default: default:
return "Unknown" return "Unknown"
......
...@@ -7,13 +7,11 @@ import ( ...@@ -7,13 +7,11 @@ import (
"testing" "testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var ( var (
testAddr = common.HexToAddress("0x1234567890123456789012345678901234567890") errMock = errors.New("mock error")
errMock = errors.New("mock error")
) )
type mockFaultDisputeGameCaller struct { type mockFaultDisputeGameCaller struct {
...@@ -42,7 +40,7 @@ func TestFaultCaller_GetGameStatus(t *testing.T) { ...@@ -42,7 +40,7 @@ func TestFaultCaller_GetGameStatus(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
caller FaultDisputeGameCaller caller FaultDisputeGameCaller
expectedStatus uint8 expectedStatus GameStatus
expectedErr error expectedErr error
}{ }{
{ {
...@@ -50,7 +48,7 @@ func TestFaultCaller_GetGameStatus(t *testing.T) { ...@@ -50,7 +48,7 @@ func TestFaultCaller_GetGameStatus(t *testing.T) {
caller: &mockFaultDisputeGameCaller{ caller: &mockFaultDisputeGameCaller{
status: 1, status: 1,
}, },
expectedStatus: 1, expectedStatus: GameStatusChallengerWon,
expectedErr: nil, expectedErr: nil,
}, },
{ {
...@@ -65,7 +63,7 @@ func TestFaultCaller_GetGameStatus(t *testing.T) { ...@@ -65,7 +63,7 @@ func TestFaultCaller_GetGameStatus(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { 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()) status, err := fc.GetGameStatus(context.Background())
require.Equal(t, test.expectedStatus, status) require.Equal(t, test.expectedStatus, status)
require.Equal(t, test.expectedErr, err) require.Equal(t, test.expectedErr, err)
...@@ -100,7 +98,7 @@ func TestFaultCaller_GetClaimDataLength(t *testing.T) { ...@@ -100,7 +98,7 @@ func TestFaultCaller_GetClaimDataLength(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { 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()) claimDataLen, err := fc.GetClaimDataLength(context.Background())
require.Equal(t, test.expectedClaimDataLen, claimDataLen) require.Equal(t, test.expectedClaimDataLen, claimDataLen)
require.Equal(t, test.expectedErr, err) require.Equal(t, test.expectedErr, err)
......
...@@ -5,7 +5,6 @@ import ( ...@@ -5,7 +5,6 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/log"
) )
// ClaimFetcher is a minimal interface around [bindings.FaultDisputeGameCaller]. // ClaimFetcher is a minimal interface around [bindings.FaultDisputeGameCaller].
...@@ -28,14 +27,12 @@ type Loader interface { ...@@ -28,14 +27,12 @@ type Loader interface {
// loader pulls in fault dispute game claim data periodically and over subscriptions. // loader pulls in fault dispute game claim data periodically and over subscriptions.
type loader struct { type loader struct {
log log.Logger
claimFetcher ClaimFetcher claimFetcher ClaimFetcher
} }
// NewLoader creates a new [loader]. // NewLoader creates a new [loader].
func NewLoader(log log.Logger, claimFetcher ClaimFetcher) *loader { func NewLoader(claimFetcher ClaimFetcher) *loader {
return &loader{ return &loader{
log: log,
claimFetcher: claimFetcher, claimFetcher: claimFetcher,
} }
} }
......
...@@ -6,9 +6,7 @@ import ( ...@@ -6,9 +6,7 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -91,10 +89,9 @@ func (m *mockClaimFetcher) ClaimDataLen(opts *bind.CallOpts) (*big.Int, error) { ...@@ -91,10 +89,9 @@ func (m *mockClaimFetcher) ClaimDataLen(opts *bind.CallOpts) (*big.Int, error) {
// TestLoader_FetchClaims_Succeeds tests [loader.FetchClaims]. // TestLoader_FetchClaims_Succeeds tests [loader.FetchClaims].
func TestLoader_FetchClaims_Succeeds(t *testing.T) { func TestLoader_FetchClaims_Succeeds(t *testing.T) {
log := testlog.Logger(t, log.LvlError)
mockClaimFetcher := newMockClaimFetcher() mockClaimFetcher := newMockClaimFetcher()
expectedClaims := mockClaimFetcher.returnClaims expectedClaims := mockClaimFetcher.returnClaims
loader := NewLoader(log, mockClaimFetcher) loader := NewLoader(mockClaimFetcher)
claims, err := loader.FetchClaims(context.Background()) claims, err := loader.FetchClaims(context.Background())
require.NoError(t, err) require.NoError(t, err)
require.ElementsMatch(t, []Claim{ require.ElementsMatch(t, []Claim{
...@@ -143,10 +140,9 @@ func TestLoader_FetchClaims_Succeeds(t *testing.T) { ...@@ -143,10 +140,9 @@ func TestLoader_FetchClaims_Succeeds(t *testing.T) {
// TestLoader_FetchClaims_ClaimDataErrors tests [loader.FetchClaims] // TestLoader_FetchClaims_ClaimDataErrors tests [loader.FetchClaims]
// when the claim fetcher [ClaimData] function call errors. // when the claim fetcher [ClaimData] function call errors.
func TestLoader_FetchClaims_ClaimDataErrors(t *testing.T) { func TestLoader_FetchClaims_ClaimDataErrors(t *testing.T) {
log := testlog.Logger(t, log.LvlError)
mockClaimFetcher := newMockClaimFetcher() mockClaimFetcher := newMockClaimFetcher()
mockClaimFetcher.claimDataError = true mockClaimFetcher.claimDataError = true
loader := NewLoader(log, mockClaimFetcher) loader := NewLoader(mockClaimFetcher)
claims, err := loader.FetchClaims(context.Background()) claims, err := loader.FetchClaims(context.Background())
require.ErrorIs(t, err, mockClaimDataError) require.ErrorIs(t, err, mockClaimDataError)
require.Empty(t, claims) require.Empty(t, claims)
...@@ -155,10 +151,9 @@ func TestLoader_FetchClaims_ClaimDataErrors(t *testing.T) { ...@@ -155,10 +151,9 @@ func TestLoader_FetchClaims_ClaimDataErrors(t *testing.T) {
// TestLoader_FetchClaims_ClaimLenErrors tests [loader.FetchClaims] // TestLoader_FetchClaims_ClaimLenErrors tests [loader.FetchClaims]
// when the claim fetcher [ClaimDataLen] function call errors. // when the claim fetcher [ClaimDataLen] function call errors.
func TestLoader_FetchClaims_ClaimLenErrors(t *testing.T) { func TestLoader_FetchClaims_ClaimLenErrors(t *testing.T) {
log := testlog.Logger(t, log.LvlError)
mockClaimFetcher := newMockClaimFetcher() mockClaimFetcher := newMockClaimFetcher()
mockClaimFetcher.claimLenError = true mockClaimFetcher.claimLenError = true
loader := NewLoader(log, mockClaimFetcher) loader := NewLoader(mockClaimFetcher)
claims, err := loader.FetchClaims(context.Background()) claims, err := loader.FetchClaims(context.Background())
require.ErrorIs(t, err, mockClaimLenError) require.ErrorIs(t, err, mockClaimLenError)
require.Empty(t, claims) 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 ...@@ -123,9 +123,9 @@ func (r *faultResponder) sendTxAndWait(ctx context.Context, txData []byte) error
return err return err
} }
if receipt.Status == types.ReceiptStatusFailed { 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 { } 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 return nil
} }
......
...@@ -2,10 +2,15 @@ package fault ...@@ -2,10 +2,15 @@ package fault
import ( import (
"errors" "errors"
"fmt"
"github.com/ethereum/go-ethereum/common" "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. // Solver uses a [TraceProvider] to determine the moves to make in a dispute game.
type Solver struct { type Solver struct {
TraceProvider TraceProvider
...@@ -54,7 +59,7 @@ func (s *Solver) handleMiddle(claim Claim) (*Claim, error) { ...@@ -54,7 +59,7 @@ func (s *Solver) handleMiddle(claim Claim) (*Claim, error) {
return nil, err return nil, err
} }
if claim.Depth() == s.gameDepth { if claim.Depth() == s.gameDepth {
return nil, errors.New("game depth reached") return nil, ErrGameDepthReached
} }
if claimCorrect { if claimCorrect {
return s.defend(claim) return s.defend(claim)
...@@ -113,7 +118,7 @@ func (s *Solver) attack(claim Claim) (*Claim, error) { ...@@ -113,7 +118,7 @@ func (s *Solver) attack(claim Claim) (*Claim, error) {
position := claim.Attack() position := claim.Attack()
value, err := s.traceAtPosition(position) value, err := s.traceAtPosition(position)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("attack claim: %w", err)
} }
return &Claim{ return &Claim{
ClaimData: ClaimData{Value: value, Position: position}, ClaimData: ClaimData{Value: value, Position: position},
...@@ -127,7 +132,7 @@ func (s *Solver) defend(claim Claim) (*Claim, error) { ...@@ -127,7 +132,7 @@ func (s *Solver) defend(claim Claim) (*Claim, error) {
position := claim.Defend() position := claim.Defend()
value, err := s.traceAtPosition(position) value, err := s.traceAtPosition(position)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("defend claim: %w", err)
} }
return &Claim{ return &Claim{
ClaimData: ClaimData{Value: value, Position: position}, ClaimData: ClaimData{Value: value, Position: position},
......
...@@ -11,6 +11,14 @@ var ( ...@@ -11,6 +11,14 @@ var (
ErrIndexTooLarge = errors.New("index is larger than the maximum index") 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. // StepCallData encapsulates the data needed to perform a step.
type StepCallData struct { type StepCallData struct {
ClaimIndex uint64 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) (* ...@@ -209,7 +209,7 @@ func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (*
Data: candidate.TxData, 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 the gas limit is set, we can use that as the gas
if candidate.GasLimit != 0 { if candidate.GasLimit != 0 {
...@@ -333,7 +333,7 @@ func (m *SimpleTxManager) sendTx(ctx context.Context, tx *types.Transaction) (*t ...@@ -333,7 +333,7 @@ func (m *SimpleTxManager) sendTx(ctx context.Context, tx *types.Transaction) (*t
// for the transaction. // for the transaction.
func (m *SimpleTxManager) publishAndWaitForTx(ctx context.Context, tx *types.Transaction, sendState *SendState, receiptChan chan *types.Receipt) { 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 := 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) cCtx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout)
defer cancel() defer cancel()
......
...@@ -103,6 +103,7 @@ export class WithdrawalMonitor extends BaseServiceV2<Options, Metrics, State> { ...@@ -103,6 +103,7 @@ export class WithdrawalMonitor extends BaseServiceV2<Options, Metrics, State> {
l2SignerOrProvider: this.options.l2RpcProvider, l2SignerOrProvider: this.options.l2RpcProvider,
l1ChainId: await getChainId(this.options.l1RpcProvider), l1ChainId: await getChainId(this.options.l1RpcProvider),
l2ChainId: await getChainId(this.options.l2RpcProvider), l2ChainId: await getChainId(this.options.l2RpcProvider),
bedrock: true,
}) })
// Not detected by default. // 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_nonAdminProof_verify_succeeds() (gas: 59050)
AdminFaucetAuthModuleTest:test_proofWithWrongId_verify_succeeds() (gas: 60673) AdminFaucetAuthModuleTest:test_proofWithWrongId_verify_succeeds() (gas: 60673)
AssetReceiverTest:test_constructor_succeeds() (gas: 9696) AssetReceiverTest:test_constructor_succeeds() (gas: 9696)
...@@ -70,14 +70,14 @@ Drippie_Test:test_status_unauthorized_reverts() (gas: 167344) ...@@ -70,14 +70,14 @@ Drippie_Test:test_status_unauthorized_reverts() (gas: 167344)
Drippie_Test:test_trigger_oneFunction_succeeds() (gas: 338143) Drippie_Test:test_trigger_oneFunction_succeeds() (gas: 338143)
Drippie_Test:test_trigger_twoFunctions_succeeds() (gas: 491870) Drippie_Test:test_trigger_twoFunctions_succeeds() (gas: 491870)
Drippie_Test:test_twice_inOneInterval_reverts() (gas: 303767) Drippie_Test:test_twice_inOneInterval_reverts() (gas: 303767)
FaucetTest:test_authAdmin_drip_succeeds() (gas: 366111) FaucetTest:test_authAdmin_drip_succeeds() (gas: 366107)
FaucetTest:test_drip_afterTimeout_succeeds() (gas: 447899) FaucetTest:test_drip_afterTimeout_succeeds() (gas: 447891)
FaucetTest:test_drip_beforeTimeout_reverts() (gas: 378888) FaucetTest:test_drip_beforeTimeout_reverts() (gas: 378884)
FaucetTest:test_drip_disabledModule_reverts() (gas: 352405) FaucetTest:test_drip_disabledModule_reverts() (gas: 352401)
FaucetTest:test_drip_emitsEvent_succeeds() (gas: 369165) FaucetTest:test_drip_emitsEvent_succeeds() (gas: 369161)
FaucetTest:test_drip_githubSendsCorrectAmount_succeeds() (gas: 366611) FaucetTest:test_drip_githubSendsCorrectAmount_succeeds() (gas: 366607)
FaucetTest:test_drip_optimistNftSendsCorrectAmount_succeeds() (gas: 366555) FaucetTest:test_drip_optimistNftSendsCorrectAmount_succeeds() (gas: 366551)
FaucetTest:test_drip_preventsReplayAttacks_succeeds() (gas: 369218) FaucetTest:test_drip_preventsReplayAttacks_succeeds() (gas: 369214)
FaucetTest:test_initialize_succeeds() (gas: 7626) FaucetTest:test_initialize_succeeds() (gas: 7626)
FaucetTest:test_nonAdmin_drip_fails() (gas: 262520) FaucetTest:test_nonAdmin_drip_fails() (gas: 262520)
FaucetTest:test_receive_succeeds() (gas: 17401) FaucetTest:test_receive_succeeds() (gas: 17401)
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
// A representation of an empty/uninitialized UID. // A representation of an empty/uninitialized UID.
...@@ -14,18 +13,14 @@ error InvalidLength(); ...@@ -14,18 +13,14 @@ error InvalidLength();
error InvalidSignature(); error InvalidSignature();
error NotFound(); error NotFound();
/** /// @dev A struct representing EIP712 signature data.
* @dev A struct representing EIP712 signature data.
*/
struct EIP712Signature { struct EIP712Signature {
uint8 v; // The recovery ID. uint8 v; // The recovery ID.
bytes32 r; // The x-coordinate of the nonce R. bytes32 r; // The x-coordinate of the nonce R.
bytes32 s; // The signature data. bytes32 s; // The signature data.
} }
/** /// @dev A struct representing a single attestation.
* @dev A struct representing a single attestation.
*/
struct Attestation { struct Attestation {
bytes32 uid; // A unique identifier of the attestation. bytes32 uid; // A unique identifier of the attestation.
bytes32 schema; // The unique identifier of the schema. bytes32 schema; // The unique identifier of the schema.
...@@ -42,26 +37,18 @@ struct Attestation { ...@@ -42,26 +37,18 @@ struct Attestation {
// Maximum upgrade forward-compatibility storage gap. // Maximum upgrade forward-compatibility storage gap.
uint32 constant MAX_GAP = 50; uint32 constant MAX_GAP = 50;
/** /// @dev A helper function to work with unchecked iterators in loops.
* @dev A helper function to work with unchecked iterators in loops. /// @param i The index to increment.
* /// @return j The incremented index.
* @param i The index to increment.
*
* @return j The incremented index.
*/
function uncheckedInc(uint256 i) pure returns (uint256 j) { function uncheckedInc(uint256 i) pure returns (uint256 j) {
unchecked { unchecked {
j = i + 1; j = i + 1;
} }
} }
/** /// @dev A helper function that converts a string to a bytes32.
* @dev A helper function that converts a string to a bytes32. /// @param str The string to convert.
* /// @return The converted bytes32.
* @param str The string to convert.
*
* @return The converted bytes32.
*/
function stringToBytes32(string memory str) pure returns (bytes32) { function stringToBytes32(string memory str) pure returns (bytes32) {
bytes32 result; bytes32 result;
...@@ -72,13 +59,9 @@ function stringToBytes32(string memory str) pure returns (bytes32) { ...@@ -72,13 +59,9 @@ function stringToBytes32(string memory str) pure returns (bytes32) {
return result; return result;
} }
/** /// @dev A helper function that converts a bytes32 to a string.
* @dev A helper function that converts a bytes32 to a string. /// @param data The bytes32 data to convert.
* /// @return The converted string.
* @param data The bytes32 data to convert.
*
* @return The converted string.
*/
function bytes32ToString(bytes32 data) pure returns (string memory) { function bytes32ToString(bytes32 data) pure returns (string memory) {
bytes memory byteArray = new bytes(32); bytes memory byteArray = new bytes(32);
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.19; pragma solidity 0.8.19;
import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol";
...@@ -85,32 +84,23 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -85,32 +84,23 @@ contract EAS is IEAS, Semver, EIP712Verifier {
// Upgrade forward-compatibility storage gap // Upgrade forward-compatibility storage gap
uint256[MAX_GAP - 3] private __gap; uint256[MAX_GAP - 3] private __gap;
/** /// @dev Creates a new EAS instance.
* @dev Creates a new EAS instance.
*/
constructor() Semver(1, 0, 0) EIP712Verifier("EAS", "1.0.0") { constructor() Semver(1, 0, 0) EIP712Verifier("EAS", "1.0.0") {
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function getSchemaRegistry() external pure returns (ISchemaRegistry) { function getSchemaRegistry() external pure returns (ISchemaRegistry) {
return _schemaRegistry; return _schemaRegistry;
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function attest(AttestationRequest calldata request) external payable returns (bytes32) { function attest(AttestationRequest calldata request) external payable returns (bytes32) {
AttestationRequestData[] memory requests = new AttestationRequestData[](1); AttestationRequestData[] memory requests = new AttestationRequestData[](1);
requests[0] = request.data; requests[0] = request.data;
return _attest(request.schema, requests, msg.sender, msg.value, true).uids[0]; return _attest(request.schema, requests, msg.sender, msg.value, true).uids[0];
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function attestByDelegation( function attestByDelegation(
DelegatedAttestationRequest calldata delegatedRequest DelegatedAttestationRequest calldata delegatedRequest
) external payable returns (bytes32) { ) external payable returns (bytes32) {
...@@ -118,13 +108,10 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -118,13 +108,10 @@ contract EAS is IEAS, Semver, EIP712Verifier {
AttestationRequestData[] memory data = new AttestationRequestData[](1); AttestationRequestData[] memory data = new AttestationRequestData[](1);
data[0] = delegatedRequest.data; data[0] = delegatedRequest.data;
return _attest(delegatedRequest.schema, data, delegatedRequest.attester, msg.value, true).uids[0]; return _attest(delegatedRequest.schema, data, delegatedRequest.attester, msg.value, true).uids[0];
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function multiAttest(MultiAttestationRequest[] calldata multiRequests) external payable returns (bytes32[] memory) { function multiAttest(MultiAttestationRequest[] calldata multiRequests) external payable returns (bytes32[] memory) {
// Since a multi-attest call is going to make multiple attestations for multiple schemas, we'd need to collect // Since a multi-attest call is going to make multiple attestations for multiple schemas, we'd need to collect
// all the returned UIDs into a single list. // all the returned UIDs into a single list.
...@@ -170,9 +157,7 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -170,9 +157,7 @@ contract EAS is IEAS, Semver, EIP712Verifier {
return _mergeUIDs(totalUids, totalUidsCount); return _mergeUIDs(totalUids, totalUidsCount);
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function multiAttestByDelegation( function multiAttestByDelegation(
MultiDelegatedAttestationRequest[] calldata multiDelegatedRequests MultiDelegatedAttestationRequest[] calldata multiDelegatedRequests
) external payable returns (bytes32[] memory) { ) external payable returns (bytes32[] memory) {
...@@ -239,9 +224,7 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -239,9 +224,7 @@ contract EAS is IEAS, Semver, EIP712Verifier {
return _mergeUIDs(totalUids, totalUidsCount); return _mergeUIDs(totalUids, totalUidsCount);
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function revoke(RevocationRequest calldata request) external payable { function revoke(RevocationRequest calldata request) external payable {
RevocationRequestData[] memory requests = new RevocationRequestData[](1); RevocationRequestData[] memory requests = new RevocationRequestData[](1);
requests[0] = request.data; requests[0] = request.data;
...@@ -249,9 +232,7 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -249,9 +232,7 @@ contract EAS is IEAS, Semver, EIP712Verifier {
_revoke(request.schema, requests, msg.sender, msg.value, true); _revoke(request.schema, requests, msg.sender, msg.value, true);
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function revokeByDelegation(DelegatedRevocationRequest calldata delegatedRequest) external payable { function revokeByDelegation(DelegatedRevocationRequest calldata delegatedRequest) external payable {
_verifyRevoke(delegatedRequest); _verifyRevoke(delegatedRequest);
...@@ -261,9 +242,7 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -261,9 +242,7 @@ contract EAS is IEAS, Semver, EIP712Verifier {
_revoke(delegatedRequest.schema, data, delegatedRequest.revoker, msg.value, true); _revoke(delegatedRequest.schema, data, delegatedRequest.revoker, msg.value, true);
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function multiRevoke(MultiRevocationRequest[] calldata multiRequests) external payable { function multiRevoke(MultiRevocationRequest[] calldata multiRequests) external payable {
// We are keeping track of the total available ETH amount that can be sent to resolvers and will keep deducting // We are keeping track of the total available ETH amount that can be sent to resolvers and will keep deducting
// from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless // from it to verify that there isn't any attempt to send too much ETH to resolvers. Please note that unless
...@@ -287,9 +266,7 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -287,9 +266,7 @@ contract EAS is IEAS, Semver, EIP712Verifier {
} }
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function multiRevokeByDelegation( function multiRevokeByDelegation(
MultiDelegatedRevocationRequest[] calldata multiDelegatedRequests MultiDelegatedRevocationRequest[] calldata multiDelegatedRequests
) external payable { ) external payable {
...@@ -339,31 +316,21 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -339,31 +316,21 @@ contract EAS is IEAS, Semver, EIP712Verifier {
} }
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function timestamp(bytes32 data) external returns (uint64) { function timestamp(bytes32 data) external returns (uint64) {
uint64 time = _time(); uint64 time = _time();
_timestamp(data, time); _timestamp(data, time);
return time; return time;
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function revokeOffchain(bytes32 data) external returns (uint64) { function revokeOffchain(bytes32 data) external returns (uint64) {
uint64 time = _time(); uint64 time = _time();
_revokeOffchain(msg.sender, data, time); _revokeOffchain(msg.sender, data, time);
return time; return time;
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function multiRevokeOffchain(bytes32[] calldata data) external returns (uint64) { function multiRevokeOffchain(bytes32[] calldata data) external returns (uint64) {
uint64 time = _time(); uint64 time = _time();
...@@ -375,9 +342,7 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -375,9 +342,7 @@ contract EAS is IEAS, Semver, EIP712Verifier {
return time; return time;
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function multiTimestamp(bytes32[] calldata data) external returns (uint64) { function multiTimestamp(bytes32[] calldata data) external returns (uint64) {
uint64 time = _time(); uint64 time = _time();
...@@ -389,45 +354,33 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -389,45 +354,33 @@ contract EAS is IEAS, Semver, EIP712Verifier {
return time; return time;
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function getAttestation(bytes32 uid) external view returns (Attestation memory) { function getAttestation(bytes32 uid) external view returns (Attestation memory) {
return _db[uid]; return _db[uid];
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function isAttestationValid(bytes32 uid) public view returns (bool) { function isAttestationValid(bytes32 uid) public view returns (bool) {
return _db[uid].uid != 0; return _db[uid].uid != 0;
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function getTimestamp(bytes32 data) external view returns (uint64) { function getTimestamp(bytes32 data) external view returns (uint64) {
return _timestamps[data]; return _timestamps[data];
} }
/** /// @inheritdoc IEAS
* @inheritdoc IEAS
*/
function getRevokeOffchain(address revoker, bytes32 data) external view returns (uint64) { function getRevokeOffchain(address revoker, bytes32 data) external view returns (uint64) {
return _revocationsOffchain[revoker][data]; return _revocationsOffchain[revoker][data];
} }
/** /// @dev Attests to a specific schema.
* @dev Attests to a specific schema. /// @param schema // the unique identifier of the schema to attest to.
* /// @param data The arguments of the attestation requests.
* @param schema // the unique identifier of the schema to attest to. /// @param attester The attesting account.
* @param data The arguments of the attestation requests. /// @param availableValue The total available ETH amount that can be sent to the resolver.
* @param attester The attesting account. /// @param last Whether this is the last attestations/revocations set.
* @param availableValue The total available ETH amount that can be sent to the resolver. /// @return The UID of the new attestations and the total sent ETH amount.
* @param last Whether this is the last attestations/revocations set.
*
* @return The UID of the new attestations and the total sent ETH amount.
*/
function _attest( function _attest(
bytes32 schema, bytes32 schema,
AttestationRequestData[] memory data, AttestationRequestData[] memory data,
...@@ -512,17 +465,13 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -512,17 +465,13 @@ contract EAS is IEAS, Semver, EIP712Verifier {
return res; return res;
} }
/** /// @dev Revokes an existing attestation to a specific schema.
* @dev Revokes an existing attestation to a specific schema. /// @param schema The unique identifier of the schema to attest to.
* /// @param data The arguments of the revocation requests.
* @param schema The unique identifier of the schema to attest to. /// @param revoker The revoking account.
* @param data The arguments of the revocation requests. /// @param availableValue The total available ETH amount that can be sent to the resolver.
* @param revoker The revoking account. /// @param last Whether this is the last attestations/revocations set.
* @param availableValue The total available ETH amount that can be sent to the resolver. /// @return Returns the total sent ETH amount.
* @param last Whether this is the last attestations/revocations set.
*
* @return Returns the total sent ETH amount.
*/
function _revoke( function _revoke(
bytes32 schema, bytes32 schema,
RevocationRequestData[] memory data, RevocationRequestData[] memory data,
...@@ -581,18 +530,14 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -581,18 +530,14 @@ contract EAS is IEAS, Semver, EIP712Verifier {
return _resolveAttestations(schemaRecord, attestations, values, true, availableValue, last); return _resolveAttestations(schemaRecord, attestations, values, true, availableValue, last);
} }
/** /// @dev Resolves a new attestation or a revocation of an existing attestation.
* @dev Resolves a new attestation or a revocation of an existing attestation. /// @param schemaRecord The schema of the attestation.
* /// @param attestation The data of the attestation to make/revoke.
* @param schemaRecord The schema of the attestation. /// @param value An explicit ETH amount to send to the resolver.
* @param attestation The data of the attestation to make/revoke. /// @param isRevocation Whether to resolve an attestation or its revocation.
* @param value An explicit ETH amount to send to the resolver. /// @param availableValue The total available ETH amount that can be sent to the resolver.
* @param isRevocation Whether to resolve an attestation or its revocation. /// @param last Whether this is the last attestations/revocations set.
* @param availableValue The total available ETH amount that can be sent to the resolver. /// @return Returns the total sent ETH amount.
* @param last Whether this is the last attestations/revocations set.
*
* @return Returns the total sent ETH amount.
*/
function _resolveAttestation( function _resolveAttestation(
SchemaRecord memory schemaRecord, SchemaRecord memory schemaRecord,
Attestation memory attestation, Attestation memory attestation,
...@@ -641,18 +586,14 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -641,18 +586,14 @@ contract EAS is IEAS, Semver, EIP712Verifier {
return value; return value;
} }
/** /// @dev Resolves multiple attestations or revocations of existing attestations.
* @dev Resolves multiple attestations or revocations of existing attestations. /// @param schemaRecord The schema of the attestation.
* /// @param attestations The data of the attestations to make/revoke.
* @param schemaRecord The schema of the attestation. /// @param values Explicit ETH amounts to send to the resolver.
* @param attestations The data of the attestations to make/revoke. /// @param isRevocation Whether to resolve an attestation or its revocation.
* @param values Explicit ETH amounts to send to the resolver. /// @param availableValue The total available ETH amount that can be sent to the resolver.
* @param isRevocation Whether to resolve an attestation or its revocation. /// @param last Whether this is the last attestations/revocations set.
* @param availableValue The total available ETH amount that can be sent to the resolver. /// @return Returns the total sent ETH amount.
* @param last Whether this is the last attestations/revocations set.
*
* @return Returns the total sent ETH amount.
*/
function _resolveAttestations( function _resolveAttestations(
SchemaRecord memory schemaRecord, SchemaRecord memory schemaRecord,
Attestation[] memory attestations, Attestation[] memory attestations,
...@@ -715,14 +656,10 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -715,14 +656,10 @@ contract EAS is IEAS, Semver, EIP712Verifier {
return totalUsedValue; return totalUsedValue;
} }
/** /// @dev Calculates a UID for a given attestation.
* @dev Calculates a UID for a given attestation. /// @param attestation The input attestation.
* /// @param bump A bump value to use in case of a UID conflict.
* @param attestation The input attestation. /// @return Attestation UID.
* @param bump A bump value to use in case of a UID conflict.
*
* @return Attestation UID.
*/
function _getUID(Attestation memory attestation, uint32 bump) private pure returns (bytes32) { function _getUID(Attestation memory attestation, uint32 bump) private pure returns (bytes32) {
return return
keccak256( keccak256(
...@@ -740,11 +677,8 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -740,11 +677,8 @@ contract EAS is IEAS, Semver, EIP712Verifier {
); );
} }
/** /// @dev Refunds remaining ETH amount to the attester.
* @dev Refunds remaining ETH amount to the attester. /// @param remainingValue The remaining ETH amount that was not sent to the resolver.
*
* @param remainingValue The remaining ETH amount that was not sent to the resolver.
*/
function _refund(uint256 remainingValue) private { function _refund(uint256 remainingValue) private {
if (remainingValue > 0) { if (remainingValue > 0) {
// Using a regular transfer here might revert, for some non-EOA attesters, due to exceeding of the 2300 // Using a regular transfer here might revert, for some non-EOA attesters, due to exceeding of the 2300
...@@ -754,12 +688,9 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -754,12 +688,9 @@ contract EAS is IEAS, Semver, EIP712Verifier {
} }
} }
/** /// @dev Timestamps the specified bytes32 data.
* @dev Timestamps the specified bytes32 data. /// @param data The data to timestamp.
* /// @param time The timestamp.
* @param data The data to timestamp.
* @param time The timestamp.
*/
function _timestamp(bytes32 data, uint64 time) private { function _timestamp(bytes32 data, uint64 time) private {
if (_timestamps[data] != 0) { if (_timestamps[data] != 0) {
revert AlreadyTimestamped(); revert AlreadyTimestamped();
...@@ -770,12 +701,9 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -770,12 +701,9 @@ contract EAS is IEAS, Semver, EIP712Verifier {
emit Timestamped(data, time); emit Timestamped(data, time);
} }
/** /// @dev Timestamps the specified bytes32 data.
* @dev Timestamps the specified bytes32 data. /// @param data The data to timestamp.
* /// @param time The timestamp.
* @param data The data to timestamp.
* @param time The timestamp.
*/
function _revokeOffchain(address revoker, bytes32 data, uint64 time) private { function _revokeOffchain(address revoker, bytes32 data, uint64 time) private {
mapping(bytes32 => uint64) storage revocations = _revocationsOffchain[revoker]; mapping(bytes32 => uint64) storage revocations = _revocationsOffchain[revoker];
...@@ -788,22 +716,17 @@ contract EAS is IEAS, Semver, EIP712Verifier { ...@@ -788,22 +716,17 @@ contract EAS is IEAS, Semver, EIP712Verifier {
emit RevokedOffchain(revoker, data, time); emit RevokedOffchain(revoker, data, time);
} }
/** /// @dev Returns the current's block timestamp.
* @dev Returns the current's block timestamp. This method is overridden during tests and used to simulate the /// This method is overridden during tests and
* current block time. /// used to simulate the current block time.
*/
function _time() internal view virtual returns (uint64) { function _time() internal view virtual returns (uint64) {
return uint64(block.timestamp); return uint64(block.timestamp);
} }
/** /// @dev Merges lists of UIDs.
* @dev Merges lists of UIDs. /// @param uidLists The provided lists of UIDs.
* /// @param uidsCount Total UIDs count.
* @param uidLists The provided lists of UIDs. /// @return A merged and flatten list of all the UIDs.
* @param uidsCount Total UIDs count.
*
* @return A merged and flatten list of all the UIDs.
*/
function _mergeUIDs(bytes32[][] memory uidLists, uint256 uidsCount) private pure returns (bytes32[] memory) { function _mergeUIDs(bytes32[][] memory uidLists, uint256 uidsCount) private pure returns (bytes32[] memory) {
bytes32[] memory uids = new bytes32[](uidsCount); bytes32[] memory uids = new bytes32[](uidsCount);
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import { ISchemaRegistry } from "./ISchemaRegistry.sol"; import { ISchemaRegistry } from "./ISchemaRegistry.sol";
import { Attestation, EIP712Signature } from "./Common.sol"; import { Attestation, EIP712Signature } from "./Common.sol";
/** /// @dev A struct representing the arguments of the attestation request.
* @dev A struct representing the arguments of the attestation request.
*/
struct AttestationRequestData { struct AttestationRequestData {
address recipient; // The recipient of the attestation. address recipient; // The recipient of the attestation.
uint64 expirationTime; // The time when the attestation expires (Unix timestamp). uint64 expirationTime; // The time when the attestation expires (Unix timestamp).
...@@ -17,17 +14,13 @@ struct AttestationRequestData { ...@@ -17,17 +14,13 @@ struct AttestationRequestData {
uint256 value; // An explicit ETH amount to send to the resolver. This is important to prevent accidental user errors. uint256 value; // An explicit ETH amount to send to the resolver. This is important to prevent accidental user errors.
} }
/** /// @dev A struct representing the full arguments of the attestation request.
* @dev A struct representing the full arguments of the attestation request.
*/
struct AttestationRequest { struct AttestationRequest {
bytes32 schema; // The unique identifier of the schema. bytes32 schema; // The unique identifier of the schema.
AttestationRequestData data; // The arguments of the attestation request. AttestationRequestData data; // The arguments of the attestation request.
} }
/** /// @dev A struct representing the full arguments of the full delegated attestation request.
* @dev A struct representing the full arguments of the full delegated attestation request.
*/
struct DelegatedAttestationRequest { struct DelegatedAttestationRequest {
bytes32 schema; // The unique identifier of the schema. bytes32 schema; // The unique identifier of the schema.
AttestationRequestData data; // The arguments of the attestation request. AttestationRequestData data; // The arguments of the attestation request.
...@@ -35,17 +28,13 @@ struct DelegatedAttestationRequest { ...@@ -35,17 +28,13 @@ struct DelegatedAttestationRequest {
address attester; // The attesting account. address attester; // The attesting account.
} }
/** /// @dev A struct representing the full arguments of the multi attestation request.
* @dev A struct representing the full arguments of the multi attestation request.
*/
struct MultiAttestationRequest { struct MultiAttestationRequest {
bytes32 schema; // The unique identifier of the schema. bytes32 schema; // The unique identifier of the schema.
AttestationRequestData[] data; // The arguments of the attestation request. AttestationRequestData[] data; // The arguments of the attestation request.
} }
/** /// @dev A struct representing the full arguments of the delegated multi attestation request.
* @dev A struct representing the full arguments of the delegated multi attestation request.
*/
struct MultiDelegatedAttestationRequest { struct MultiDelegatedAttestationRequest {
bytes32 schema; // The unique identifier of the schema. bytes32 schema; // The unique identifier of the schema.
AttestationRequestData[] data; // The arguments of the attestation requests. AttestationRequestData[] data; // The arguments of the attestation requests.
...@@ -53,25 +42,19 @@ struct MultiDelegatedAttestationRequest { ...@@ -53,25 +42,19 @@ struct MultiDelegatedAttestationRequest {
address attester; // The attesting account. address attester; // The attesting account.
} }
/** /// @dev A struct representing the arguments of the revocation request.
* @dev A struct representing the arguments of the revocation request.
*/
struct RevocationRequestData { struct RevocationRequestData {
bytes32 uid; // The UID of the attestation to revoke. bytes32 uid; // The UID of the attestation to revoke.
uint256 value; // An explicit ETH amount to send to the resolver. This is important to prevent accidental user errors. uint256 value; // An explicit ETH amount to send to the resolver. This is important to prevent accidental user errors.
} }
/** /// @dev A struct representing the full arguments of the revocation request.
* @dev A struct representing the full arguments of the revocation request.
*/
struct RevocationRequest { struct RevocationRequest {
bytes32 schema; // The unique identifier of the schema. bytes32 schema; // The unique identifier of the schema.
RevocationRequestData data; // The arguments of the revocation request. RevocationRequestData data; // The arguments of the revocation request.
} }
/** /// @dev A struct representing the arguments of the full delegated revocation request.
* @dev A struct representing the arguments of the full delegated revocation request.
*/
struct DelegatedRevocationRequest { struct DelegatedRevocationRequest {
bytes32 schema; // The unique identifier of the schema. bytes32 schema; // The unique identifier of the schema.
RevocationRequestData data; // The arguments of the revocation request. RevocationRequestData data; // The arguments of the revocation request.
...@@ -79,17 +62,13 @@ struct DelegatedRevocationRequest { ...@@ -79,17 +62,13 @@ struct DelegatedRevocationRequest {
address revoker; // The revoking account. address revoker; // The revoking account.
} }
/** /// @dev A struct representing the full arguments of the multi revocation request.
* @dev A struct representing the full arguments of the multi revocation request.
*/
struct MultiRevocationRequest { struct MultiRevocationRequest {
bytes32 schema; // The unique identifier of the schema. bytes32 schema; // The unique identifier of the schema.
RevocationRequestData[] data; // The arguments of the revocation request. RevocationRequestData[] data; // The arguments of the revocation request.
} }
/** /// @dev A struct representing the full arguments of the delegated multi revocation request.
* @dev A struct representing the full arguments of the delegated multi revocation request.
*/
struct MultiDelegatedRevocationRequest { struct MultiDelegatedRevocationRequest {
bytes32 schema; // The unique identifier of the schema. bytes32 schema; // The unique identifier of the schema.
RevocationRequestData[] data; // The arguments of the revocation requests. RevocationRequestData[] data; // The arguments of the revocation requests.
...@@ -100,366 +79,298 @@ struct MultiDelegatedRevocationRequest { ...@@ -100,366 +79,298 @@ struct MultiDelegatedRevocationRequest {
/// @title IEAS /// @title IEAS
/// @notice The Ethereum Attestation Service interface. /// @notice The Ethereum Attestation Service interface.
interface IEAS { interface IEAS {
/** /// @dev Emitted when an attestation has been made.
* @dev Emitted when an attestation has been made. /// @param recipient The recipient of the attestation.
* /// @param attester The attesting account.
* @param recipient The recipient of the attestation. /// @param uid The UID the revoked attestation.
* @param attester The attesting account. /// @param schema The UID of the schema.
* @param uid The UID the revoked attestation.
* @param schema The UID of the schema.
*/
event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schema); event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schema);
/** /// @dev Emitted when an attestation has been revoked.
* @dev Emitted when an attestation has been revoked. /// @param recipient The recipient of the attestation.
* /// @param attester The attesting account.
* @param recipient The recipient of the attestation. /// @param schema The UID of the schema.
* @param attester The attesting account. /// @param uid The UID the revoked attestation.
* @param schema The UID of the schema.
* @param uid The UID the revoked attestation.
*/
event Revoked(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schema); event Revoked(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schema);
/** /// @dev Emitted when a data has been timestamped.
* @dev Emitted when a data has been timestamped. /// @param data The data.
* /// @param timestamp The timestamp.
* @param data The data.
* @param timestamp The timestamp.
*/
event Timestamped(bytes32 indexed data, uint64 indexed timestamp); event Timestamped(bytes32 indexed data, uint64 indexed timestamp);
/** /// @dev Emitted when a data has been revoked.
* @dev Emitted when a data has been revoked. /// @param revoker The address of the revoker.
* /// @param data The data.
* @param revoker The address of the revoker. /// @param timestamp The timestamp.
* @param data The data.
* @param timestamp The timestamp.
*/
event RevokedOffchain(address indexed revoker, bytes32 indexed data, uint64 indexed timestamp); event RevokedOffchain(address indexed revoker, bytes32 indexed data, uint64 indexed timestamp);
/** /// @notice Returns the address of the global schema registry.
* @dev Returns the address of the global schema registry. /// @return The address of the global schema registry.
*
* @return The address of the global schema registry.
*/
function getSchemaRegistry() external view returns (ISchemaRegistry); function getSchemaRegistry() external view returns (ISchemaRegistry);
/** /// @notice Attests to a specific schema.
* @dev Attests to a specific schema. ///
* /// Example:
* @param request The arguments of the attestation request. ///
* /// attest({
* Example: /// schema: "0facc36681cbe2456019c1b0d1e7bedd6d1d40f6f324bf3dd3a4cef2999200a0",
* /// data: {
* attest({ /// recipient: "0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf",
* schema: "0facc36681cbe2456019c1b0d1e7bedd6d1d40f6f324bf3dd3a4cef2999200a0", /// expirationTime: 0,
* data: { /// revocable: true,
* recipient: "0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf", /// refUID: "0x0000000000000000000000000000000000000000000000000000000000000000",
* expirationTime: 0, /// data: "0xF00D",
* revocable: true, /// value: 0
* refUID: "0x0000000000000000000000000000000000000000000000000000000000000000", /// }
* data: "0xF00D", /// })
* value: 0 ///
* } /// @param request The arguments of the attestation request.
* }) /// @return The UID of the new attestation.
*
* @return The UID of the new attestation.
*/
function attest(AttestationRequest calldata request) external payable returns (bytes32); function attest(AttestationRequest calldata request) external payable returns (bytes32);
/** /// @notice Attests to a specific schema via the provided EIP712 signature.
* @dev Attests to a specific schema via the provided EIP712 signature. ///
* /// Example:
* @param delegatedRequest The arguments of the delegated attestation request. ///
* /// attestByDelegation({
* Example: /// schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc',
* /// data: {
* attestByDelegation({ /// recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
* schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc', /// expirationTime: 1673891048,
* data: { /// revocable: true,
* recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', /// refUID: '0x0000000000000000000000000000000000000000000000000000000000000000',
* expirationTime: 1673891048, /// data: '0x1234',
* revocable: true, /// value: 0
* refUID: '0x0000000000000000000000000000000000000000000000000000000000000000', /// },
* data: '0x1234', /// signature: {
* value: 0 /// v: 28,
* }, /// r: '0x148c...b25b',
* signature: { /// s: '0x5a72...be22'
* v: 28, /// },
* r: '0x148c...b25b', /// attester: '0xc5E8740aD971409492b1A63Db8d83025e0Fc427e'
* s: '0x5a72...be22' /// })
* }, ///
* attester: '0xc5E8740aD971409492b1A63Db8d83025e0Fc427e' /// @param delegatedRequest The arguments of the delegated attestation request.
* }) /// @return The UID of the new attestation.
*
* @return The UID of the new attestation.
*/
function attestByDelegation( function attestByDelegation(
DelegatedAttestationRequest calldata delegatedRequest DelegatedAttestationRequest calldata delegatedRequest
) external payable returns (bytes32); ) external payable returns (bytes32);
/** /// @notice Attests to multiple schemas.
* @dev Attests to multiple schemas. ///
* /// Example:
* @param multiRequests The arguments of the multi attestation requests. The requests should be grouped by distinct ///
* schema ids to benefit from the best batching optimization. /// multiAttest([{
* /// schema: '0x33e9094830a5cba5554d1954310e4fbed2ef5f859ec1404619adea4207f391fd',
* Example: /// data: [{
* /// recipient: '0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf',
* multiAttest([{ /// expirationTime: 1673891048,
* schema: '0x33e9094830a5cba5554d1954310e4fbed2ef5f859ec1404619adea4207f391fd', /// revocable: true,
* data: [{ /// refUID: '0x0000000000000000000000000000000000000000000000000000000000000000',
* recipient: '0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf', /// data: '0x1234',
* expirationTime: 1673891048, /// value: 1000
* revocable: true, /// },
* refUID: '0x0000000000000000000000000000000000000000000000000000000000000000', /// {
* data: '0x1234', /// recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
* value: 1000 /// expirationTime: 0,
* }, /// revocable: false,
* { /// refUID: '0x480df4a039efc31b11bfdf491b383ca138b6bde160988222a2a3509c02cee174',
* recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', /// data: '0x00',
* expirationTime: 0, /// value: 0
* revocable: false, /// }],
* refUID: '0x480df4a039efc31b11bfdf491b383ca138b6bde160988222a2a3509c02cee174', /// },
* data: '0x00', /// {
* value: 0 /// schema: '0x5ac273ce41e3c8bfa383efe7c03e54c5f0bff29c9f11ef6ffa930fc84ca32425',
* }], /// data: [{
* }, /// recipient: '0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf',
* { /// expirationTime: 0,
* schema: '0x5ac273ce41e3c8bfa383efe7c03e54c5f0bff29c9f11ef6ffa930fc84ca32425', /// revocable: true,
* data: [{ /// refUID: '0x75bf2ed8dca25a8190c50c52db136664de25b2449535839008ccfdab469b214f',
* recipient: '0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf', /// data: '0x12345678',
* expirationTime: 0, /// value: 0
* revocable: true, /// },
* refUID: '0x75bf2ed8dca25a8190c50c52db136664de25b2449535839008ccfdab469b214f', /// }])
* data: '0x12345678', ///
* value: 0 /// @param multiRequests The arguments of the multi attestation requests. The requests should be grouped by distinct
* }, /// schema ids to benefit from the best batching optimization.
* }]) /// @return The UIDs of the new attestations.
*
* @return The UIDs of the new attestations.
*/
function multiAttest(MultiAttestationRequest[] calldata multiRequests) external payable returns (bytes32[] memory); function multiAttest(MultiAttestationRequest[] calldata multiRequests) external payable returns (bytes32[] memory);
/** /// @notice Attests to multiple schemas using via provided EIP712 signatures.
* @dev Attests to multiple schemas using via provided EIP712 signatures. ///
* /// Example:
* @param multiDelegatedRequests The arguments of the delegated multi attestation requests. The requests should be ///
* grouped by distinct schema ids to benefit from the best batching optimization. /// multiAttestByDelegation([{
* /// schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc',
* Example: /// data: [{
* /// recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
* multiAttestByDelegation([{ /// expirationTime: 1673891048,
* schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc', /// revocable: true,
* data: [{ /// refUID: '0x0000000000000000000000000000000000000000000000000000000000000000',
* recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', /// data: '0x1234',
* expirationTime: 1673891048, /// value: 0
* revocable: true, /// },
* refUID: '0x0000000000000000000000000000000000000000000000000000000000000000', /// {
* data: '0x1234', /// recipient: '0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf',
* value: 0 /// expirationTime: 0,
* }, /// revocable: false,
* { /// refUID: '0x0000000000000000000000000000000000000000000000000000000000000000',
* recipient: '0xdEADBeAFdeAdbEafdeadbeafDeAdbEAFdeadbeaf', /// data: '0x00',
* expirationTime: 0, /// value: 0
* revocable: false, /// }],
* refUID: '0x0000000000000000000000000000000000000000000000000000000000000000', /// signatures: [{
* data: '0x00', /// v: 28,
* value: 0 /// r: '0x148c...b25b',
* }], /// s: '0x5a72...be22'
* signatures: [{ /// },
* v: 28, /// {
* r: '0x148c...b25b', /// v: 28,
* s: '0x5a72...be22' /// r: '0x487s...67bb',
* }, /// s: '0x12ad...2366'
* { /// }],
* v: 28, /// attester: '0x1D86495b2A7B524D747d2839b3C645Bed32e8CF4'
* r: '0x487s...67bb', /// }])
* s: '0x12ad...2366' ///
* }], /// @param multiDelegatedRequests The arguments of the delegated multi attestation requests. The requests should be
* attester: '0x1D86495b2A7B524D747d2839b3C645Bed32e8CF4' /// grouped by distinct schema ids to benefit from the best batching optimization.
* }]) /// @return The UIDs of the new attestations.
*
* @return The UIDs of the new attestations.
*/
function multiAttestByDelegation( function multiAttestByDelegation(
MultiDelegatedAttestationRequest[] calldata multiDelegatedRequests MultiDelegatedAttestationRequest[] calldata multiDelegatedRequests
) external payable returns (bytes32[] memory); ) external payable returns (bytes32[] memory);
/** /// @notice Revokes an existing attestation to a specific schema.
* @dev Revokes an existing attestation to a specific schema. ///
* /// Example:
* Example: ///
* /// revoke({
* revoke({ /// schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc',
* schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc', /// data: {
* data: { /// uid: '0x101032e487642ee04ee17049f99a70590c735b8614079fc9275f9dd57c00966d',
* uid: '0x101032e487642ee04ee17049f99a70590c735b8614079fc9275f9dd57c00966d', /// value: 0
* value: 0 /// }
* } /// })
* }) ///
* /// @param request The arguments of the revocation request.
* @param request The arguments of the revocation request.
*/
function revoke(RevocationRequest calldata request) external payable; function revoke(RevocationRequest calldata request) external payable;
/** /// @notice Revokes an existing attestation to a specific schema via the provided EIP712 signature.
* @dev Revokes an existing attestation to a specific schema via the provided EIP712 signature. ///
* /// Example:
* Example: ///
* /// revokeByDelegation({
* revokeByDelegation({ /// schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc',
* schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc', /// data: {
* data: { /// uid: '0xcbbc12102578c642a0f7b34fe7111e41afa25683b6cd7b5a14caf90fa14d24ba',
* uid: '0xcbbc12102578c642a0f7b34fe7111e41afa25683b6cd7b5a14caf90fa14d24ba', /// value: 0
* value: 0 /// },
* }, /// signature: {
* signature: { /// v: 27,
* v: 27, /// r: '0xb593...7142',
* r: '0xb593...7142', /// s: '0x0f5b...2cce'
* s: '0x0f5b...2cce' /// },
* }, /// revoker: '0x244934dd3e31bE2c81f84ECf0b3E6329F5381992'
* revoker: '0x244934dd3e31bE2c81f84ECf0b3E6329F5381992' /// })
* }) ///
* /// @param delegatedRequest The arguments of the delegated revocation request.
* @param delegatedRequest The arguments of the delegated revocation request.
*/
function revokeByDelegation(DelegatedRevocationRequest calldata delegatedRequest) external payable; function revokeByDelegation(DelegatedRevocationRequest calldata delegatedRequest) external payable;
/** /// @notice Revokes existing attestations to multiple schemas.
* @dev Revokes existing attestations to multiple schemas. ///
* /// Example:
* @param multiRequests The arguments of the multi revocation requests. The requests should be grouped by distinct ///
* schema ids to benefit from the best batching optimization. /// multiRevoke([{
* /// schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc',
* Example: /// data: [{
* /// uid: '0x211296a1ca0d7f9f2cfebf0daaa575bea9b20e968d81aef4e743d699c6ac4b25',
* multiRevoke([{ /// value: 1000
* schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc', /// },
* data: [{ /// {
* uid: '0x211296a1ca0d7f9f2cfebf0daaa575bea9b20e968d81aef4e743d699c6ac4b25', /// uid: '0xe160ac1bd3606a287b4d53d5d1d6da5895f65b4b4bab6d93aaf5046e48167ade',
* value: 1000 /// value: 0
* }, /// }],
* { /// },
* uid: '0xe160ac1bd3606a287b4d53d5d1d6da5895f65b4b4bab6d93aaf5046e48167ade', /// {
* value: 0 /// schema: '0x5ac273ce41e3c8bfa383efe7c03e54c5f0bff29c9f11ef6ffa930fc84ca32425',
* }], /// data: [{
* }, /// uid: '0x053d42abce1fd7c8fcddfae21845ad34dae287b2c326220b03ba241bc5a8f019',
* { /// value: 0
* schema: '0x5ac273ce41e3c8bfa383efe7c03e54c5f0bff29c9f11ef6ffa930fc84ca32425', /// },
* data: [{ /// }])
* uid: '0x053d42abce1fd7c8fcddfae21845ad34dae287b2c326220b03ba241bc5a8f019', ///
* value: 0 /// @param multiRequests The arguments of the multi revocation requests. The requests should be grouped by distinct
* }, /// schema ids to benefit from the best batching optimization.
* }])
*/
function multiRevoke(MultiRevocationRequest[] calldata multiRequests) external payable; function multiRevoke(MultiRevocationRequest[] calldata multiRequests) external payable;
/** /// @notice Revokes existing attestations to multiple schemas via provided EIP712 signatures.
* @dev Revokes existing attestations to multiple schemas via provided EIP712 signatures. ///
* /// Example:
* @param multiDelegatedRequests The arguments of the delegated multi revocation attestation requests. The requests should be ///
* grouped by distinct schema ids to benefit from the best batching optimization. /// multiRevokeByDelegation([{
* /// schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc',
* Example: /// data: [{
* /// uid: '0x211296a1ca0d7f9f2cfebf0daaa575bea9b20e968d81aef4e743d699c6ac4b25',
* multiRevokeByDelegation([{ /// value: 1000
* schema: '0x8e72f5bc0a8d4be6aa98360baa889040c50a0e51f32dbf0baa5199bd93472ebc', /// },
* data: [{ /// {
* uid: '0x211296a1ca0d7f9f2cfebf0daaa575bea9b20e968d81aef4e743d699c6ac4b25', /// uid: '0xe160ac1bd3606a287b4d53d5d1d6da5895f65b4b4bab6d93aaf5046e48167ade',
* value: 1000 /// value: 0
* }, /// }],
* { /// signatures: [{
* uid: '0xe160ac1bd3606a287b4d53d5d1d6da5895f65b4b4bab6d93aaf5046e48167ade', /// v: 28,
* value: 0 /// r: '0x148c...b25b',
* }], /// s: '0x5a72...be22'
* signatures: [{ /// },
* v: 28, /// {
* r: '0x148c...b25b', /// v: 28,
* s: '0x5a72...be22' /// r: '0x487s...67bb',
* }, /// s: '0x12ad...2366'
* { /// }],
* v: 28, /// revoker: '0x244934dd3e31bE2c81f84ECf0b3E6329F5381992'
* r: '0x487s...67bb', /// }])
* s: '0x12ad...2366' ///
* }], /// @param multiDelegatedRequests The arguments of the delegated multi revocation attestation requests. The requests should be
* revoker: '0x244934dd3e31bE2c81f84ECf0b3E6329F5381992' /// grouped by distinct schema ids to benefit from the best batching optimization.
* }])
*
*/
function multiRevokeByDelegation( function multiRevokeByDelegation(
MultiDelegatedRevocationRequest[] calldata multiDelegatedRequests MultiDelegatedRevocationRequest[] calldata multiDelegatedRequests
) external payable; ) external payable;
/** /// @notice Timestamps the specified bytes32 data.
* @dev Timestamps the specified bytes32 data. /// @param data The data to timestamp.
* /// @return The timestamp the data was timestamped with.
* @param data The data to timestamp.
*
* @return The timestamp the data was timestamped with.
*/
function timestamp(bytes32 data) external returns (uint64); function timestamp(bytes32 data) external returns (uint64);
/** /// @notice Timestamps the specified multiple bytes32 data.
* @dev Timestamps the specified multiple bytes32 data. /// @param data The data to timestamp.
* /// @return The timestamp the data was timestamped with.
* @param data The data to timestamp.
*
* @return The timestamp the data was timestamped with.
*/
function multiTimestamp(bytes32[] calldata data) external returns (uint64); function multiTimestamp(bytes32[] calldata data) external returns (uint64);
/** /// @notice Revokes the specified bytes32 data.
* @dev Revokes the specified bytes32 data. /// @param data The data to timestamp.
* /// @return The timestamp the data was revoked with.
* @param data The data to timestamp.
*
* @return The timestamp the data was revoked with.
*/
function revokeOffchain(bytes32 data) external returns (uint64); function revokeOffchain(bytes32 data) external returns (uint64);
/** /// @notice Revokes the specified multiple bytes32 data.
* @dev Revokes the specified multiple bytes32 data. /// @param data The data to timestamp.
* /// @return The timestamp the data was revoked with.
* @param data The data to timestamp.
*
* @return The timestamp the data was revoked with.
*/
function multiRevokeOffchain(bytes32[] calldata data) external returns (uint64); function multiRevokeOffchain(bytes32[] calldata data) external returns (uint64);
/** /// @notice Returns an existing attestation by UID.
* @dev Returns an existing attestation by UID. /// @param uid The UID of the attestation to retrieve.
* /// @return The attestation data members.
* @param uid The UID of the attestation to retrieve.
*
* @return The attestation data members.
*/
function getAttestation(bytes32 uid) external view returns (Attestation memory); function getAttestation(bytes32 uid) external view returns (Attestation memory);
/** /// @notice Checks whether an attestation exists.
* @dev Checks whether an attestation exists. /// @param uid The UID of the attestation to retrieve.
* /// @return Whether an attestation exists.
* @param uid The UID of the attestation to retrieve.
*
* @return Whether an attestation exists.
*/
function isAttestationValid(bytes32 uid) external view returns (bool); function isAttestationValid(bytes32 uid) external view returns (bool);
/** /// @notice Returns the timestamp that the specified data was timestamped with.
* @dev Returns the timestamp that the specified data was timestamped with. /// @param data The data to query.
* /// @return The timestamp the data was timestamped with.
* @param data The data to query.
*
* @return The timestamp the data was timestamped with.
*/
function getTimestamp(bytes32 data) external view returns (uint64); function getTimestamp(bytes32 data) external view returns (uint64);
/** /// @notice Returns the timestamp that the specified data was timestamped with.
* @dev Returns the timestamp that the specified data was timestamped with. /// @param data The data to query.
* /// @return The timestamp the data was timestamped with.
* @param data The data to query.
*
* @return The timestamp the data was timestamped with.
*/
function getRevokeOffchain(address revoker, bytes32 data) external view returns (uint64); function getRevokeOffchain(address revoker, bytes32 data) external view returns (uint64);
} }
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import { ISchemaResolver } from "./resolver/ISchemaResolver.sol"; 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 { struct SchemaRecord {
bytes32 uid; // The unique identifier of the schema. bytes32 uid; // The unique identifier of the schema.
ISchemaResolver resolver; // Optional schema resolver. ISchemaResolver resolver; // Optional schema resolver.
...@@ -17,31 +14,20 @@ struct SchemaRecord { ...@@ -17,31 +14,20 @@ struct SchemaRecord {
/// @title ISchemaRegistry /// @title ISchemaRegistry
/// @notice The interface of global attestation schemas for the Ethereum Attestation Service protocol. /// @notice The interface of global attestation schemas for the Ethereum Attestation Service protocol.
interface ISchemaRegistry { interface ISchemaRegistry {
/** /// @dev Emitted when a new schema has been registered
* @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.
* @param uid The schema UID.
* @param registerer The address of the account used to register the schema.
*/
event Registered(bytes32 indexed uid, address registerer); event Registered(bytes32 indexed uid, address registerer);
/** /// @dev Submits and reserves a new schema
* @dev Submits and reserves a new schema /// @param schema The schema data schema.
* /// @param resolver An optional schema resolver.
* @param schema The schema data schema. /// @param revocable Whether the schema allows revocations explicitly.
* @param resolver An optional schema resolver. /// @return The UID of the new schema.
* @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); function register(string calldata schema, ISchemaResolver resolver, bool revocable) external returns (bytes32);
/** /// @dev Returns an existing schema by UID
* @dev Returns an existing schema by UID /// @param uid The UID of the schema to retrieve.
* /// @return The schema data members.
* @param uid The UID of the schema to retrieve.
*
* @return The schema data members.
*/
function getSchema(bytes32 uid) external view returns (SchemaRecord memory); function getSchema(bytes32 uid) external view returns (SchemaRecord memory);
} }
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.19; pragma solidity 0.8.19;
import { Semver } from "../universal/Semver.sol"; import { Semver } from "../universal/Semver.sol";
...@@ -22,14 +21,10 @@ contract SchemaRegistry is ISchemaRegistry, Semver { ...@@ -22,14 +21,10 @@ contract SchemaRegistry is ISchemaRegistry, Semver {
// Upgrade forward-compatibility storage gap // Upgrade forward-compatibility storage gap
uint256[MAX_GAP - 1] private __gap; uint256[MAX_GAP - 1] private __gap;
/** /// @dev Creates a new SchemaRegistry instance.
* @dev Creates a new SchemaRegistry instance.
*/
constructor() Semver(1, 0, 0) {} constructor() Semver(1, 0, 0) {}
/** /// @inheritdoc ISchemaRegistry
* @inheritdoc ISchemaRegistry
*/
function register(string calldata schema, ISchemaResolver resolver, bool revocable) external returns (bytes32) { function register(string calldata schema, ISchemaResolver resolver, bool revocable) external returns (bytes32) {
SchemaRecord memory schemaRecord = SchemaRecord({ SchemaRecord memory schemaRecord = SchemaRecord({
uid: EMPTY_UID, uid: EMPTY_UID,
...@@ -51,20 +46,14 @@ contract SchemaRegistry is ISchemaRegistry, Semver { ...@@ -51,20 +46,14 @@ contract SchemaRegistry is ISchemaRegistry, Semver {
return uid; return uid;
} }
/** /// @inheritdoc ISchemaRegistry
* @inheritdoc ISchemaRegistry
*/
function getSchema(bytes32 uid) external view returns (SchemaRecord memory) { function getSchema(bytes32 uid) external view returns (SchemaRecord memory) {
return _registry[uid]; return _registry[uid];
} }
/** /// @dev Calculates a UID for a given schema.
* @dev Calculates a UID for a given schema. /// @param schemaRecord The input schema.
* /// @return schema UID.
* @param schemaRecord The input schema.
*
* @return schema UID.
*/
function _getUID(SchemaRecord memory schemaRecord) private pure returns (bytes32) { function _getUID(SchemaRecord memory schemaRecord) private pure returns (bytes32) {
return keccak256(abi.encodePacked(schemaRecord.schema, schemaRecord.resolver, schemaRecord.revocable)); return keccak256(abi.encodePacked(schemaRecord.schema, schemaRecord.resolver, schemaRecord.revocable));
} }
......
...@@ -36,59 +36,44 @@ abstract contract EIP712Verifier is EIP712 { ...@@ -36,59 +36,44 @@ abstract contract EIP712Verifier is EIP712 {
// Upgrade forward-compatibility storage gap // Upgrade forward-compatibility storage gap
uint256[MAX_GAP - 1] private __gap; uint256[MAX_GAP - 1] private __gap;
/** /// @dev Creates a new EIP712Verifier instance.
* @dev Creates a new EIP712Verifier instance. /// @param version The current major version of the signing domain
*
* @param version The current major version of the signing domain
*/
constructor(string memory name, string memory version) EIP712(name, version) { constructor(string memory name, string memory version) EIP712(name, version) {
_name = stringToBytes32(name); _name = stringToBytes32(name);
} }
/** /// @notice Returns the domain separator used in the encoding of the signatures for attest, and revoke.
* @dev Returns the domain separator used in the encoding of the signatures for attest, and revoke.
*/
function getDomainSeparator() external view returns (bytes32) { function getDomainSeparator() external view returns (bytes32) {
return _domainSeparatorV4(); return _domainSeparatorV4();
} }
/** /// @notice Returns the current nonce per-account.
* @dev Returns the current nonce per-account. /// @param account The requested account.
* /// @return The current nonce.
* @param account The requested account.
*
* @return The current nonce.
*/
function getNonce(address account) external view returns (uint256) { function getNonce(address account) external view returns (uint256) {
return _nonces[account]; return _nonces[account];
} }
/** /// @notice Returns the EIP712 type hash for the attest function.
* Returns the EIP712 type hash for the attest function. /// @return The EIP712 attest function type hash.
*/
function getAttestTypeHash() external pure returns (bytes32) { function getAttestTypeHash() external pure returns (bytes32) {
return ATTEST_TYPEHASH; return ATTEST_TYPEHASH;
} }
/** /// @notice Returns the EIP712 type hash for the revoke function.
* Returns the EIP712 type hash for the revoke function. /// @return hash_ The EIP712 revoke function type hash.
*/
function getRevokeTypeHash() external pure returns (bytes32) { function getRevokeTypeHash() external pure returns (bytes32) {
return REVOKE_TYPEHASH; return REVOKE_TYPEHASH;
} }
/** /// @notice Returns the EIP712 name.
* Returns the EIP712 name. /// @return The EIP712 name.
*/
function getName() external view returns (string memory) { function getName() external view returns (string memory) {
return bytes32ToString(_name); return bytes32ToString(_name);
} }
/** /// @notice Verifies delegated attestation request.
* @dev Verifies delegated attestation request. /// @param request The arguments of the delegated attestation request.
*
* @param request The arguments of the delegated attestation request.
*/
function _verifyAttest(DelegatedAttestationRequest memory request) internal { function _verifyAttest(DelegatedAttestationRequest memory request) internal {
AttestationRequestData memory data = request.data; AttestationRequestData memory data = request.data;
EIP712Signature memory signature = request.signature; EIP712Signature memory signature = request.signature;
...@@ -118,11 +103,8 @@ abstract contract EIP712Verifier is EIP712 { ...@@ -118,11 +103,8 @@ abstract contract EIP712Verifier is EIP712 {
} }
} }
/** /// @notice Verifies delegated revocation request.
* @dev Verifies delegated revocation request. /// @param request The arguments of the delegated revocation request.
*
* @param request The arguments of the delegated revocation request.
*/
function _verifyRevoke(DelegatedRevocationRequest memory request) internal { function _verifyRevoke(DelegatedRevocationRequest memory request) internal {
RevocationRequestData memory data = request.data; RevocationRequestData memory data = request.data;
EIP712Signature memory signature = request.signature; EIP712Signature memory signature = request.signature;
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import { Attestation } from "../Common.sol"; import { Attestation } from "../Common.sol";
...@@ -7,50 +6,33 @@ import { Attestation } from "../Common.sol"; ...@@ -7,50 +6,33 @@ import { Attestation } from "../Common.sol";
/// @title ISchemaResolver /// @title ISchemaResolver
/// @notice The interface of an optional schema resolver. /// @notice The interface of an optional schema resolver.
interface ISchemaResolver { interface ISchemaResolver {
/** /// @notice Checks if the resolve can be sent ETH.
* @dev Returns whether the resolver supports ETH transfers. /// @return Whether the resolver supports ETH transfers.
*/
function isPayable() external pure returns (bool); function isPayable() external pure returns (bool);
/** /// @notice Processes an attestation and verifies whether it's valid.
* @dev Processes an attestation and verifies whether it's valid. /// @param attestation The new attestation.
* /// @return Whether the attestation is valid.
* @param attestation The new attestation.
*
* @return Whether the attestation is valid.
*/
function attest(Attestation calldata attestation) external payable returns (bool); function attest(Attestation calldata attestation) external payable returns (bool);
/** /// @notice Processes multiple attestations and verifies whether they are valid.
* @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.
* @param attestations The new attestations. /// @return Whether all the attestations are valid.
* @param values Explicit ETH amounts which were sent with each attestation.
*
* @return Whether all the attestations are valid.
*/
function multiAttest( function multiAttest(
Attestation[] calldata attestations, Attestation[] calldata attestations,
uint256[] calldata values uint256[] calldata values
) external payable returns (bool); ) external payable returns (bool);
/** /// @notice Processes an attestation revocation and verifies if it can be revoked.
* @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.
* @param attestation The existing attestation to be revoked.
*
* @return Whether the attestation can be revoked.
*/
function revoke(Attestation calldata attestation) external payable returns (bool); function revoke(Attestation calldata attestation) external payable returns (bool);
/** /// @notice Processes revocation of multiple attestation and verifies they can be revoked.
* @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.
* @param attestations The existing attestations to be revoked. /// @return Whether the attestations can be revoked.
* @param values Explicit ETH amounts which were sent with each revocation.
*
* @return Whether the attestations can be revoked.
*/
function multiRevoke( function multiRevoke(
Attestation[] calldata attestations, Attestation[] calldata attestations,
uint256[] calldata values uint256[] calldata values
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.19; pragma solidity 0.8.19;
import { Semver } from "../../universal/Semver.sol"; import { Semver } from "../../universal/Semver.sol";
...@@ -19,11 +18,8 @@ abstract contract SchemaResolver is ISchemaResolver, Semver { ...@@ -19,11 +18,8 @@ abstract contract SchemaResolver is ISchemaResolver, Semver {
// The global EAS contract. // The global EAS contract.
IEAS internal immutable _eas; IEAS internal immutable _eas;
/** /// @dev Creates a new resolver.
* @dev Creates a new resolver. /// @param eas The address of the global EAS contract.
*
* @param eas The address of the global EAS contract.
*/
constructor(IEAS eas) Semver(1, 0, 0) { constructor(IEAS eas) Semver(1, 0, 0) {
if (address(eas) == address(0)) { if (address(eas) == address(0)) {
revert InvalidEAS(); revert InvalidEAS();
...@@ -32,41 +28,31 @@ abstract contract SchemaResolver is ISchemaResolver, Semver { ...@@ -32,41 +28,31 @@ abstract contract SchemaResolver is ISchemaResolver, Semver {
_eas = eas; _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() { modifier onlyEAS() {
_onlyEAS(); _onlyEAS();
_; _;
} }
/** /// @inheritdoc ISchemaResolver
* @inheritdoc ISchemaResolver
*/
function isPayable() public pure virtual returns (bool) { function isPayable() public pure virtual returns (bool) {
return false; return false;
} }
/** /// @dev ETH callback.
* @dev ETH callback.
*/
receive() external payable virtual { receive() external payable virtual {
if (!isPayable()) { if (!isPayable()) {
revert NotPayable(); revert NotPayable();
} }
} }
/** /// @inheritdoc ISchemaResolver
* @inheritdoc ISchemaResolver
*/
function attest(Attestation calldata attestation) external payable onlyEAS returns (bool) { function attest(Attestation calldata attestation) external payable onlyEAS returns (bool) {
return onAttest(attestation, msg.value); return onAttest(attestation, msg.value);
} }
/** /// @inheritdoc ISchemaResolver
* @inheritdoc ISchemaResolver
*/
function multiAttest( function multiAttest(
Attestation[] calldata attestations, Attestation[] calldata attestations,
uint256[] calldata values uint256[] calldata values
...@@ -100,16 +86,12 @@ abstract contract SchemaResolver is ISchemaResolver, Semver { ...@@ -100,16 +86,12 @@ abstract contract SchemaResolver is ISchemaResolver, Semver {
return true; return true;
} }
/** /// @inheritdoc ISchemaResolver
* @inheritdoc ISchemaResolver
*/
function revoke(Attestation calldata attestation) external payable onlyEAS returns (bool) { function revoke(Attestation calldata attestation) external payable onlyEAS returns (bool) {
return onRevoke(attestation, msg.value); return onRevoke(attestation, msg.value);
} }
/** /// @inheritdoc ISchemaResolver
* @inheritdoc ISchemaResolver
*/
function multiRevoke( function multiRevoke(
Attestation[] calldata attestations, Attestation[] calldata attestations,
uint256[] calldata values uint256[] calldata values
...@@ -143,35 +125,25 @@ abstract contract SchemaResolver is ISchemaResolver, Semver { ...@@ -143,35 +125,25 @@ abstract contract SchemaResolver is ISchemaResolver, Semver {
return true; return true;
} }
/** /// @notice A resolver callback that should be implemented by child contracts.
* @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
* @param attestation The new attestation. /// both attest() and multiAttest() callbacks EAS-only callbacks and that in case of multi attestations, it'll
* @param value An explicit ETH amount that was sent to the resolver. Please note that this value is verified in /// usually hold that msg.value != value, since msg.value aggregated the sent ETH amounts for all the attestations
* both attest() and multiAttest() callbacks EAS-only callbacks and that in case of multi attestations, it'll /// in the batch.
* usually hold that msg.value != value, since msg.value aggregated the sent ETH amounts for all the attestations /// @return Whether the attestation is valid.
* in the batch.
*
* @return Whether the attestation is valid.
*/
function onAttest(Attestation calldata attestation, uint256 value) internal virtual returns (bool); function onAttest(Attestation calldata attestation, uint256 value) internal virtual returns (bool);
/** /// @notice Processes an attestation revocation and verifies if it can be revoked.
* @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
* @param attestation The existing attestation to be revoked. /// both revoke() and multiRevoke() callbacks EAS-only callbacks and that in case of multi attestations, it'll
* @param value An explicit ETH amount that was sent to the resolver. Please note that this value is verified in /// usually hold that msg.value != value, since msg.value aggregated the sent ETH amounts for all the attestations
* both revoke() and multiRevoke() callbacks EAS-only callbacks and that in case of multi attestations, it'll /// in the batch.
* usually hold that msg.value != value, since msg.value aggregated the sent ETH amounts for all the attestations /// @return Whether the attestation can be revoked.
* in the batch.
*
* @return Whether the attestation can be revoked.
*/
function onRevoke(Attestation calldata attestation, uint256 value) internal virtual returns (bool); function onRevoke(Attestation calldata attestation, uint256 value) internal virtual returns (bool);
/** /// @notice Ensures that only the EAS contract can make this call.
* @dev Ensures that only the EAS contract can make this call.
*/
function _onlyEAS() private view { function _onlyEAS() private view {
if (msg.sender != address(_eas)) { if (msg.sender != address(_eas)) {
revert AccessDenied(); revert AccessDenied();
......
...@@ -4,27 +4,22 @@ pragma solidity 0.8.15; ...@@ -4,27 +4,22 @@ pragma solidity 0.8.15;
import { AssetReceiver } from "../AssetReceiver.sol"; import { AssetReceiver } from "../AssetReceiver.sol";
import { IDripCheck } from "./IDripCheck.sol"; import { IDripCheck } from "./IDripCheck.sol";
/** /// @title Drippie
* @title Drippie /// @notice Drippie is a system for managing automated contract interactions. A specific interaction
* @notice Drippie is a system for managing automated contract interactions. A specific interaction /// is called a "drip" and can be executed according to some condition (called a dripcheck)
* is called a "drip" and can be executed according to some condition (called a dripcheck) /// and an execution interval. Drips cannot be executed faster than the execution interval.
* and an execution interval. Drips cannot be executed faster than the execution interval. /// Drips can trigger arbitrary contract calls where the calling contract is this contract
* Drips can trigger arbitrary contract calls where the calling contract is this contract /// address. Drips can also send ETH value, which makes them ideal for keeping addresses
* address. Drips can also send ETH value, which makes them ideal for keeping addresses /// sufficiently funded with ETH. Drippie is designed to be connected with smart contract
* sufficiently funded with ETH. Drippie is designed to be connected with smart contract /// automation services so that drips can be executed automatically. However, Drippie is
* automation services so that drips can be executed automatically. However, Drippie is /// specifically designed to be separated from these services so that trust assumptions are
* specifically designed to be separated from these services so that trust assumptions are /// better compartmentalized.
* better compartmentalized.
*/
contract Drippie is AssetReceiver { contract Drippie is AssetReceiver {
/** /// @notice Enum representing different status options for a given drip.
* @notice Enum representing different status options for a given drip. /// @custom:value NONE Drip does not exist.
* /// @custom:value PAUSED Drip is paused and cannot be executed until reactivated.
* @custom:value NONE Drip does not exist. /// @custom:value ACTIVE Drip is active and can be executed.
* @custom:value PAUSED Drip is paused and cannot be executed until reactivated. /// @custom:value ARCHIVED Drip is archived and can no longer be executed or reactivated.
* @custom:value ACTIVE Drip is active and can be executed.
* @custom:value ARCHIVED Drip is archived and can no longer be executed or reactivated.
*/
enum DripStatus { enum DripStatus {
NONE, NONE,
PAUSED, PAUSED,
...@@ -32,18 +27,14 @@ contract Drippie is AssetReceiver { ...@@ -32,18 +27,14 @@ contract Drippie is AssetReceiver {
ARCHIVED ARCHIVED
} }
/** /// @notice Represents a drip action.
* @notice Represents a drip action.
*/
struct DripAction { struct DripAction {
address payable target; address payable target;
bytes data; bytes data;
uint256 value; uint256 value;
} }
/** /// @notice Represents the configuration for a given drip.
* @notice Represents the configuration for a given drip.
*/
struct DripConfig { struct DripConfig {
bool reentrant; bool reentrant;
uint256 interval; uint256 interval;
...@@ -52,9 +43,7 @@ contract Drippie is AssetReceiver { ...@@ -52,9 +43,7 @@ contract Drippie is AssetReceiver {
DripAction[] actions; DripAction[] actions;
} }
/** /// @notice Represents the state of an active drip.
* @notice Represents the state of an active drip.
*/
struct DripState { struct DripState {
DripStatus status; DripStatus status;
DripConfig config; DripConfig config;
...@@ -62,13 +51,10 @@ contract Drippie is AssetReceiver { ...@@ -62,13 +51,10 @@ contract Drippie is AssetReceiver {
uint256 count; uint256 count;
} }
/** /// @notice Emitted when a new drip is created.
* @notice Emitted when a new drip is created. /// @param nameref Indexed name parameter (hashed).
* /// @param name Unindexed name parameter (unhashed).
* @param nameref Indexed name parameter (hashed). /// @param config Config for the created drip.
* @param name Unindexed name parameter (unhashed).
* @param config Config for the created drip.
*/
event DripCreated( event DripCreated(
// Emit name twice because indexed version is hashed. // Emit name twice because indexed version is hashed.
string indexed nameref, string indexed nameref,
...@@ -76,13 +62,10 @@ contract Drippie is AssetReceiver { ...@@ -76,13 +62,10 @@ contract Drippie is AssetReceiver {
DripConfig config DripConfig config
); );
/** /// @notice Emitted when a drip status is updated.
* @notice Emitted when a drip status is updated. /// @param nameref Indexed name parameter (hashed).
* /// @param name Unindexed name parameter (unhashed).
* @param nameref Indexed name parameter (hashed). /// @param status New drip status.
* @param name Unindexed name parameter (unhashed).
* @param status New drip status.
*/
event DripStatusUpdated( event DripStatusUpdated(
// Emit name twice because indexed version is hashed. // Emit name twice because indexed version is hashed.
string indexed nameref, string indexed nameref,
...@@ -90,14 +73,11 @@ contract Drippie is AssetReceiver { ...@@ -90,14 +73,11 @@ contract Drippie is AssetReceiver {
DripStatus status DripStatus status
); );
/** /// @notice Emitted when a drip is executed.
* @notice Emitted when a drip is executed. /// @param nameref Indexed name parameter (hashed).
* /// @param name Unindexed name parameter (unhashed).
* @param nameref Indexed name parameter (hashed). /// @param executor Address that executed the drip.
* @param name Unindexed name parameter (unhashed). /// @param timestamp Time when the drip was executed.
* @param executor Address that executed the drip.
* @param timestamp Time when the drip was executed.
*/
event DripExecuted( event DripExecuted(
// Emit name twice because indexed version is hashed. // Emit name twice because indexed version is hashed.
string indexed nameref, string indexed nameref,
...@@ -106,24 +86,17 @@ contract Drippie is AssetReceiver { ...@@ -106,24 +86,17 @@ contract Drippie is AssetReceiver {
uint256 timestamp uint256 timestamp
); );
/** /// @notice Maps from drip names to drip states.
* @notice Maps from drip names to drip states.
*/
mapping(string => DripState) public drips; mapping(string => DripState) public drips;
/** //// @param _owner Initial contract owner.
* @param _owner Initial contract owner.
*/
constructor(address _owner) AssetReceiver(_owner) {} constructor(address _owner) AssetReceiver(_owner) {}
/** /// @notice Creates a new drip with the given name and configuration. Once created, drips cannot
* @notice Creates a new drip with the given name and configuration. Once created, drips cannot /// be modified in any way (this is a security measure). If you want to update a drip,
* be modified in any way (this is a security measure). If you want to update a drip, /// simply pause (and potentially archive) the existing drip and create a new one.
* simply pause (and potentially archive) the existing drip and create a new one. /// @param _name Name of the drip.
* /// @param _config Configuration for the drip.
* @param _name Name of the drip.
* @param _config Configuration for the drip.
*/
function create(string calldata _name, DripConfig calldata _config) external onlyOwner { function create(string calldata _name, DripConfig calldata _config) external onlyOwner {
// Make sure this drip doesn't already exist. We *must* guarantee that no other function // Make sure this drip doesn't already exist. We *must* guarantee that no other function
// will ever set the status of a drip back to NONE after it's been created. This is why // will ever set the status of a drip back to NONE after it's been created. This is why
...@@ -165,15 +138,12 @@ contract Drippie is AssetReceiver { ...@@ -165,15 +138,12 @@ contract Drippie is AssetReceiver {
emit DripCreated(_name, _name, _config); emit DripCreated(_name, _name, _config);
} }
/** /// @notice Sets the status for a given drip. The behavior of this function depends on the
* @notice Sets the status for a given drip. The behavior of this function depends on the /// status that the user is trying to set. A drip can always move between ACTIVE and
* status that the user is trying to set. A drip can always move between ACTIVE and /// PAUSED, but it can never move back to NONE and once ARCHIVED, it can never move back
* PAUSED, but it can never move back to NONE and once ARCHIVED, it can never move back /// to ACTIVE or PAUSED.
* to ACTIVE or PAUSED. /// @param _name Name of the drip to update.
* /// @param _status New drip status.
* @param _name Name of the drip to update.
* @param _status New drip status.
*/
function status(string calldata _name, DripStatus _status) external onlyOwner { function status(string calldata _name, DripStatus _status) external onlyOwner {
// Make sure we can never set drip status back to NONE. A simple security measure to // Make sure we can never set drip status back to NONE. A simple security measure to
// prevent accidental overwrites if this code is ever updated down the line. // prevent accidental overwrites if this code is ever updated down the line.
...@@ -223,13 +193,9 @@ contract Drippie is AssetReceiver { ...@@ -223,13 +193,9 @@ contract Drippie is AssetReceiver {
emit DripStatusUpdated(_name, _name, _status); emit DripStatusUpdated(_name, _name, _status);
} }
/** /// @notice Checks if a given drip is executable.
* @notice Checks if a given drip is executable. /// @param _name Drip to check.
* /// @return True if the drip is executable, reverts otherwise.
* @param _name Drip to check.
*
* @return True if the drip is executable, reverts otherwise.
*/
function executable(string calldata _name) public view returns (bool) { function executable(string calldata _name) public view returns (bool) {
DripState storage state = drips[_name]; DripState storage state = drips[_name];
...@@ -259,18 +225,15 @@ contract Drippie is AssetReceiver { ...@@ -259,18 +225,15 @@ contract Drippie is AssetReceiver {
return true; return true;
} }
/** /// @notice Triggers a drip. This function is deliberately left as a public function because the
* @notice Triggers a drip. This function is deliberately left as a public function because the /// assumption being made here is that setting the drip to ACTIVE is an affirmative
* assumption being made here is that setting the drip to ACTIVE is an affirmative /// signal that the drip should be executable according to the drip parameters, drip
* signal that the drip should be executable according to the drip parameters, drip /// check, and drip interval. Note that drip parameters are read entirely from the state
* check, and drip interval. Note that drip parameters are read entirely from the state /// and are not supplied as user input, so there should not be any way for a
* and are not supplied as user input, so there should not be any way for a /// non-authorized user to influence the behavior of the drip. Note that the drip check
* non-authorized user to influence the behavior of the drip. Note that the drip check /// is executed only **once** at the beginning of the call to the drip function and will
* is executed only **once** at the beginning of the call to the drip function and will /// not be executed again between the drip actions within this call.
* not be executed again between the drip actions within this call. /// @param _name Name of the drip to trigger.
*
* @param _name Name of the drip to trigger.
*/
function drip(string calldata _name) external { function drip(string calldata _name) external {
DripState storage state = drips[_name]; DripState storage state = drips[_name];
......
...@@ -7,12 +7,8 @@ interface IDripCheck { ...@@ -7,12 +7,8 @@ interface IDripCheck {
// possible to easily encode parameters on the client side. Solidity does not support generics // 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. // so it's not possible to do this with explicit typing.
/** /// @notice Checks whether a drip should be executable.
* @notice Checks whether a drip should be executable. /// @param _params Encoded parameters for the drip check.
* /// @return execute_ Whether the drip should be executed.
* @param _params Encoded parameters for the drip check. function check(bytes memory _params) external view returns (bool execute_);
*
* @return Whether the drip should be executed.
*/
function check(bytes memory _params) external view returns (bool);
} }
...@@ -3,30 +3,23 @@ pragma solidity 0.8.15; ...@@ -3,30 +3,23 @@ pragma solidity 0.8.15;
import { IDripCheck } from "../IDripCheck.sol"; import { IDripCheck } from "../IDripCheck.sol";
/** /// @title CheckBalanceHigh
* @title CheckBalanceHigh /// @notice DripCheck for checking if an account's balance is above a given threshold.
* @notice DripCheck for checking if an account's balance is above a given threshold.
*/
contract CheckBalanceHigh is IDripCheck { contract CheckBalanceHigh is IDripCheck {
struct Params { struct Params {
address target; address target;
uint256 threshold; uint256 threshold;
} }
/** /// @notice External event used to help client-side tooling encode parameters.
* @notice External event used to help client-side tooling encode parameters. /// @param params Parameters to encode.
*
* @param params Parameters to encode.
*/
event _EventToExposeStructInABI__Params(Params params); event _EventToExposeStructInABI__Params(Params params);
/** /// @inheritdoc IDripCheck
* @inheritdoc IDripCheck function check(bytes memory _params) external view returns (bool execute_) {
*/
function check(bytes memory _params) external view returns (bool) {
Params memory params = abi.decode(_params, (Params)); Params memory params = abi.decode(_params, (Params));
// Check target balance is above threshold. // 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; ...@@ -3,30 +3,23 @@ pragma solidity 0.8.15;
import { IDripCheck } from "../IDripCheck.sol"; import { IDripCheck } from "../IDripCheck.sol";
/** /// @title CheckBalanceLow
* @title CheckBalanceLow /// @notice DripCheck for checking if an account's balance is below a given threshold.
* @notice DripCheck for checking if an account's balance is below a given threshold.
*/
contract CheckBalanceLow is IDripCheck { contract CheckBalanceLow is IDripCheck {
struct Params { struct Params {
address target; address target;
uint256 threshold; uint256 threshold;
} }
/** /// @notice External event used to help client-side tooling encode parameters.
* @notice External event used to help client-side tooling encode parameters. /// @param params Parameters to encode.
*
* @param params Parameters to encode.
*/
event _EventToExposeStructInABI__Params(Params params); event _EventToExposeStructInABI__Params(Params params);
/** /// @inheritdoc IDripCheck
* @inheritdoc IDripCheck function check(bytes memory _params) external view returns (bool execute_) {
*/
function check(bytes memory _params) external view returns (bool) {
Params memory params = abi.decode(_params, (Params)); Params memory params = abi.decode(_params, (Params));
// Check target ETH balance is below threshold. // 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 { ...@@ -7,10 +7,8 @@ interface IGelatoTreasury {
function userTokenBalance(address _user, address _token) external view returns (uint256); function userTokenBalance(address _user, address _token) external view returns (uint256);
} }
/** /// @title CheckGelatoLow
* @title CheckGelatoLow /// @notice DripCheck for checking if an account's Gelato ETH balance is below some threshold.
* @notice DripCheck for checking if an account's Gelato ETH balance is below some threshold.
*/
contract CheckGelatoLow is IDripCheck { contract CheckGelatoLow is IDripCheck {
struct Params { struct Params {
address treasury; address treasury;
...@@ -18,25 +16,21 @@ contract CheckGelatoLow is IDripCheck { ...@@ -18,25 +16,21 @@ contract CheckGelatoLow is IDripCheck {
address recipient; address recipient;
} }
/** /// @notice External event used to help client-side tooling encode parameters.
* @notice External event used to help client-side tooling encode parameters. /// @param params Parameters to encode.
*
* @param params Parameters to encode.
*/
event _EventToExposeStructInABI__Params(Params params); event _EventToExposeStructInABI__Params(Params params);
/** /// @inheritdoc IDripCheck
* @inheritdoc IDripCheck function check(bytes memory _params) external view returns (bool execute_) {
*/
function check(bytes memory _params) external view returns (bool) {
Params memory params = abi.decode(_params, (Params)); Params memory params = abi.decode(_params, (Params));
// Check GelatoTreasury ETH balance is below threshold. // Check GelatoTreasury ETH balance is below threshold.
return execute_ =
IGelatoTreasury(params.treasury).userTokenBalance( IGelatoTreasury(params.treasury).userTokenBalance(
params.recipient, params.recipient,
// Gelato represents ETH as 0xeeeee....eeeee // Gelato represents ETH as 0xeeeee....eeeee
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
) < params.threshold; ) <
params.threshold;
} }
} }
...@@ -3,15 +3,11 @@ pragma solidity 0.8.15; ...@@ -3,15 +3,11 @@ pragma solidity 0.8.15;
import { IDripCheck } from "../IDripCheck.sol"; import { IDripCheck } from "../IDripCheck.sol";
/** /// @title CheckTrue
* @title CheckTrue /// @notice DripCheck that always returns true.
* @notice DripCheck that always returns true.
*/
contract CheckTrue is IDripCheck { contract CheckTrue is IDripCheck {
/** /// @inheritdoc IDripCheck
* @inheritdoc IDripCheck function check(bytes memory) external pure returns (bool execute_) {
*/ execute_ = true;
function check(bytes memory) external pure returns (bool) {
return true;
} }
} }
...@@ -3,32 +3,23 @@ pragma solidity 0.8.15; ...@@ -3,32 +3,23 @@ pragma solidity 0.8.15;
import { IFaucetAuthModule } from "./authmodules/IFaucetAuthModule.sol"; import { IFaucetAuthModule } from "./authmodules/IFaucetAuthModule.sol";
/** /// @title SafeSend
* @title SafeSend /// @notice Sends ETH to a recipient account without triggering any code.
* @notice Sends ETH to a recipient account without triggering any code.
*/
contract SafeSend { contract SafeSend {
/** /// @param _recipient Account to send ETH to.
* @param _recipient Account to send ETH to.
*/
constructor(address payable _recipient) payable { constructor(address payable _recipient) payable {
selfdestruct(_recipient); selfdestruct(_recipient);
} }
} }
/** /// @title Faucet
* @title Faucet /// @notice Faucet contract that drips ETH to users.
* @notice Faucet contract that drips ETH to users.
*/
contract Faucet { contract Faucet {
/** /// @notice Emitted on each 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 authModule The type of authentication that was used for verifying the drip. /// @param amount The amount of funds sent.
* @param userId The id of the user that requested the drip. /// @param recipient The recipient of the drip.
* @param amount The amount of funds sent.
* @param recipient The recipient of the drip.
*/
event Drip( event Drip(
string indexed authModule, string indexed authModule,
bytes32 indexed userId, bytes32 indexed userId,
...@@ -36,26 +27,20 @@ contract Faucet { ...@@ -36,26 +27,20 @@ contract Faucet {
address indexed recipient address indexed recipient
); );
/** /// @notice Parameters for a drip.
* @notice Parameters for a drip.
*/
struct DripParameters { struct DripParameters {
address payable recipient; address payable recipient;
bytes32 nonce; bytes32 nonce;
} }
/** /// @notice Parameters for authentication.
* @notice Parameters for authentication.
*/
struct AuthParameters { struct AuthParameters {
IFaucetAuthModule module; IFaucetAuthModule module;
bytes32 id; bytes32 id;
bytes proof; bytes proof;
} }
/** /// @notice Configuration for an authentication module.
* @notice Configuration for an authentication module.
*/
struct ModuleConfig { struct ModuleConfig {
string name; string name;
bool enabled; bool enabled;
...@@ -63,74 +48,51 @@ contract Faucet { ...@@ -63,74 +48,51 @@ contract Faucet {
uint256 amount; uint256 amount;
} }
/** /// @notice Admin address that can configure the faucet.
* @notice Admin address that can configure the faucet.
*/
address public immutable ADMIN; 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; 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; 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; 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() { modifier priviledged() {
require(msg.sender == ADMIN, "Faucet: function can only be called by admin"); 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) { constructor(address _admin) {
ADMIN = _admin; ADMIN = _admin;
} }
/** /// @notice Allows users to donate ETH to this contract.
* @notice Allows users to donate ETH to this contract.
*/
receive() external payable { receive() external payable {
// Thank you! // Thank you!
} }
/** /// @notice Allows the admin to withdraw funds.
* @notice Allows the admin to withdraw funds. /// @param _recipient Address to receive the funds.
* /// @param _amount Amount of ETH in wei to withdraw.
* @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 { function withdraw(address payable _recipient, uint256 _amount) public priviledged {
new SafeSend{ value: _amount }(_recipient); new SafeSend{ value: _amount }(_recipient);
} }
/** /// @notice Allows the admin to configure an authentication module.
* @notice Allows the admin to configure an authentication module. /// @param _module Authentication module to configure.
* /// @param _config Configuration to set for the module.
* @param _module Authentication module to configure.
* @param _config Configuration to set for the module.
*/
function configure(IFaucetAuthModule _module, ModuleConfig memory _config) public priviledged { function configure(IFaucetAuthModule _module, ModuleConfig memory _config) public priviledged {
modules[_module] = _config; modules[_module] = _config;
} }
/** /// @notice Drips ETH to a recipient account.
* @notice Drips ETH to a recipient account. /// @param _params Drip parameters.
* /// @param _auth Authentication parameters.
* @param _params Drip parameters.
* @param _auth Authentication parameters.
*/
function drip(DripParameters memory _params, AuthParameters memory _auth) public { function drip(DripParameters memory _params, AuthParameters memory _auth) public {
// Grab the module config once. // Grab the module config once.
ModuleConfig memory config = modules[_auth.module]; ModuleConfig memory config = modules[_auth.module];
......
...@@ -6,41 +6,30 @@ import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/Sig ...@@ -6,41 +6,30 @@ import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/Sig
import { IFaucetAuthModule } from "./IFaucetAuthModule.sol"; import { IFaucetAuthModule } from "./IFaucetAuthModule.sol";
import { Faucet } from "../Faucet.sol"; import { Faucet } from "../Faucet.sol";
/** /// @title AdminFaucetAuthModule
* @title AdminFaucetAuthModule /// @notice FaucetAuthModule that allows an admin to sign off on a given faucet drip. Takes an admin
* @notice FaucetAuthModule that allows an admin to sign off on a given faucet drip. Takes an admin /// as the constructor argument.
* as the constructor argument.
*/
contract AdminFaucetAuthModule is IFaucetAuthModule, EIP712 { 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; address public immutable ADMIN;
/** /// @notice EIP712 typehash for the Proof type.
* @notice EIP712 typehash for the Proof type.
*/
bytes32 public constant PROOF_TYPEHASH = bytes32 public constant PROOF_TYPEHASH =
keccak256("Proof(address recipient,bytes32 nonce,bytes32 id)"); keccak256("Proof(address recipient,bytes32 nonce,bytes32 id)");
/** /// @notice Struct that represents a proof that verifies the admin.
* @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 recipient Address that will be receiving the faucet funds. /// @custom:field id id for the user requesting 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 { struct Proof {
address recipient; address recipient;
bytes32 nonce; bytes32 nonce;
bytes32 id; bytes32 id;
} }
/** /// @param _admin Admin address that can sign off on drips.
* @param _admin Admin address that can sign off on drips. /// @param _name Contract name.
* @param _name Contract name. /// @param _version The current major version of the signing domain.
* @param _version The current major version of the signing domain.
*/
constructor( constructor(
address _admin, address _admin,
string memory _name, string memory _name,
...@@ -49,22 +38,19 @@ contract AdminFaucetAuthModule is IFaucetAuthModule, EIP712 { ...@@ -49,22 +38,19 @@ contract AdminFaucetAuthModule is IFaucetAuthModule, EIP712 {
ADMIN = _admin; ADMIN = _admin;
} }
/** /// @inheritdoc IFaucetAuthModule
* @inheritdoc IFaucetAuthModule
*/
function verify( function verify(
Faucet.DripParameters memory _params, Faucet.DripParameters memory _params,
bytes32 _id, bytes32 _id,
bytes memory _proof bytes memory _proof
) external view returns (bool) { ) external view returns (bool valid_) {
// Generate a EIP712 typed data hash to compare against the proof. // Generate a EIP712 typed data hash to compare against the proof.
return valid_ = SignatureChecker.isValidSignatureNow(
SignatureChecker.isValidSignatureNow( ADMIN,
ADMIN, _hashTypedDataV4(
_hashTypedDataV4( keccak256(abi.encode(PROOF_TYPEHASH, _params.recipient, _params.nonce, _id))
keccak256(abi.encode(PROOF_TYPEHASH, _params.recipient, _params.nonce, _id)) ),
), _proof
_proof );
);
} }
} }
...@@ -3,18 +3,14 @@ pragma solidity 0.8.15; ...@@ -3,18 +3,14 @@ pragma solidity 0.8.15;
import { Faucet } from "../Faucet.sol"; import { Faucet } from "../Faucet.sol";
/** /// @title IFaucetAuthModule
* @title IFaucetAuthModule /// @notice Interface for faucet authentication modules.
* @notice Interface for faucet authentication modules.
*/
interface IFaucetAuthModule { interface IFaucetAuthModule {
/** /// @notice Verifies that the given drip parameters are valid.
* @notice Verifies that the given drip parameters are valid. /// @param _params Drip parameters to verify.
* /// @param _id Authentication ID to verify.
* @param _params Drip parameters to verify. /// @param _proof Authentication proof to verify.
* @param _id Authentication ID to verify. /// @return valid_ True if the drip parameters are valid.
* @param _proof Authentication proof to verify.
*/
function verify( function verify(
Faucet.DripParameters memory _params, Faucet.DripParameters memory _params,
bytes32 _id, bytes32 _id,
......
...@@ -3,39 +3,29 @@ pragma solidity 0.8.15; ...@@ -3,39 +3,29 @@ pragma solidity 0.8.15;
import { Semver } from "../../universal/Semver.sol"; import { Semver } from "../../universal/Semver.sol";
/** /// @title AttestationStation
* @title AttestationStation /// @author Optimism Collective
* @author Optimism Collective /// @author Gitcoin
* @author Gitcoin /// @notice Where attestations live.
* @notice Where attestations live.
*/
contract AttestationStation is Semver { contract AttestationStation is Semver {
/** /// @notice Struct representing data that is being attested.
* @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 about Address for which the attestation is about. /// @custom:field val The attestation as arbitrary bytes.
* @custom:field key A bytes32 key for the attestation.
* @custom:field val The attestation as arbitrary bytes.
*/
struct AttestationData { struct AttestationData {
address about; address about;
bytes32 key; bytes32 key;
bytes val; 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; mapping(address => mapping(address => mapping(bytes32 => bytes))) public attestations;
/** /// @notice Emitted when Attestation is created.
* @notice Emitted when Attestation is created. /// @param creator Address that made the attestation.
* /// @param about Address attestation is about.
* @param creator Address that made the attestation. /// @param key Key of the attestation.
* @param about Address attestation is about. /// @param val Value of the attestation.
* @param key Key of the attestation.
* @param val Value of the attestation.
*/
event AttestationCreated( event AttestationCreated(
address indexed creator, address indexed creator,
address indexed about, address indexed about,
...@@ -43,18 +33,13 @@ contract AttestationStation is Semver { ...@@ -43,18 +33,13 @@ contract AttestationStation is Semver {
bytes val bytes val
); );
/** /// @custom:semver 1.1.0
* @custom:semver 1.1.0
*/
constructor() Semver(1, 1, 0) {} constructor() Semver(1, 1, 0) {}
/** /// @notice Allows anyone to create an 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 _about Address that the attestation is about. /// @param _val An arbitrary value stored as part of the attestation.
* @param _key A key used to namespace the attestation.
* @param _val An arbitrary value stored as part of the attestation.
*/
function attest( function attest(
address _about, address _about,
bytes32 _key, bytes32 _key,
...@@ -65,11 +50,8 @@ contract AttestationStation is Semver { ...@@ -65,11 +50,8 @@ contract AttestationStation is Semver {
emit AttestationCreated(msg.sender, _about, _key, _val); emit AttestationCreated(msg.sender, _about, _key, _val);
} }
/** /// @notice Allows anyone to create attestations.
* @notice Allows anyone to create attestations. /// @param _attestations An array of AttestationData structs.
*
* @param _attestations An array of AttestationData structs.
*/
function attest(AttestationData[] calldata _attestations) external { function attest(AttestationData[] calldata _attestations) external {
uint256 length = _attestations.length; uint256 length = _attestations.length;
for (uint256 i = 0; i < length; ) { for (uint256 i = 0; i < length; ) {
......
...@@ -9,41 +9,29 @@ import { AttestationStation } from "./AttestationStation.sol"; ...@@ -9,41 +9,29 @@ import { AttestationStation } from "./AttestationStation.sol";
import { OptimistAllowlist } from "./OptimistAllowlist.sol"; import { OptimistAllowlist } from "./OptimistAllowlist.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
/** /// @author Optimism Collective
* @author Optimism Collective /// @author Gitcoin
* @author Gitcoin /// @title Optimist
* @title Optimist /// @notice A Soul Bound Token for real humans only(tm).
* @notice A Soul Bound Token for real humans only(tm).
*/
contract Optimist is ERC721BurnableUpgradeable, Semver { 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"); 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; address public immutable BASE_URI_ATTESTOR;
/** /// @notice Address of the AttestationStation contract.
* @notice Address of the AttestationStation contract.
*/
AttestationStation public immutable ATTESTATION_STATION; AttestationStation public immutable ATTESTATION_STATION;
/** /// @notice Address of the OptimistAllowlist contract.
* @notice Address of the OptimistAllowlist contract.
*/
OptimistAllowlist public immutable OPTIMIST_ALLOWLIST; OptimistAllowlist public immutable OPTIMIST_ALLOWLIST;
/** /// @custom:semver 2.0.0
* @custom:semver 2.0.0 /// @param _name Token name.
* @param _name Token name. /// @param _symbol Token symbol.
* @param _symbol Token symbol. /// @param _baseURIAttestor Address of the baseURI attestor.
* @param _baseURIAttestor Address of the baseURI attestor. /// @param _attestationStation Address of the AttestationStation contract.
* @param _attestationStation Address of the AttestationStation contract. /// @param _optimistAllowlist Address of the OptimistAllowlist contract
* @param _optimistAllowlist Address of the OptimistAllowlist contract
*/
constructor( constructor(
string memory _name, string memory _name,
string memory _symbol, string memory _symbol,
...@@ -57,109 +45,81 @@ contract Optimist is ERC721BurnableUpgradeable, Semver { ...@@ -57,109 +45,81 @@ contract Optimist is ERC721BurnableUpgradeable, Semver {
initialize(_name, _symbol); initialize(_name, _symbol);
} }
/** /// @notice Initializes the Optimist contract.
* @notice Initializes the Optimist contract. /// @param _name Token name.
* /// @param _symbol Token symbol.
* @param _name Token name.
* @param _symbol Token symbol.
*/
function initialize(string memory _name, string memory _symbol) public initializer { function initialize(string memory _name, string memory _symbol) public initializer {
__ERC721_init(_name, _symbol); __ERC721_init(_name, _symbol);
__ERC721Burnable_init(); __ERC721Burnable_init();
} }
/** /// @notice Allows an address to mint an Optimist NFT. Token ID is the uint256 representation
* @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
* of the recipient's address. Recipients must be permitted to mint, eventually anyone /// will be able to mint. One token per address.
* will be able to mint. One token per address. /// @param _recipient Address of the token recipient.
*
* @param _recipient Address of the token recipient.
*/
function mint(address _recipient) public { function mint(address _recipient) public {
require(isOnAllowList(_recipient), "Optimist: address is not on allowList"); require(isOnAllowList(_recipient), "Optimist: address is not on allowList");
_safeMint(_recipient, tokenIdOfAddress(_recipient)); _safeMint(_recipient, tokenIdOfAddress(_recipient));
} }
/** /// @notice Returns the baseURI for all tokens.
* @notice Returns the baseURI for all tokens. /// @return uri_ BaseURI for all tokens.
* function baseURI() public view returns (string memory uri_) {
* @return BaseURI for all tokens. uri_ = string(
*/ abi.encodePacked(
function baseURI() public view returns (string memory) { ATTESTATION_STATION.attestations(
return BASE_URI_ATTESTOR,
string( address(this),
abi.encodePacked( bytes32("optimist.base-uri")
ATTESTATION_STATION.attestations(
BASE_URI_ATTESTOR,
address(this),
bytes32("optimist.base-uri")
)
) )
); )
);
} }
/** /// @notice Returns the token URI for a given token by ID
* @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.
* @param _tokenId Token ID to query. function tokenURI(uint256 _tokenId) public view virtual override returns (string memory uri_) {
uri_ = string(
* @return Token URI for the given token by ID. abi.encodePacked(
*/ baseURI(),
function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) { "/",
return // Properly format the token ID as a 20 byte hex string (address).
string( Strings.toHexString(_tokenId, 20),
abi.encodePacked( ".json"
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
* @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
* 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
* Citizens House, mints are currently restricted. Eventually anyone will be able /// to mint.
* to mint. /// @return allowed_ Whether or not the address is allowed to mint yet.
* function isOnAllowList(address _recipient) public view returns (bool allowed_) {
* @return Whether or not the address is allowed to mint yet. allowed_ = OPTIMIST_ALLOWLIST.isAllowedToMint(_recipient);
*/
function isOnAllowList(address _recipient) public view returns (bool) {
return OPTIMIST_ALLOWLIST.isAllowedToMint(_recipient);
} }
/** /// @notice Returns the token ID for the token owned by a given address. This is the uint256
* @notice Returns the token ID for the token owned by a given address. This is the uint256 /// representation of the given address.
* representation of the given address. /// @return Token ID for the token owned by the given address.
*
* @return Token ID for the token owned by the given address.
*/
function tokenIdOfAddress(address _owner) public pure returns (uint256) { function tokenIdOfAddress(address _owner) public pure returns (uint256) {
return uint256(uint160(_owner)); 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 { function approve(address, uint256) public pure override {
revert("Optimist: soul bound token"); 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 { function setApprovalForAll(address, bool) public virtual override {
revert("Optimist: soul bound token"); revert("Optimist: soul bound token");
} }
/** /// @notice Prevents transfers of the Optimist NFT (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.
* @param _from Address of the token sender.
* @param _to Address of the token recipient.
*/
function _beforeTokenTransfer( function _beforeTokenTransfer(
address _from, address _from,
address _to, address _to,
......
...@@ -5,54 +5,37 @@ import { Semver } from "../../universal/Semver.sol"; ...@@ -5,54 +5,37 @@ import { Semver } from "../../universal/Semver.sol";
import { AttestationStation } from "./AttestationStation.sol"; import { AttestationStation } from "./AttestationStation.sol";
import { OptimistConstants } from "./libraries/OptimistConstants.sol"; import { OptimistConstants } from "./libraries/OptimistConstants.sol";
/** /// @title OptimistAllowlist
* @title OptimistAllowlist /// @notice Source of truth for whether an address is able to mint an Optimist NFT.
* @notice Source of truth for whether an address is able to mint an Optimist NFT. /// isAllowedToMint function checks various signals to return boolean value
isAllowedToMint function checks various signals to return boolean value for whether an /// for whether an address is eligible or not.
address is eligible or not.
*/
contract OptimistAllowlist is Semver { contract OptimistAllowlist is Semver {
/** /// @notice Attestation key used by the AllowlistAttestor to manually add addresses to the
* @notice Attestation key used by the AllowlistAttestor to manually add addresses to the /// allowlist.
* allowlist.
*/
bytes32 public constant OPTIMIST_CAN_MINT_ATTESTATION_KEY = bytes32("optimist.can-mint"); 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 public constant COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY =
bytes32("coinbase.quest-eligible"); bytes32("coinbase.quest-eligible");
/** /// @notice Address of the AttestationStation contract.
* @notice Address of the AttestationStation contract.
*/
AttestationStation public immutable ATTESTATION_STATION; 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; 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; address public immutable COINBASE_QUEST_ATTESTOR;
/** /// @notice Address of OptimistInviter contract that issues 'optimist.can-mint-from-invite'
* @notice Address of OptimistInviter contract that issues 'optimist.can-mint-from-invite' /// attestations.
* attestations.
*/
address public immutable OPTIMIST_INVITER; address public immutable OPTIMIST_INVITER;
/** /// @custom:semver 1.0.0
* @custom:semver 1.0.0 /// @param _attestationStation Address of the AttestationStation contract.
* /// @param _allowlistAttestor Address of the allowlist attestor.
* @param _attestationStation Address of the AttestationStation contract. /// @param _coinbaseQuestAttestor Address of the Coinbase Quest attestor.
* @param _allowlistAttestor Address of the allowlist attestor. /// @param _optimistInviter Address of the OptimistInviter contract.
* @param _coinbaseQuestAttestor Address of the Coinbase Quest attestor.
* @param _optimistInviter Address of the OptimistInviter contract.
*/
constructor( constructor(
AttestationStation _attestationStation, AttestationStation _attestationStation,
address _allowlistAttestor, address _allowlistAttestor,
...@@ -65,95 +48,83 @@ contract OptimistAllowlist is Semver { ...@@ -65,95 +48,83 @@ contract OptimistAllowlist is Semver {
OPTIMIST_INVITER = _optimistInviter; OPTIMIST_INVITER = _optimistInviter;
} }
/** /// @notice Checks whether a given address is allowed to mint the Optimist NFT yet. Since the
* @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
* Optimist NFT will also be used as part of the Citizens House, mints are currently /// restricted. Eventually anyone will be able to mint.
* 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.
* Currently, address is allowed to mint if it satisfies any of the following: /// 2) Has a valid 'coinbase.quest-eligible' attestation from Coinbase Quest attestor
* 1) Has a valid 'optimist.can-mint' attestation from the allowlist attestor. /// 3) Has a valid 'optimist.can-mint-from-invite' attestation from the OptimistInviter
* 2) Has a valid 'coinbase.quest-eligible' attestation from Coinbase Quest attestor /// contract.
* 3) Has a valid 'optimist.can-mint-from-invite' attestation from the OptimistInviter /// @param _claimer Address to check.
* contract. /// @return allowed_ Whether or not the address is allowed to mint yet.
* function isAllowedToMint(address _claimer) public view returns (bool allowed_) {
* @param _claimer Address to check. allowed_ =
*
* @return Whether or not the address is allowed to mint yet.
*/
function isAllowedToMint(address _claimer) public view returns (bool) {
return
_hasAttestationFromAllowlistAttestor(_claimer) || _hasAttestationFromAllowlistAttestor(_claimer) ||
_hasAttestationFromCoinbaseQuestAttestor(_claimer) || _hasAttestationFromCoinbaseQuestAttestor(_claimer) ||
_hasAttestationFromOptimistInviter(_claimer); _hasAttestationFromOptimistInviter(_claimer);
} }
/** /// @notice Checks whether an address has a valid 'optimist.can-mint' attestation from the
* @notice Checks whether an address has a valid 'optimist.can-mint' attestation from the /// allowlist attestor.
* allowlist attestor. /// @param _claimer Address to check.
* /// @return valid_ Whether or not the address has a valid attestation.
* @param _claimer Address to check. function _hasAttestationFromAllowlistAttestor(address _claimer)
* internal
* @return Whether or not the address has a valid attestation. view
*/ returns (bool valid_)
function _hasAttestationFromAllowlistAttestor(address _claimer) internal view returns (bool) { {
// Expected attestation value is bytes32("true") // Expected attestation value is bytes32("true")
return valid_ = _hasValidAttestation(
_hasValidAttestation(ALLOWLIST_ATTESTOR, _claimer, OPTIMIST_CAN_MINT_ATTESTATION_KEY); ALLOWLIST_ATTESTOR,
_claimer,
OPTIMIST_CAN_MINT_ATTESTATION_KEY
);
} }
/** /// @notice Checks whether an address has a valid attestation from the Coinbase attestor.
* @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.
* @param _claimer Address to check.
*
* @return Whether or not the address has a valid attestation.
*/
function _hasAttestationFromCoinbaseQuestAttestor(address _claimer) function _hasAttestationFromCoinbaseQuestAttestor(address _claimer)
internal internal
view view
returns (bool) returns (bool valid_)
{ {
// Expected attestation value is bytes32("true") // Expected attestation value is bytes32("true")
return valid_ = _hasValidAttestation(
_hasValidAttestation( COINBASE_QUEST_ATTESTOR,
COINBASE_QUEST_ATTESTOR, _claimer,
_claimer, COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY
COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY );
);
} }
/** /// @notice Checks whether an address has a valid attestation from the OptimistInviter contract.
* @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.
* @param _claimer Address to check. function _hasAttestationFromOptimistInviter(address _claimer)
* internal
* @return Whether or not the address has a valid attestation. view
*/ returns (bool valid_)
function _hasAttestationFromOptimistInviter(address _claimer) internal view returns (bool) { {
// Expected attestation value is the inviter's address // Expected attestation value is the inviter's address
return valid_ = _hasValidAttestation(
_hasValidAttestation( OPTIMIST_INVITER,
OPTIMIST_INVITER, _claimer,
_claimer, OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY
OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY );
);
} }
/** /// @notice Checks whether an 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.
* Any attestation val other than bytes32("") is considered truthy. /// @param _creator Address that made the attestation.
* /// @param _about Address attestation is about.
* @param _creator Address that made the attestation. /// @param _key Key of the attestation.
* @param _about Address attestation is about. /// @return valid_ Whether or not the address has a valid truthy attestation.
* @param _key Key of the attestation.
*
* @return Whether or not the address has a valid truthy attestation.
*/
function _hasValidAttestation( function _hasValidAttestation(
address _creator, address _creator,
address _about, address _about,
bytes32 _key bytes32 _key
) internal view returns (bool) { ) internal view returns (bool valid_) {
return ATTESTATION_STATION.attestations(_creator, _about, _key).length > 0; valid_ = ATTESTATION_STATION.attestations(_creator, _about, _key).length > 0;
} }
} }
...@@ -9,144 +9,108 @@ import { ...@@ -9,144 +9,108 @@ import {
EIP712Upgradeable EIP712Upgradeable
} from "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol"; } from "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol";
/** /// @custom:upgradeable
* @custom:upgradeable /// @title OptimistInviter
* @title OptimistInviter /// @notice OptimistInviter issues "optimist.can-invite" and "optimist.can-mint-from-invite"
* @notice OptimistInviter issues "optimist.can-invite" and "optimist.can-mint-from-invite" /// attestations. Accounts that have invites can issue signatures that allow other
* attestations. Accounts that have invites can issue signatures that allow other /// accounts to claim an invite. The invitee uses a claim and reveal flow to claim the
* accounts to claim an invite. The invitee uses a claim and reveal flow to claim the /// invite to an address of their choosing.
* invite to an address of their choosing. ///
* /// Parties involved:
* Parties involved: /// 1) INVITE_GRANTER: trusted account that can allow accounts to issue invites
* 1) INVITE_GRANTER: trusted account that can allow accounts to issue invites /// 2) issuer: account that is allowed to issue invites
* 2) issuer: account that is allowed to issue invites /// 3) claimer: account that receives the invites
* 3) claimer: account that receives the invites ///
* /// Flow:
* Flow: /// 1) INVITE_GRANTER calls _setInviteCount to allow an issuer to issue a certain number
* 1) INVITE_GRANTER calls _setInviteCount to allow an issuer to issue a certain number /// of invites, and also creates a "optimist.can-invite" attestation for the issuer
* of invites, and also creates a "optimist.can-invite" attestation for the issuer /// 2) Off-chain, the issuer signs (EIP-712) a ClaimableInvite to produce a signature
* 2) Off-chain, the issuer signs (EIP-712) a ClaimableInvite to produce a signature /// 3) Off-chain, invite issuer sends the plaintext ClaimableInvite and the signature
* 3) Off-chain, invite issuer sends the plaintext ClaimableInvite and the signature /// to the recipient
* to the recipient /// 4) claimer chooses an address they want to receive the invite on
* 4) claimer chooses an address they want to receive the invite on /// 5) claimer commits the hash of the address they want to receive the invite on and the
* 5) claimer commits the hash of the address they want to receive the invite on and the /// received signature keccak256(abi.encode(addressToReceiveTo, receivedSignature))
* received signature keccak256(abi.encode(addressToReceiveTo, receivedSignature)) /// using the commitInvite function
* using the commitInvite function /// 6) claimer waits for the MIN_COMMITMENT_PERIOD to pass.
* 6) claimer waits for the MIN_COMMITMENT_PERIOD to pass. /// 7) claimer reveals the plaintext ClaimableInvite and the signature using the
* 7) claimer reveals the plaintext ClaimableInvite and the signature using the /// claimInvite function, receiving the "optimist.can-mint-from-invite" attestation
* claimInvite function, receiving the "optimist.can-mint-from-invite" attestation
*/
contract OptimistInviter is Semver, EIP712Upgradeable { contract OptimistInviter is Semver, EIP712Upgradeable {
/** /// @notice Emitted when an invite is claimed.
* @notice Emitted when an invite is claimed. /// @param issuer Address that issued the signature.
* /// @param claimer Address that claimed the invite.
* @param issuer Address that issued the signature.
* @param claimer Address that claimed the invite.
*/
event InviteClaimed(address indexed issuer, address indexed claimer); event InviteClaimed(address indexed issuer, address indexed claimer);
/** /// @notice Version used for the EIP712 domain separator. This version is separated from the
* @notice Version used for the EIP712 domain separator. This version is separated from the /// contract semver because the EIP712 domain separator is used to sign messages, and
* contract semver because the EIP712 domain separator is used to sign messages, and /// changing the domain separator invalidates all existing signatures. We should only
* changing the domain separator invalidates all existing signatures. We should only /// bump this version if we make a major change to the signature scheme.
* bump this version if we make a major change to the signature scheme.
*/
string public constant EIP712_VERSION = "1.0.0"; string public constant EIP712_VERSION = "1.0.0";
/** /// @notice EIP712 typehash for the ClaimableInvite type.
* @notice EIP712 typehash for the ClaimableInvite type.
*/
bytes32 public constant CLAIMABLE_INVITE_TYPEHASH = bytes32 public constant CLAIMABLE_INVITE_TYPEHASH =
keccak256("ClaimableInvite(address issuer,bytes32 nonce)"); keccak256("ClaimableInvite(address issuer,bytes32 nonce)");
/** /// @notice Attestation key for that signals that an account was allowed to issue invites
* @notice Attestation key for that signals that an account was allowed to issue invites
*/
bytes32 public constant CAN_INVITE_ATTESTATION_KEY = bytes32("optimist.can-invite"); bytes32 public constant CAN_INVITE_ATTESTATION_KEY = bytes32("optimist.can-invite");
/** /// @notice Granter who can set accounts' invite counts.
* @notice Granter who can set accounts' invite counts.
*/
address public immutable INVITE_GRANTER; address public immutable INVITE_GRANTER;
/** /// @notice Address of the AttestationStation contract.
* @notice Address of the AttestationStation contract.
*/
AttestationStation public immutable ATTESTATION_STATION; AttestationStation public immutable ATTESTATION_STATION;
/** /// @notice Minimum age of a commitment (in seconds) before it can be revealed using
* @notice Minimum age of a commitment (in seconds) before it can be revealed using claimInvite. /// claimInvite. Currently set to 60 seconds.
* Currently set to 60 seconds. ///
* /// Prevents an attacker from front-running a commitment by taking the signature in the
* Prevents an attacker from front-running a commitment by taking the signature in the /// claimInvite call and quickly committing and claiming it before the the claimer's
* claimInvite call and quickly committing and claiming it before the the claimer's /// transaction succeeds. With this, frontrunning a commitment requires that an attacker
* transaction succeeds. With this, frontrunning a commitment requires that an attacker /// be able to prevent the honest claimer's claimInvite transaction from being included
* be able to prevent the honest claimer's claimInvite transaction from being included /// for this long.
* for this long.
*/
uint256 public constant MIN_COMMITMENT_PERIOD = 60; uint256 public constant MIN_COMMITMENT_PERIOD = 60;
/** /// @notice Struct that represents a claimable invite that will be signed by the issuer.
* @notice Struct that represents a claimable invite that will be signed by the issuer. /// @custom:field issuer Address that issued the signature. Reason this is explicitly included,
* /// and not implicitly assumed to be the recovered address from the
* @custom:field issuer Address that issued the signature. Reason this is explicitly included, /// signature is that the issuer may be using a ERC-1271 compatible
* and not implicitly assumed to be the recovered address from the /// contract wallet, where the recovered address is not the same as the
* signature is that the issuer may be using a ERC-1271 compatible /// issuer, or the signature is not an ECDSA signature at all.
* contract wallet, where the recovered address is not the same as the /// @custom:field nonce Pseudorandom nonce to prevent replay attacks.
* issuer, or the signature is not an ECDSA signature at all.
* @custom:field nonce Pseudorandom nonce to prevent replay attacks.
*/
struct ClaimableInvite { struct ClaimableInvite {
address issuer; address issuer;
bytes32 nonce; bytes32 nonce;
} }
/** /// @notice Maps from hashes to the timestamp when they were committed.
* @notice Maps from hashes to the timestamp when they were committed.
*/
mapping(bytes32 => uint256) public commitmentTimestamps; mapping(bytes32 => uint256) public commitmentTimestamps;
/** /// @notice Maps from addresses to nonces to whether or not they have been used.
* @notice Maps from addresses to nonces to whether or not they have been used.
*/
mapping(address => mapping(bytes32 => bool)) public usedNonces; mapping(address => mapping(bytes32 => bool)) public usedNonces;
/** /// @notice Maps from addresses to number of invites they have.
* @notice Maps from addresses to number of invites they have.
*/
mapping(address => uint256) public inviteCounts; mapping(address => uint256) public inviteCounts;
/** /// @custom:semver 1.0.0
* @custom:semver 1.0.0 /// @param _inviteGranter Address of the invite granter.
* /// @param _attestationStation Address of the AttestationStation contract.
* @param _inviteGranter Address of the invite granter.
* @param _attestationStation Address of the AttestationStation contract.
*/
constructor(address _inviteGranter, AttestationStation _attestationStation) Semver(1, 0, 0) { constructor(address _inviteGranter, AttestationStation _attestationStation) Semver(1, 0, 0) {
INVITE_GRANTER = _inviteGranter; INVITE_GRANTER = _inviteGranter;
ATTESTATION_STATION = _attestationStation; ATTESTATION_STATION = _attestationStation;
} }
/** /// @notice Initializes this contract, setting the EIP712 context.
* @notice Initializes this contract, setting the EIP712 context. /// Only update the EIP712_VERSION when there is a change to the signature scheme.
* /// After the EIP712 version is changed, any signatures issued off-chain but not
* Only update the EIP712_VERSION when there is a change to the signature scheme. /// claimed yet will no longer be accepted by the claimInvite function. Please make
* After the EIP712 version is changed, any signatures issued off-chain but not /// sure to notify the issuers that they must re-issue their invite signatures.
* claimed yet will no longer be accepted by the claimInvite function. Please make /// @param _name Contract name.
* sure to notify the issuers that they must re-issue their invite signatures.
*
* @param _name Contract name.
*/
function initialize(string memory _name) public initializer { function initialize(string memory _name) public initializer {
__EIP712_init(_name, EIP712_VERSION); __EIP712_init(_name, EIP712_VERSION);
} }
/** /// @notice Allows invite granter to set the number of invites an address has.
* @notice Allows invite granter to set the number of invites an address has. /// @param _accounts An array of accounts to update the invite counts of.
* /// @param _inviteCount Number of invites to set to.
* @param _accounts An array of accounts to update the invite counts of.
* @param _inviteCount Number of invites to set to.
*/
function setInviteCounts(address[] calldata _accounts, uint256 _inviteCount) public { function setInviteCounts(address[] calldata _accounts, uint256 _inviteCount) public {
// Only invite granter can grant invites // Only invite granter can grant invites
require( require(
...@@ -178,25 +142,21 @@ contract OptimistInviter is Semver, EIP712Upgradeable { ...@@ -178,25 +142,21 @@ contract OptimistInviter is Semver, EIP712Upgradeable {
ATTESTATION_STATION.attest(attestations); ATTESTATION_STATION.attest(attestations);
} }
/** /// @notice Allows anyone (but likely the claimer) to commit a received signature along with the
* @notice Allows anyone (but likely the claimer) to commit a received signature along with the /// address to claim to.
* address to claim to. ///
* /// Before calling this function, the claimer should have received a signature from the
* Before calling this function, the claimer should have received a signature from the /// issuer off-chain. The claimer then calls this function with the hash of the
* issuer off-chain. The claimer then calls this function with the hash of the /// claimer's address and the received signature. This is necessary to prevent
* claimer's address and the received signature. This is necessary to prevent /// front-running when the invitee is claiming the invite. Without a commit and reveal
* front-running when the invitee is claiming the invite. Without a commit and reveal /// scheme, anyone who is watching the mempool can take the signature being submitted
* scheme, anyone who is watching the mempool can take the signature being submitted /// and front run the transaction to claim the invite to their own address.
* and front run the transaction to claim the invite to their own address. ///
* /// The same commitment can only be made once, and the function reverts if the
* The same commitment can only be made once, and the function reverts if the /// commitment has already been made. This prevents griefing where a malicious party can
* commitment has already been made. This prevents griefing where a malicious party can /// prevent the original claimer from being able to claimInvite.
* prevent the original claimer from being able to claimInvite. /// @param _commitment A hash of the claimer and signature concatenated.
* /// keccak256(abi.encode(_claimer, _signature))
*
* @param _commitment A hash of the claimer and signature concatenated.
* keccak256(abi.encode(_claimer, _signature))
*/
function commitInvite(bytes32 _commitment) public { function commitInvite(bytes32 _commitment) public {
// Check that the commitment hasn't already been made. This prevents griefing where // Check that the commitment hasn't already been made. This prevents griefing where
// a malicious party continuously re-submits the same commitment, preventing the original // a malicious party continuously re-submits the same commitment, preventing the original
...@@ -206,23 +166,19 @@ contract OptimistInviter is Semver, EIP712Upgradeable { ...@@ -206,23 +166,19 @@ contract OptimistInviter is Semver, EIP712Upgradeable {
commitmentTimestamps[_commitment] = block.timestamp; commitmentTimestamps[_commitment] = block.timestamp;
} }
/** /// @notice Allows anyone to reveal a commitment and claim an invite.
* @notice Allows anyone to reveal a commitment and claim an invite. /// The hash, keccak256(abi.encode(_claimer, _signature)), should have been already
* /// committed using commitInvite. Before issuing the "optimist.can-mint-from-invite"
* The hash, keccak256(abi.encode(_claimer, _signature)), should have been already /// attestation, this function checks that
* committed using commitInvite. Before issuing the "optimist.can-mint-from-invite" /// 1) the hash corresponding to the _claimer and the _signature was committed
* attestation, this function checks that /// 2) MIN_COMMITMENT_PERIOD has passed since the commitment was made.
* 1) the hash corresponding to the _claimer and the _signature was committed /// 3) the _signature is signed correctly by the issuer
* 2) MIN_COMMITMENT_PERIOD has passed since the commitment was made. /// 4) the _signature hasn't already been used to claim an invite before
* 3) the _signature is signed correctly by the issuer /// 5) the _signature issuer has not used up all of their invites
* 4) the _signature hasn't already been used to claim an invite before /// This function doesn't require that the _claimer is calling this function.
* 5) the _signature issuer has not used up all of their invites /// @param _claimer Address that will be granted the invite.
* This function doesn't require that the _claimer is calling this function. /// @param _claimableInvite ClaimableInvite struct containing the issuer and nonce.
* /// @param _signature Signature signed over the claimable invite.
* @param _claimer Address that will be granted the invite.
* @param _claimableInvite ClaimableInvite struct containing the issuer and nonce.
* @param _signature Signature signed over the claimable invite.
*/
function claimInvite( function claimInvite(
address _claimer, address _claimer,
ClaimableInvite calldata _claimableInvite, ClaimableInvite calldata _claimableInvite,
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
/** /// @title OptimistConstants
* @title OptimistConstants /// @notice Library for storing Optimist related constants that are shared in multiple contracts.
* @notice Library for storing Optimist related constants that are shared in multiple contracts.
*/
library OptimistConstants { 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 internal constant OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY =
bytes32("optimist.can-mint-from-invite"); bytes32("optimist.can-mint-from-invite");
} }
...@@ -5,9 +5,7 @@ import { Test } from "forge-std/Test.sol"; ...@@ -5,9 +5,7 @@ import { Test } from "forge-std/Test.sol";
import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol"; import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol";
contract AddressAliasHelper_applyAndUndo_Test is Test { 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 { function testFuzz_applyAndUndo_succeeds(address _address) external {
address aliased = AddressAliasHelper.applyL1ToL2Alias(_address); address aliased = AddressAliasHelper.applyL1ToL2Alias(_address);
address unaliased = AddressAliasHelper.undoL1ToL2Alias(aliased); address unaliased = AddressAliasHelper.undoL1ToL2Alias(aliased);
......
//SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
...@@ -6,41 +6,32 @@ import { AdminFaucetAuthModule } from "../periphery/faucet/authmodules/AdminFauc ...@@ -6,41 +6,32 @@ import { AdminFaucetAuthModule } from "../periphery/faucet/authmodules/AdminFauc
import { Faucet } from "../periphery/faucet/Faucet.sol"; import { Faucet } from "../periphery/faucet/Faucet.sol";
import { FaucetHelper } from "./Helpers.sol"; import { FaucetHelper } from "./Helpers.sol";
/** /// @title AdminFaucetAuthModuleTest
* @title AdminFaucetAuthModuleTest /// @notice Tests the AdminFaucetAuthModule contract.
* @notice Tests the AdminFaucetAuthModule contract.
*/
contract AdminFaucetAuthModuleTest is Test { contract AdminFaucetAuthModuleTest is Test {
/** /// @notice The admin of the `AdminFaucetAuthModule` contract.
* @notice The admin of the `AdminFaucetAuthModule` contract.
*/
address internal admin; address internal admin;
/**
* @notice Private key of the `admin`. /// @notice Private key of the `admin`.
*/
uint256 internal adminKey; uint256 internal adminKey;
/**
* @notice Not an admin of the `AdminFaucetAuthModule` contract. /// @notice Not an admin of the `AdminFaucetAuthModule` contract.
*/
address internal nonAdmin; address internal nonAdmin;
/**
* @notice Private key of the `nonAdmin`. /// @notice Private key of the `nonAdmin`.
*/
uint256 internal nonAdminKey; uint256 internal nonAdminKey;
/**
* @notice An instance of the `AdminFaucetAuthModule` contract. /// @notice An instance of the `AdminFaucetAuthModule` contract.
*/
AdminFaucetAuthModule internal adminFam; AdminFaucetAuthModule internal adminFam;
/**
* @notice An instance of the `FaucetHelper` contract. /// @notice An instance of the `FaucetHelper` contract.
*/
FaucetHelper internal faucetHelper; FaucetHelper internal faucetHelper;
string internal adminFamName = "AdminFAM"; string internal adminFamName = "AdminFAM";
string internal adminFamVersion = "1"; string internal adminFamVersion = "1";
/** /// @notice Deploy the `AdminFaucetAuthModule` contract.
* @notice Deploy the `AdminFaucetAuthModule` contract.
*/
function setUp() external { function setUp() external {
adminKey = 0xB0B0B0B0; adminKey = 0xB0B0B0B0;
admin = vm.addr(adminKey); admin = vm.addr(adminKey);
...@@ -53,10 +44,7 @@ contract AdminFaucetAuthModuleTest is Test { ...@@ -53,10 +44,7 @@ contract AdminFaucetAuthModuleTest is Test {
faucetHelper = new FaucetHelper(); faucetHelper = new FaucetHelper();
} }
/** /// @notice Get signature as a bytes blob.
* @notice Get signature as a bytes blob.
*
*/
function _getSignature(uint256 _signingPrivateKey, bytes32 _digest) function _getSignature(uint256 _signingPrivateKey, bytes32 _digest)
internal internal
pure pure
...@@ -68,11 +56,9 @@ contract AdminFaucetAuthModuleTest is Test { ...@@ -68,11 +56,9 @@ contract AdminFaucetAuthModuleTest is Test {
return signature; return signature;
} }
/** /// @notice Signs a proof with the given private key and returns the signature using
* @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
* the given EIP712 domain separator. This assumes that the issuer's address is the /// corresponding public key to _issuerPrivateKey.
* corresponding public key to _issuerPrivateKey.
*/
function issueProofWithEIP712Domain( function issueProofWithEIP712Domain(
uint256 _issuerPrivateKey, uint256 _issuerPrivateKey,
bytes memory _eip712Name, bytes memory _eip712Name,
...@@ -101,9 +87,7 @@ contract AdminFaucetAuthModuleTest is Test { ...@@ -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 { function test_adminProof_verify_succeeds() external {
bytes32 nonce = faucetHelper.consumeNonce(); bytes32 nonce = faucetHelper.consumeNonce();
address fundsReceiver = makeAddr("fundsReceiver"); address fundsReceiver = makeAddr("fundsReceiver");
...@@ -129,9 +113,7 @@ contract AdminFaucetAuthModuleTest is Test { ...@@ -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 { function test_nonAdminProof_verify_succeeds() external {
bytes32 nonce = faucetHelper.consumeNonce(); bytes32 nonce = faucetHelper.consumeNonce();
address fundsReceiver = makeAddr("fundsReceiver"); address fundsReceiver = makeAddr("fundsReceiver");
...@@ -157,10 +139,8 @@ contract AdminFaucetAuthModuleTest is Test { ...@@ -157,10 +139,8 @@ contract AdminFaucetAuthModuleTest is Test {
); );
} }
/** /// @notice Assert that verify returns false for proofs where the id in the proof is different
* @notice assert that verify returns false for proofs where the id in the proof is different /// than the id in the call to verify.
* than the id in the call to verify.
*/
function test_proofWithWrongId_verify_succeeds() external { function test_proofWithWrongId_verify_succeeds() external {
bytes32 nonce = faucetHelper.consumeNonce(); bytes32 nonce = faucetHelper.consumeNonce();
address fundsReceiver = makeAddr("fundsReceiver"); address fundsReceiver = makeAddr("fundsReceiver");
......
//SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
/* Testing utilities */ // Testing utilities
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { TestERC20 } from "./Helpers.sol"; import { TestERC20 } from "./Helpers.sol";
import { TestERC721 } from "./Helpers.sol"; import { TestERC721 } from "./Helpers.sol";
...@@ -53,12 +53,12 @@ contract AssetReceiver_Initializer is Test { ...@@ -53,12 +53,12 @@ contract AssetReceiver_Initializer is Test {
} }
contract AssetReceiverTest is AssetReceiver_Initializer { 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 { function test_constructor_succeeds() external {
assertEq(address(alice), assetReceiver.owner()); assertEq(address(alice), assetReceiver.owner());
} }
// Tests that receive works as inteded /// @notice Tests that receive works as inteded.
function test_receive_succeeds() external { function test_receive_succeeds() external {
// Check that contract balance is 0 initially // Check that contract balance is 0 initially
assertEq(address(assetReceiver).balance, 0); assertEq(address(assetReceiver).balance, 0);
...@@ -74,7 +74,8 @@ contract AssetReceiverTest is AssetReceiver_Initializer { ...@@ -74,7 +74,8 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(address(assetReceiver).balance, 100); 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 { function test_withdrawETH_succeeds() external {
// Check contract initial balance // Check contract initial balance
assertEq(address(assetReceiver).balance, 0); assertEq(address(assetReceiver).balance, 0);
...@@ -96,14 +97,14 @@ contract AssetReceiverTest is AssetReceiver_Initializer { ...@@ -96,14 +97,14 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(address(alice).balance, 2 ether); 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 { function test_withdrawETH_unauthorized_reverts() external {
vm.deal(address(assetReceiver), 1 ether); vm.deal(address(assetReceiver), 1 ether);
vm.expectRevert("UNAUTHORIZED"); vm.expectRevert("UNAUTHORIZED");
assetReceiver.withdrawETH(payable(alice)); 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 { function test_withdrawETHwithAmount_succeeds() external {
assertEq(address(assetReceiver).balance, 0); assertEq(address(assetReceiver).balance, 0);
...@@ -124,14 +125,14 @@ contract AssetReceiverTest is AssetReceiver_Initializer { ...@@ -124,14 +125,14 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(address(alice).balance, 1.5 ether); 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 { function test_withdrawETHwithAmount_unauthorized_reverts() external {
vm.deal(address(assetReceiver), 1 ether); vm.deal(address(assetReceiver), 1 ether);
vm.expectRevert("UNAUTHORIZED"); vm.expectRevert("UNAUTHORIZED");
assetReceiver.withdrawETH(payable(alice), 0.5 ether); 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 { function test_withdrawERC20_succeeds() external {
// check balances before the call // check balances before the call
assertEq(testERC20.balanceOf(address(assetReceiver)), 0); assertEq(testERC20.balanceOf(address(assetReceiver)), 0);
...@@ -152,14 +153,14 @@ contract AssetReceiverTest is AssetReceiver_Initializer { ...@@ -152,14 +153,14 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(testERC20.balanceOf(address(assetReceiver)), 0); 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 { function test_withdrawERC20_unauthorized_reverts() external {
deal(address(testERC20), address(assetReceiver), 100_000); deal(address(testERC20), address(assetReceiver), 100_000);
vm.expectRevert("UNAUTHORIZED"); vm.expectRevert("UNAUTHORIZED");
assetReceiver.withdrawERC20(testERC20, alice); 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 { function test_withdrawERC20withAmount_succeeds() external {
// check balances before the call // check balances before the call
assertEq(testERC20.balanceOf(address(assetReceiver)), 0); assertEq(testERC20.balanceOf(address(assetReceiver)), 0);
...@@ -180,14 +181,14 @@ contract AssetReceiverTest is AssetReceiver_Initializer { ...@@ -180,14 +181,14 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(testERC20.balanceOf(address(assetReceiver)), 50_000); 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 { function test_withdrawERC20withAmount_unauthorized_reverts() external {
deal(address(testERC20), address(assetReceiver), 100_000); deal(address(testERC20), address(assetReceiver), 100_000);
vm.expectRevert("UNAUTHORIZED"); vm.expectRevert("UNAUTHORIZED");
assetReceiver.withdrawERC20(testERC20, alice, 50_000); assetReceiver.withdrawERC20(testERC20, alice, 50_000);
} }
// Test withdrawERC721 from owner /// @notice Test withdrawERC721 from owner.
function test_withdrawERC721_succeeds() external { function test_withdrawERC721_succeeds() external {
// Check owner of the token before calling withdrawERC721 // Check owner of the token before calling withdrawERC721
assertEq(testERC721.ownerOf(DEFAULT_TOKEN_ID), alice); assertEq(testERC721.ownerOf(DEFAULT_TOKEN_ID), alice);
...@@ -208,7 +209,7 @@ contract AssetReceiverTest is AssetReceiver_Initializer { ...@@ -208,7 +209,7 @@ contract AssetReceiverTest is AssetReceiver_Initializer {
assertEq(testERC721.ownerOf(DEFAULT_TOKEN_ID), alice); 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 { function test_withdrawERC721_unauthorized_reverts() external {
vm.prank(alice); vm.prank(alice);
testERC721.transferFrom(alice, address(assetReceiver), DEFAULT_TOKEN_ID); testERC721.transferFrom(alice, address(assetReceiver), DEFAULT_TOKEN_ID);
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
/* Testing utilities */ // Testing utilities
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { Vm } from "forge-std/Vm.sol"; import { Vm } from "forge-std/Vm.sol";
import "./CommonTest.t.sol"; import "./CommonTest.t.sol";
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
// Testing utilities // Testing utilities
......
//SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { CheckBalanceHigh } from "../periphery/drippie/dripchecks/CheckBalanceHigh.sol"; import { CheckBalanceHigh } from "../periphery/drippie/dripchecks/CheckBalanceHigh.sol";
/** /// @title CheckBalanceHighTest
* @title CheckBalanceHighTest /// @notice Tests the CheckBalanceHigh contract via fuzzing both the success case
* @notice Tests the CheckBalanceHigh contract via fuzzing both the success case /// and the failure case.
* and the failure case.
*/
contract CheckBalanceHighTest is Test { contract CheckBalanceHighTest is Test {
/** /// @notice An instance of the CheckBalanceHigh contract.
* @notice An instance of the CheckBalanceHigh contract.
*/
CheckBalanceHigh c; CheckBalanceHigh c;
/** /// @notice Deploy the `CheckTrue` contract.
* @notice Deploy the `CheckTrue` contract.
*/
function setUp() external { function setUp() external {
c = new CheckBalanceHigh(); c = new CheckBalanceHigh();
} }
/** /// @notice Fuzz the `check` function and assert that it always returns true
* @notice Fuzz the `check` function and assert that it always returns true /// when the target's balance is larger than the threshold.
* when the target's balance is larger than the threshold.
*/
function testFuzz_check_succeeds(address _target, uint256 _threshold) external { function testFuzz_check_succeeds(address _target, uint256 _threshold) external {
CheckBalanceHigh.Params memory p = CheckBalanceHigh.Params({ CheckBalanceHigh.Params memory p = CheckBalanceHigh.Params({
target: _target, target: _target,
...@@ -39,10 +31,8 @@ contract CheckBalanceHighTest is Test { ...@@ -39,10 +31,8 @@ contract CheckBalanceHighTest is Test {
assertEq(c.check(abi.encode(p)), true); assertEq(c.check(abi.encode(p)), true);
} }
/** /// @notice Fuzz the `check` function and assert that it always returns false
* @notice Fuzz the `check` function and assert that it always returns false /// when the target's balance is smaller than the threshold.
* when the target's balance is smaller than the threshold.
*/
function testFuzz_check_lowBalance_fails(address _target, uint256 _threshold) external { function testFuzz_check_lowBalance_fails(address _target, uint256 _threshold) external {
CheckBalanceHigh.Params memory p = CheckBalanceHigh.Params({ CheckBalanceHigh.Params memory p = CheckBalanceHigh.Params({
target: _target, target: _target,
......
//SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { CheckBalanceLow } from "../periphery/drippie/dripchecks/CheckBalanceLow.sol"; import { CheckBalanceLow } from "../periphery/drippie/dripchecks/CheckBalanceLow.sol";
/** /// @title CheckBalanceLowTest
* @title CheckBalanceLowTest /// @notice Tests the CheckBalanceLow contract via fuzzing both the success case
* @notice Tests the CheckBalanceLow contract via fuzzing both the success case /// and the failure case.
* and the failure case.
*/
contract CheckBalanceLowTest is Test { contract CheckBalanceLowTest is Test {
/** /// @notice An instance of the CheckBalanceLow contract.
* @notice An instance of the CheckBalanceLow contract.
*/
CheckBalanceLow c; CheckBalanceLow c;
/** /// @notice Deploy the `CheckBalanceLow` contract.
* @notice Deploy the `CheckBalanceLow` contract.
*/
function setUp() external { function setUp() external {
c = new CheckBalanceLow(); c = new CheckBalanceLow();
} }
/** /// @notice Fuzz the `check` function and assert that it always returns true
* @notice Fuzz the `check` function and assert that it always returns true /// when the target's balance is smaller than the threshold.
* when the target's balance is smaller than the threshold.
*/
function testFuzz_check_succeeds(address _target, uint256 _threshold) external { function testFuzz_check_succeeds(address _target, uint256 _threshold) external {
CheckBalanceLow.Params memory p = CheckBalanceLow.Params({ CheckBalanceLow.Params memory p = CheckBalanceLow.Params({
target: _target, target: _target,
...@@ -37,10 +29,8 @@ contract CheckBalanceLowTest is Test { ...@@ -37,10 +29,8 @@ contract CheckBalanceLowTest is Test {
assertEq(c.check(abi.encode(p)), true); assertEq(c.check(abi.encode(p)), true);
} }
/** /// @notice Fuzz the `check` function and assert that it always returns false
* @notice Fuzz the `check` function and assert that it always returns false /// when the target's balance is larger than the threshold.
* when the target's balance is larger than the threshold.
*/
function testFuzz_check_highBalance_fails(address _target, uint256 _threshold) external { function testFuzz_check_highBalance_fails(address _target, uint256 _threshold) external {
CheckBalanceLow.Params memory p = CheckBalanceLow.Params({ CheckBalanceLow.Params memory p = CheckBalanceLow.Params({
target: _target, target: _target,
......
//SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
...@@ -7,11 +7,9 @@ import { ...@@ -7,11 +7,9 @@ import {
IGelatoTreasury IGelatoTreasury
} from "../periphery/drippie/dripchecks/CheckGelatoLow.sol"; } from "../periphery/drippie/dripchecks/CheckGelatoLow.sol";
/** /// @title MockGelatoTreasury
* @title MockGelatoTreasury /// @notice Mocks the Gelato treasury for testing purposes. Allows arbitrary
* @notice Mocks the Gelato treasury for testing purposes. Allows arbitrary /// setting of user balances.
* setting of user balances.
*/
contract MockGelatoTreasury is IGelatoTreasury { contract MockGelatoTreasury is IGelatoTreasury {
mapping(address => mapping(address => uint256)) private tokenBalances; mapping(address => mapping(address => uint256)) private tokenBalances;
...@@ -28,39 +26,27 @@ contract MockGelatoTreasury is IGelatoTreasury { ...@@ -28,39 +26,27 @@ contract MockGelatoTreasury is IGelatoTreasury {
} }
} }
/** /// @title CheckGelatoLowTest
* @title CheckGelatoLowTest /// @notice Tests the CheckBalanceHigh contract via fuzzing both the success case
* @notice Tests the CheckBalanceHigh contract via fuzzing both the success case /// and the failure case.
* and the failure case.
*/
contract CheckGelatoLowTest is Test { contract CheckGelatoLowTest is Test {
/** /// @notice An instance of the CheckGelatoLow contract.
* @notice An instance of the CheckGelatoLow contract.
*/
CheckGelatoLow c; CheckGelatoLow c;
/** /// @notice An instance of the MockGelatoTreasury contract.
* @notice An instance of the MockGelatoTreasury contract.
*/
MockGelatoTreasury gelato; MockGelatoTreasury gelato;
/** /// @notice The account Gelato uses to represent ether
* @notice The account Gelato uses to represent ether
*/
address internal constant eth = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; address internal constant eth = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/** /// @notice Deploy the `CheckGelatoLow` and `MockGelatoTreasury` contracts.
* @notice Deploy the `CheckGelatoLow` and `MockGelatoTreasury` contracts.
*/
function setUp() external { function setUp() external {
c = new CheckGelatoLow(); c = new CheckGelatoLow();
gelato = new MockGelatoTreasury(); gelato = new MockGelatoTreasury();
} }
/** /// @notice Fuzz the `check` function and assert that it always returns true
* @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.
* when the user's balance in the treasury is less than the threshold.
*/
function testFuzz_check_succeeds(uint256 _threshold, address _recipient) external { function testFuzz_check_succeeds(uint256 _threshold, address _recipient) external {
CheckGelatoLow.Params memory p = CheckGelatoLow.Params({ CheckGelatoLow.Params memory p = CheckGelatoLow.Params({
treasury: address(gelato), treasury: address(gelato),
...@@ -73,11 +59,9 @@ contract CheckGelatoLowTest is Test { ...@@ -73,11 +59,9 @@ contract CheckGelatoLowTest is Test {
assertEq(c.check(abi.encode(p)), true); assertEq(c.check(abi.encode(p)), true);
} }
/** /// @notice Fuzz the `check` function and assert that it always returns false
* @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
* when the user's balance in the treasury is greater than or equal /// to the threshold.
* to the threshold.
*/
function testFuzz_check_highBalance_fails(uint256 _threshold, address _recipient) external { function testFuzz_check_highBalance_fails(uint256 _threshold, address _recipient) external {
CheckGelatoLow.Params memory p = CheckGelatoLow.Params({ CheckGelatoLow.Params memory p = CheckGelatoLow.Params({
treasury: address(gelato), treasury: address(gelato),
......
//SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { CheckTrue } from "../periphery/drippie/dripchecks/CheckTrue.sol"; import { CheckTrue } from "../periphery/drippie/dripchecks/CheckTrue.sol";
/** /// @title CheckTrueTest
* @title CheckTrueTest /// @notice Ensures that the CheckTrue DripCheck contract always returns true.
* @notice Ensures that the CheckTrue DripCheck contract always returns true.
*/
contract CheckTrueTest is Test { contract CheckTrueTest is Test {
/** /// @notice An instance of the CheckTrue contract.
* @notice An instance of the CheckTrue contract.
*/
CheckTrue c; CheckTrue c;
/** /// @notice Deploy the `CheckTrue` contract.
* @notice Deploy the `CheckTrue` contract.
*/
function setUp() external { function setUp() external {
c = new CheckTrue(); 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 { function testFuzz_always_true_succeeds(bytes memory input) external {
assertEq(c.check(input), true); assertEq(c.check(input), true);
} }
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
/* Testing utilities */ // Testing utilities
import { Test, StdUtils } from "forge-std/Test.sol"; import { Test, StdUtils } from "forge-std/Test.sol";
import { L2OutputOracle } from "../L1/L2OutputOracle.sol"; import { L2OutputOracle } from "../L1/L2OutputOracle.sol";
import { L2ToL1MessagePasser } from "../L2/L2ToL1MessagePasser.sol"; import { L2ToL1MessagePasser } from "../L2/L2ToL1MessagePasser.sol";
...@@ -766,9 +766,7 @@ contract ConfigurableCaller { ...@@ -766,9 +766,7 @@ contract ConfigurableCaller {
event WhatHappened(bool success, bytes returndata); 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 { function call() external {
if (doRevert) { if (doRevert) {
revert("ConfigurableCaller: revert"); revert("ConfigurableCaller: revert");
...@@ -787,31 +785,23 @@ contract ConfigurableCaller { ...@@ -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 { function setDoRevert(bool _doRevert) external {
doRevert = _doRevert; 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 { function setTarget(address _target) external {
target = _target; 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 { function setPayload(bytes calldata _payload) external {
payload = _payload; payload = _payload;
} }
/** /// @notice Fallback function that reverts if `doRevert` is true.
* @notice Fallback function that reverts if `doRevert` is true. /// Otherwise, it does nothing.
* Otherwise, it does nothing.
*/
fallback() external { fallback() external {
if (doRevert) { if (doRevert) {
revert("ConfigurableCaller: revert"); revert("ConfigurableCaller: revert");
......
//SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
...@@ -7,14 +7,12 @@ import { IDripCheck } from "../periphery/drippie/IDripCheck.sol"; ...@@ -7,14 +7,12 @@ import { IDripCheck } from "../periphery/drippie/IDripCheck.sol";
import { CheckTrue } from "../periphery/drippie/dripchecks/CheckTrue.sol"; import { CheckTrue } from "../periphery/drippie/dripchecks/CheckTrue.sol";
import { SimpleStorage } from "./Helpers.sol"; import { SimpleStorage } from "./Helpers.sol";
/** /// @title TestDrippie
* @title TestDrippie /// @notice This is a wrapper contract around Drippie used for testing.
* @notice This is a wrapper contract around Drippie used for testing. /// Returning an entire `Drippie.DripState` causes stack too
* Returning an entire `Drippie.DripState` causes stack too /// deep errors without `--vir-ir` which causes the compile time
* deep errors without `--vir-ir` which causes the compile time /// to go up by ~4x. Each of the methods is a simple getter around
* to go up by ~4x. Each of the methods is a simple getter around /// parts of the `DripState`.
* parts of the `DripState`.
*/
contract TestDrippie is Drippie { contract TestDrippie is Drippie {
constructor(address owner) Drippie(owner) {} constructor(address owner) Drippie(owner) {}
...@@ -47,58 +45,38 @@ contract TestDrippie is Drippie { ...@@ -47,58 +45,38 @@ contract TestDrippie is Drippie {
} }
} }
/** /// @title Drippie_Test
* @title Drippie_Test /// @notice Test coverage of the Drippie contract.
* @notice Test coverage of the Drippie contract.
*/
contract Drippie_Test is Test { contract Drippie_Test is Test {
/** /// @notice Emitted when a drip is executed.
* @notice Emitted when a drip is executed.
*/
event DripExecuted(string indexed nameref, string name, address executor, uint256 timestamp); event DripExecuted(string indexed nameref, string name, address executor, uint256 timestamp);
/** /// @notice Emitted when a drip's status is updated.
* @notice Emitted when a drip's status is updated.
*/
event DripStatusUpdated(string indexed nameref, string name, Drippie.DripStatus status); event DripStatusUpdated(string indexed nameref, string name, Drippie.DripStatus status);
/** /// @notice Emitted when a drip is created.
* @notice Emitted when a drip is created.
*/
event DripCreated(string indexed nameref, string name, Drippie.DripConfig config); event DripCreated(string indexed nameref, string name, Drippie.DripConfig config);
/** /// @notice Address of the test DripCheck. CheckTrue is deployed
* @notice Address of the test DripCheck. CheckTrue is deployed /// here so it always returns true.
* here so it always returns true.
*/
IDripCheck check; IDripCheck check;
/** /// @notice Address of a SimpleStorage contract. Used to test that
* @notice Address of a SimpleStorage contract. Used to test that /// calls are actually made by Drippie.
* calls are actually made by Drippie.
*/
SimpleStorage simpleStorage; SimpleStorage simpleStorage;
/** /// @notice Address of the Drippie contract.
* @notice Address of the Drippie contract.
*/
TestDrippie drippie; TestDrippie drippie;
/** /// @notice Address of the Drippie owner
* @notice Address of the Drippie owner
*/
address constant alice = address(0x42); address constant alice = address(0x42);
/** /// @notice The name of the default drip
* @notice The name of the default drip
*/
string constant dripName = "foo"; string constant dripName = "foo";
/** /// @notice Set up the test suite by deploying a CheckTrue DripCheck.
* @notice Set up the test suite by deploying a CheckTrue DripCheck. /// This is a mock that always returns true. Alice is the owner
* This is a mock that always returns true. Alice is the owner /// and also give drippie 1 ether so that it can balance transfer.
* and also give drippie 1 ether so that it can balance transfer.
*/
function setUp() external { function setUp() external {
check = IDripCheck(new CheckTrue()); check = IDripCheck(new CheckTrue());
simpleStorage = new SimpleStorage(); simpleStorage = new SimpleStorage();
...@@ -107,12 +85,10 @@ contract Drippie_Test is Test { ...@@ -107,12 +85,10 @@ contract Drippie_Test is Test {
vm.deal(address(drippie), 1 ether); vm.deal(address(drippie), 1 ether);
} }
/** /// @notice Builds a default Drippie.DripConfig. Uses CheckTrue as the
* @notice Builds a default Drippie.DripConfig. Uses CheckTrue as the /// dripcheck so it will always be able to do its DripActions.
* dripcheck so it will always be able to do its DripActions. /// Gives a dummy DripAction that does a simple transfer to
* Gives a dummy DripAction that does a simple transfer to /// a dummy address.
* a dummy address.
*/
function _defaultConfig() internal view returns (Drippie.DripConfig memory) { function _defaultConfig() internal view returns (Drippie.DripConfig memory) {
Drippie.DripAction[] memory actions = new Drippie.DripAction[](1); Drippie.DripAction[] memory actions = new Drippie.DripAction[](1);
actions[0] = Drippie.DripAction({ target: payable(address(0x44)), data: hex"", value: 1 }); actions[0] = Drippie.DripAction({ target: payable(address(0x44)), data: hex"", value: 1 });
...@@ -127,9 +103,7 @@ contract Drippie_Test is Test { ...@@ -127,9 +103,7 @@ contract Drippie_Test is Test {
}); });
} }
/** /// @notice Creates a default drip using the default drip config.
* @notice Creates a default drip using the default drip config.
*/
function _createDefaultDrip(string memory name) internal { function _createDefaultDrip(string memory name) internal {
address owner = drippie.owner(); address owner = drippie.owner();
Drippie.DripConfig memory cfg = _defaultConfig(); Drippie.DripConfig memory cfg = _defaultConfig();
...@@ -137,18 +111,14 @@ contract Drippie_Test is Test { ...@@ -137,18 +111,14 @@ contract Drippie_Test is Test {
drippie.create(name, cfg); drippie.create(name, cfg);
} }
/** /// @notice Moves the block's timestamp forward so that it is
* @notice Moves the block's timestamp forward so that it is /// possible to execute a drip.
* possible to execute a drip.
*/
function _warpToExecutable(string memory name) internal { function _warpToExecutable(string memory name) internal {
Drippie.DripConfig memory config = drippie.dripConfig(name); Drippie.DripConfig memory config = drippie.dripConfig(name);
vm.warp(config.interval + drippie.dripStateLast(name)); vm.warp(config.interval + drippie.dripStateLast(name));
} }
/** /// @notice Creates a drip and asserts that it was configured as expected.
* @notice Creates a drip and asserts that it was configured as expected.
*/
function test_create_succeeds() external { function test_create_succeeds() external {
Drippie.DripConfig memory cfg = _defaultConfig(); Drippie.DripConfig memory cfg = _defaultConfig();
vm.expectEmit(true, true, true, true); vm.expectEmit(true, true, true, true);
...@@ -185,9 +155,7 @@ contract Drippie_Test is Test { ...@@ -185,9 +155,7 @@ contract Drippie_Test is Test {
} }
} }
/** /// @notice Ensures that the same drip cannot be created two times.
* @notice Ensures that the same drip cannot be created two times.
*/
function test_create_calledTwice_reverts() external { function test_create_calledTwice_reverts() external {
vm.startPrank(drippie.owner()); vm.startPrank(drippie.owner());
Drippie.DripConfig memory cfg = _defaultConfig(); Drippie.DripConfig memory cfg = _defaultConfig();
...@@ -197,9 +165,7 @@ contract Drippie_Test is Test { ...@@ -197,9 +165,7 @@ contract Drippie_Test is Test {
vm.stopPrank(); vm.stopPrank();
} }
/** /// @notice Ensures that only the owner of Drippie can create a drip.
* @notice Ensures that only the owner of Drippie can create a drip.
*/
function testFuzz_owner_unauthorized_reverts(address caller) external { function testFuzz_owner_unauthorized_reverts(address caller) external {
vm.assume(caller != drippie.owner()); vm.assume(caller != drippie.owner());
vm.prank(caller); vm.prank(caller);
...@@ -207,9 +173,7 @@ contract Drippie_Test is Test { ...@@ -207,9 +173,7 @@ contract Drippie_Test is Test {
drippie.create(dripName, _defaultConfig()); drippie.create(dripName, _defaultConfig());
} }
/** /// @notice The owner should be able to set the status of the drip.
* @notice The owner should be able to set the status of the drip.
*/
function test_set_status_succeeds() external { function test_set_status_succeeds() external {
vm.expectEmit(true, true, true, true); vm.expectEmit(true, true, true, true);
emit DripCreated(dripName, dripName, _defaultConfig()); emit DripCreated(dripName, dripName, _defaultConfig());
...@@ -255,9 +219,7 @@ contract Drippie_Test is Test { ...@@ -255,9 +219,7 @@ contract Drippie_Test is Test {
} }
} }
/** /// @notice The drip status cannot be set back to NONE after it is created.
* @notice The drip status cannot be set back to NONE after it is created.
*/
function test_set_statusNone_reverts() external { function test_set_statusNone_reverts() external {
_createDefaultDrip(dripName); _createDefaultDrip(dripName);
...@@ -268,10 +230,8 @@ contract Drippie_Test is Test { ...@@ -268,10 +230,8 @@ contract Drippie_Test is Test {
drippie.status(dripName, Drippie.DripStatus.NONE); drippie.status(dripName, Drippie.DripStatus.NONE);
} }
/** /// @notice The owner cannot set the status of the drip to the status that
* @notice The owner cannot set the status of the drip to the status that /// it is already set as.
* it is already set as.
*/
function test_set_statusSame_reverts() external { function test_set_statusSame_reverts() external {
_createDefaultDrip(dripName); _createDefaultDrip(dripName);
...@@ -282,10 +242,8 @@ contract Drippie_Test is Test { ...@@ -282,10 +242,8 @@ contract Drippie_Test is Test {
drippie.status(dripName, Drippie.DripStatus.PAUSED); drippie.status(dripName, Drippie.DripStatus.PAUSED);
} }
/** /// @notice The owner should be able to archive the drip if it is in the
* @notice The owner should be able to archive the drip if it is in the /// paused state.
* paused state.
*/
function test_shouldArchive_ifPaused_succeeds() external { function test_shouldArchive_ifPaused_succeeds() external {
_createDefaultDrip(dripName); _createDefaultDrip(dripName);
...@@ -306,10 +264,8 @@ contract Drippie_Test is Test { ...@@ -306,10 +264,8 @@ contract Drippie_Test is Test {
assertEq(uint256(status), uint256(Drippie.DripStatus.ARCHIVED)); assertEq(uint256(status), uint256(Drippie.DripStatus.ARCHIVED));
} }
/** /// @notice The owner should not be able to archive the drip if it is in the
* @notice The owner should not be able to archive the drip if it is in the /// active state.
* active state.
*/
function test_shouldNotArchive_ifActive_reverts() external { function test_shouldNotArchive_ifActive_reverts() external {
_createDefaultDrip(dripName); _createDefaultDrip(dripName);
...@@ -323,30 +279,24 @@ contract Drippie_Test is Test { ...@@ -323,30 +279,24 @@ contract Drippie_Test is Test {
drippie.status(dripName, Drippie.DripStatus.ARCHIVED); drippie.status(dripName, Drippie.DripStatus.ARCHIVED);
} }
/** /// @notice The owner should not be allowed to pause the drip if it
* @notice The owner should not be allowed to pause the drip if it /// has already been archived.
* has already been archived.
*/
function test_shouldNotAllowPaused_ifArchived_reverts() external { function test_shouldNotAllowPaused_ifArchived_reverts() external {
_createDefaultDrip(dripName); _createDefaultDrip(dripName);
_notAllowFromArchive(dripName, Drippie.DripStatus.PAUSED); _notAllowFromArchive(dripName, Drippie.DripStatus.PAUSED);
} }
/** /// @notice The owner should not be allowed to make the drip active again if
* @notice The owner should not be allowed to make the drip active again if /// it has already been archived.
* it has already been archived.
*/
function test_shouldNotAllowActive_ifArchived_reverts() external { function test_shouldNotAllowActive_ifArchived_reverts() external {
_createDefaultDrip(dripName); _createDefaultDrip(dripName);
_notAllowFromArchive(dripName, Drippie.DripStatus.ACTIVE); _notAllowFromArchive(dripName, Drippie.DripStatus.ACTIVE);
} }
/** /// @notice Archive the drip and then attempt to set the status to the passed
* @notice Archive the drip and then attempt to set the status to the passed /// in status.
* in status.
*/
function _notAllowFromArchive(string memory name, Drippie.DripStatus status) internal { function _notAllowFromArchive(string memory name, Drippie.DripStatus status) internal {
address owner = drippie.owner(); address owner = drippie.owner();
vm.prank(owner); vm.prank(owner);
...@@ -358,9 +308,7 @@ contract Drippie_Test is Test { ...@@ -358,9 +308,7 @@ contract Drippie_Test is Test {
drippie.status(name, status); drippie.status(name, status);
} }
/** /// @notice Attempt to update a drip that does not exist.
* @notice Attempt to update a drip that does not exist.
*/
function test_name_notExist_reverts() external { function test_name_notExist_reverts() external {
string memory otherName = "bar"; string memory otherName = "bar";
...@@ -373,9 +321,7 @@ contract Drippie_Test is Test { ...@@ -373,9 +321,7 @@ contract Drippie_Test is Test {
drippie.status(otherName, Drippie.DripStatus.ARCHIVED); drippie.status(otherName, Drippie.DripStatus.ARCHIVED);
} }
/** /// @notice Expect a revert when attempting to set the status when not the owner.
* @notice Expect a revert when attempting to set the status when not the owner.
*/
function test_status_unauthorized_reverts() external { function test_status_unauthorized_reverts() external {
_createDefaultDrip(dripName); _createDefaultDrip(dripName);
...@@ -383,9 +329,7 @@ contract Drippie_Test is Test { ...@@ -383,9 +329,7 @@ contract Drippie_Test is Test {
drippie.status(dripName, Drippie.DripStatus.ACTIVE); drippie.status(dripName, Drippie.DripStatus.ACTIVE);
} }
/** /// @notice The drip should execute and be able to transfer value.
* @notice The drip should execute and be able to transfer value.
*/
function test_drip_amount_succeeds() external { function test_drip_amount_succeeds() external {
_createDefaultDrip(dripName); _createDefaultDrip(dripName);
...@@ -415,9 +359,7 @@ contract Drippie_Test is Test { ...@@ -415,9 +359,7 @@ contract Drippie_Test is Test {
drippie.drip(dripName); drippie.drip(dripName);
} }
/** /// @notice A single DripAction should be able to make a state modifying call.
* @notice A single DripAction should be able to make a state modifying call.
*/
function test_trigger_oneFunction_succeeds() external { function test_trigger_oneFunction_succeeds() external {
Drippie.DripConfig memory cfg = _defaultConfig(); Drippie.DripConfig memory cfg = _defaultConfig();
...@@ -452,9 +394,7 @@ contract Drippie_Test is Test { ...@@ -452,9 +394,7 @@ contract Drippie_Test is Test {
assertEq(simpleStorage.get(key), value); assertEq(simpleStorage.get(key), value);
} }
/** /// @notice Multiple drip actions should be able to be triggered with the same check.
* @notice Multiple drip actions should be able to be triggered with the same check.
*/
function test_trigger_twoFunctions_succeeds() external { function test_trigger_twoFunctions_succeeds() external {
Drippie.DripConfig memory cfg = _defaultConfig(); Drippie.DripConfig memory cfg = _defaultConfig();
Drippie.DripAction[] memory actions = new Drippie.DripAction[](2); Drippie.DripAction[] memory actions = new Drippie.DripAction[](2);
...@@ -511,11 +451,9 @@ contract Drippie_Test is Test { ...@@ -511,11 +451,9 @@ contract Drippie_Test is Test {
assertEq(simpleStorage.get(keyTwo), valueTwo); assertEq(simpleStorage.get(keyTwo), valueTwo);
} }
/** /// @notice The drips can only be triggered once per interval. Attempt to
* @notice The drips can only be triggered once per interval. Attempt to /// trigger the same drip multiple times in the same interval. Then
* trigger the same drip multiple times in the same interval. Then /// move forward to the next interval and it should trigger.
* move forward to the next interval and it should trigger.
*/
function test_twice_inOneInterval_reverts() external { function test_twice_inOneInterval_reverts() external {
_createDefaultDrip(dripName); _createDefaultDrip(dripName);
...@@ -547,10 +485,8 @@ contract Drippie_Test is Test { ...@@ -547,10 +485,8 @@ contract Drippie_Test is Test {
drippie.drip(dripName); drippie.drip(dripName);
} }
/** /// @notice It should revert if attempting to trigger a drip that does not exist.
* @notice It should revert if attempting to trigger a drip that does not exist. /// Note that the drip was never created at the beginning of the test.
* Note that the drip was never created at the beginning of the test.
*/
function test_drip_notExist_reverts() external { function test_drip_notExist_reverts() external {
vm.prank(drippie.owner()); vm.prank(drippie.owner());
...@@ -559,9 +495,7 @@ contract Drippie_Test is Test { ...@@ -559,9 +495,7 @@ contract Drippie_Test is Test {
drippie.drip(dripName); drippie.drip(dripName);
} }
/** /// @notice The owner cannot trigger the drip when it is paused.
* @notice The owner cannot trigger the drip when it is paused.
*/
function test_not_active_reverts() external { function test_not_active_reverts() external {
_createDefaultDrip(dripName); _createDefaultDrip(dripName);
...@@ -575,9 +509,7 @@ contract Drippie_Test is Test { ...@@ -575,9 +509,7 @@ contract Drippie_Test is Test {
drippie.drip(dripName); drippie.drip(dripName);
} }
/** /// @notice The interval must be 0 if reentrant is set on the config.
* @notice The interval must be 0 if reentrant is set on the config.
*/
function test_reentrant_succeeds() external { function test_reentrant_succeeds() external {
address owner = drippie.owner(); address owner = drippie.owner();
Drippie.DripConfig memory cfg = _defaultConfig(); Drippie.DripConfig memory cfg = _defaultConfig();
...@@ -594,10 +526,8 @@ contract Drippie_Test is Test { ...@@ -594,10 +526,8 @@ contract Drippie_Test is Test {
assertEq(_cfg.interval, 0); assertEq(_cfg.interval, 0);
} }
/** /// @notice A non zero interval when reentrant is true will cause a revert
* @notice A non zero interval when reentrant is true will cause a revert /// when creating a drip.
* when creating a drip.
*/
function test_drip_reentrant_reverts() external { function test_drip_reentrant_reverts() external {
address owner = drippie.owner(); address owner = drippie.owner();
Drippie.DripConfig memory cfg = _defaultConfig(); Drippie.DripConfig memory cfg = _defaultConfig();
...@@ -610,10 +540,8 @@ contract Drippie_Test is Test { ...@@ -610,10 +540,8 @@ contract Drippie_Test is Test {
drippie.create(dripName, cfg); drippie.create(dripName, cfg);
} }
/** /// @notice If reentrant is false and the interval is 0 then it should
* @notice If reentrant is false and the interval is 0 then it should /// revert when the drip is created.
* revert when the drip is created.
*/
function test_notReentrant_zeroInterval_reverts() external { function test_notReentrant_zeroInterval_reverts() external {
address owner = drippie.owner(); address owner = drippie.owner();
Drippie.DripConfig memory cfg = _defaultConfig(); Drippie.DripConfig memory cfg = _defaultConfig();
......
//SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
...@@ -45,9 +45,7 @@ contract Faucet_Initializer is Test { ...@@ -45,9 +45,7 @@ contract Faucet_Initializer is Test {
_initializeContracts(); _initializeContracts();
} }
/** /// @notice Instantiates a Faucet.
* @notice Instantiates a Faucet.
*/
function _initializeContracts() internal { function _initializeContracts() internal {
faucet = new Faucet(faucetContractAdmin); faucet = new Faucet(faucetContractAdmin);
...@@ -76,10 +74,7 @@ contract Faucet_Initializer is Test { ...@@ -76,10 +74,7 @@ contract Faucet_Initializer is Test {
vm.stopPrank(); vm.stopPrank();
} }
/** /// @notice Get signature as a bytes blob.
* @notice Get signature as a bytes blob.
*
*/
function _getSignature(uint256 _signingPrivateKey, bytes32 _digest) function _getSignature(uint256 _signingPrivateKey, bytes32 _digest)
internal internal
pure pure
...@@ -91,11 +86,9 @@ contract Faucet_Initializer is Test { ...@@ -91,11 +86,9 @@ contract Faucet_Initializer is Test {
return signature; return signature;
} }
/** /// @notice Signs a proof with the given private key and returns the signature using
* @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
* the given EIP712 domain separator. This assumes that the issuer's address is the /// corresponding public key to _issuerPrivateKey.
* corresponding public key to _issuerPrivateKey.
*/
function issueProofWithEIP712Domain( function issueProofWithEIP712Domain(
uint256 _issuerPrivateKey, uint256 _issuerPrivateKey,
bytes memory _eip712Name, bytes memory _eip712Name,
......
//SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
import { ERC20 } from "@rari-capital/solmate/src/tokens/ERC20.sol"; import { ERC20 } from "@rari-capital/solmate/src/tokens/ERC20.sol";
...@@ -66,38 +66,27 @@ contract SimpleStorage { ...@@ -66,38 +66,27 @@ contract SimpleStorage {
} }
} }
/** /// @notice Simple helper contract that helps with testing flow and signature for
* Simple helper contract that helps with testing flow and signature for OptimistInviter contract. /// OptimistInviter contract. Made this a separate contract instead of including
* Made this a separate contract instead of including in OptimistInviter.t.sol for reusability. /// in OptimistInviter.t.sol for reusability.
*/
contract OptimistInviterHelper { contract OptimistInviterHelper {
/** /// @notice EIP712 typehash for the ClaimableInvite type.
* @notice EIP712 typehash for the ClaimableInvite type.
*/
bytes32 public constant CLAIMABLE_INVITE_TYPEHASH = bytes32 public constant CLAIMABLE_INVITE_TYPEHASH =
keccak256("ClaimableInvite(address issuer,bytes32 nonce)"); keccak256("ClaimableInvite(address issuer,bytes32 nonce)");
/** /// @notice EIP712 typehash for the EIP712Domain type that is included as part of the signature.
* @notice EIP712 typehash for the EIP712Domain type that is included as part of the signature.
*/
bytes32 public constant EIP712_DOMAIN_TYPEHASH = bytes32 public constant EIP712_DOMAIN_TYPEHASH =
keccak256( keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
); );
/** /// @notice Address of OptimistInviter contract we are testing.
* @notice Address of OptimistInviter contract we are testing.
*/
OptimistInviter public optimistInviter; OptimistInviter public optimistInviter;
/** /// @notice OptimistInviter contract name. Used to construct the EIP-712 domain.
* @notice OptimistInviter contract name. Used to construct the EIP-712 domain.
*/
string public name; string public name;
/** /// @notice Keeps track of current nonce to generate new nonces for each invite.
* @notice Keeps track of current nonce to generate new nonces for each invite.
*/
uint256 public currentNonce; uint256 public currentNonce;
constructor(OptimistInviter _optimistInviter, string memory _name) { constructor(OptimistInviter _optimistInviter, string memory _name) {
...@@ -105,13 +94,9 @@ contract OptimistInviterHelper { ...@@ -105,13 +94,9 @@ contract OptimistInviterHelper {
name = _name; name = _name;
} }
/** /// @notice Returns the hash of the struct ClaimableInvite.
* @notice Returns the hash of the struct ClaimableInvite. /// @param _claimableInvite ClaimableInvite struct to hash.
* /// @return EIP-712 typed struct hash.
* @param _claimableInvite ClaimableInvite struct to hash.
*
* @return EIP-712 typed struct hash.
*/
function getClaimableInviteStructHash(OptimistInviter.ClaimableInvite memory _claimableInvite) function getClaimableInviteStructHash(OptimistInviter.ClaimableInvite memory _claimableInvite)
public public
pure pure
...@@ -127,23 +112,16 @@ contract OptimistInviterHelper { ...@@ -127,23 +112,16 @@ contract OptimistInviterHelper {
); );
} }
/** /// @notice Returns a bytes32 nonce that should change everytime. In practice, people should use
* @notice Returns a bytes32 nonce that should change everytime. In practice, people should use /// pseudorandom nonces.
* pseudorandom nonces. /// @return Nonce that should be used as part of ClaimableInvite.
*
* @return Nonce that should be used as part of ClaimableInvite.
*/
function consumeNonce() public returns (bytes32) { function consumeNonce() public returns (bytes32) {
return bytes32(keccak256(abi.encode(currentNonce++))); return bytes32(keccak256(abi.encode(currentNonce++)));
} }
/** /// @notice Returns a ClaimableInvite with the issuer and current nonce.
* @notice Returns a ClaimableInvite with the issuer and current nonce. /// @param _issuer Issuer to include in the ClaimableInvite.
* /// @return ClaimableInvite that can be hashed & signed.
* @param _issuer Issuer to include in the ClaimableInvite.
*
* @return ClaimableInvite that can be hashed & signed.
*/
function getClaimableInviteWithNewNonce(address _issuer) function getClaimableInviteWithNewNonce(address _issuer)
public public
returns (OptimistInviter.ClaimableInvite memory) returns (OptimistInviter.ClaimableInvite memory)
...@@ -151,13 +129,9 @@ contract OptimistInviterHelper { ...@@ -151,13 +129,9 @@ contract OptimistInviterHelper {
return OptimistInviter.ClaimableInvite(_issuer, consumeNonce()); return OptimistInviter.ClaimableInvite(_issuer, consumeNonce());
} }
/** /// @notice Computes the EIP712 digest with default correct parameters.
* @notice Computes the EIP712 digest with default correct parameters. /// @param _claimableInvite ClaimableInvite struct to hash.
* /// @return EIP-712 compatible digest.
* @param _claimableInvite ClaimableInvite struct to hash.
*
* @return EIP-712 compatible digest.
*/
function getDigest(OptimistInviter.ClaimableInvite calldata _claimableInvite) function getDigest(OptimistInviter.ClaimableInvite calldata _claimableInvite)
public public
view view
...@@ -173,18 +147,14 @@ contract OptimistInviterHelper { ...@@ -173,18 +147,14 @@ contract OptimistInviterHelper {
); );
} }
/** /// @notice Computes the EIP712 digest with the given domain parameters.
* @notice Computes the EIP712 digest with the given domain parameters. /// Used for testing that different domain parameters fail.
* Used for testing that different domain parameters fail. /// @param _claimableInvite ClaimableInvite struct to hash.
* /// @param _name Contract name to use in the EIP712 domain.
* @param _claimableInvite ClaimableInvite struct to hash. /// @param _version Contract version to use in the EIP712 domain.
* @param _name Contract name to use in the EIP712 domain. /// @param _chainid Chain ID to use in the EIP712 domain.
* @param _version Contract version to use in the EIP712 domain. /// @param _verifyingContract Address to use in the EIP712 domain.
* @param _chainid Chain ID to use in the EIP712 domain. /// @return EIP-712 compatible digest.
* @param _verifyingContract Address to use in the EIP712 domain.
*
* @return EIP-712 compatible digest.
*/
function getDigestWithEIP712Domain( function getDigestWithEIP712Domain(
OptimistInviter.ClaimableInvite calldata _claimableInvite, OptimistInviter.ClaimableInvite calldata _claimableInvite,
bytes memory _name, bytes memory _name,
...@@ -206,11 +176,9 @@ contract OptimistInviterHelper { ...@@ -206,11 +176,9 @@ contract OptimistInviterHelper {
} }
} }
// solhint-disable max-line-length /// @notice Simple ERC1271 wallet that can be used to test the ERC1271 signature checker.
/** /// @notice https://github.com/OpenZeppelin/openzeppelin-contracts/
* Simple ERC1271 wallet that can be used to test the ERC1271 signature checker. /// blob/master/contracts/mocks/ERC1271WalletMock.sol
* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/mocks/ERC1271WalletMock.sol
*/
contract TestERC1271Wallet is Ownable, IERC1271 { contract TestERC1271Wallet is Ownable, IERC1271 {
constructor(address originalOwner) { constructor(address originalOwner) {
transferOwnership(originalOwner); transferOwnership(originalOwner);
...@@ -227,46 +195,31 @@ contract TestERC1271Wallet is Ownable, IERC1271 { ...@@ -227,46 +195,31 @@ contract TestERC1271Wallet is Ownable, IERC1271 {
} }
} }
/** /// @notice Simple helper contract that helps with testing the Faucet contract.
* Simple helper contract that helps with testing the Faucet contract.
*/
contract FaucetHelper { contract FaucetHelper {
/** /// @notice EIP712 typehash for the Proof type.
* @notice EIP712 typehash for the Proof type.
*/
bytes32 public constant PROOF_TYPEHASH = bytes32 public constant PROOF_TYPEHASH =
keccak256("Proof(address recipient,bytes32 nonce,bytes32 id)"); keccak256("Proof(address recipient,bytes32 nonce,bytes32 id)");
/** /// @notice EIP712 typehash for the EIP712Domain type that is included as part of the signature.
* @notice EIP712 typehash for the EIP712Domain type that is included as part of the signature.
*/
bytes32 public constant EIP712_DOMAIN_TYPEHASH = bytes32 public constant EIP712_DOMAIN_TYPEHASH =
keccak256( keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
); );
/** /// @notice Keeps track of current nonce to generate new nonces for each drip.
* @notice Keeps track of current nonce to generate new nonces for each drip.
*/
uint256 public currentNonce; uint256 public currentNonce;
/** /// @notice Returns a bytes32 nonce that should change everytime. In practice, people should use
* @notice Returns a bytes32 nonce that should change everytime. In practice, people should use /// pseudorandom nonces.
* pseudorandom nonces. /// @return Nonce that should be used as part of drip parameters.
*
* @return Nonce that should be used as part of drip parameters.
*/
function consumeNonce() public returns (bytes32) { function consumeNonce() public returns (bytes32) {
return bytes32(keccak256(abi.encode(currentNonce++))); return bytes32(keccak256(abi.encode(currentNonce++)));
} }
/** /// @notice Returns the hash of the struct Proof.
* @notice Returns the hash of the struct Proof. /// @param _proof Proof struct to hash.
* /// @return EIP-712 typed struct hash.
* @param _proof Proof struct to hash.
*
* @return EIP-712 typed struct hash.
*/
function getProofStructHash(AdminFaucetAuthModule.Proof memory _proof) function getProofStructHash(AdminFaucetAuthModule.Proof memory _proof)
public public
pure pure
...@@ -275,20 +228,16 @@ contract FaucetHelper { ...@@ -275,20 +228,16 @@ contract FaucetHelper {
return keccak256(abi.encode(PROOF_TYPEHASH, _proof.recipient, _proof.nonce, _proof.id)); return keccak256(abi.encode(PROOF_TYPEHASH, _proof.recipient, _proof.nonce, _proof.id));
} }
/** /// @notice Computes the EIP712 digest with the given domain parameters.
* @notice Computes the EIP712 digest with the given domain parameters. /// Used for testing that different domain parameters fail.
* Used for testing that different domain parameters fail. /// @param _proof Proof struct to hash.
* /// @param _name Contract name to use in the EIP712 domain.
* @param _proof Proof struct to hash. /// @param _version Contract version to use in the EIP712 domain.
* @param _name Contract name to use in the EIP712 domain. /// @param _chainid Chain ID to use in the EIP712 domain.
* @param _version Contract version to use in the EIP712 domain. /// @param _verifyingContract Address to use in the EIP712 domain.
* @param _chainid Chain ID to use in the EIP712 domain. /// @param _verifyingContract Address to use in the EIP712 domain.
* @param _verifyingContract Address to use in the EIP712 domain. /// @param _verifyingContract Address to use in the EIP712 domain.
* @param _verifyingContract Address to use in the EIP712 domain. /// @return EIP-712 compatible digest.
* @param _verifyingContract Address to use in the EIP712 domain.
*
* @return EIP-712 compatible digest.
*/
function getDigestWithEIP712Domain( function getDigestWithEIP712Domain(
AdminFaucetAuthModule.Proof memory _proof, AdminFaucetAuthModule.Proof memory _proof,
bytes memory _name, bytes memory _name,
......
//SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity >=0.6.2 <0.9.0; pragma solidity >=0.6.2 <0.9.0;
/* Testing utilities */ // Testing utilities
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { AttestationStation } from "../periphery/op-nft/AttestationStation.sol"; import { AttestationStation } from "../periphery/op-nft/AttestationStation.sol";
import { Optimist } from "../periphery/op-nft/Optimist.sol"; import { Optimist } from "../periphery/op-nft/Optimist.sol";
...@@ -67,9 +67,7 @@ contract Optimist_Initializer is Test { ...@@ -67,9 +67,7 @@ contract Optimist_Initializer is Test {
address internal bob; address internal bob;
address internal sally; address internal sally;
/** /// @notice BaseURI attestor sets the baseURI of the Optimist NFT.
* @notice BaseURI attestor sets the baseURI of the Optimist NFT.
*/
function _attestBaseURI(string memory _baseUri) internal { function _attestBaseURI(string memory _baseUri) internal {
bytes32 baseURIAttestationKey = optimist.BASE_URI_ATTESTATION_KEY(); bytes32 baseURIAttestationKey = optimist.BASE_URI_ATTESTATION_KEY();
AttestationStation.AttestationData[] AttestationStation.AttestationData[]
...@@ -91,9 +89,7 @@ contract Optimist_Initializer is Test { ...@@ -91,9 +89,7 @@ contract Optimist_Initializer is Test {
attestationStation.attest(attestationData); attestationStation.attest(attestationData);
} }
/** /// @notice Allowlist attestor creates an attestation for an address.
* @notice Allowlist attestor creates an attestation for an address.
*/
function _attestAllowlist(address _about) internal { function _attestAllowlist(address _about) internal {
bytes32 attestationKey = optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(); bytes32 attestationKey = optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY();
AttestationStation.AttestationData[] AttestationStation.AttestationData[]
...@@ -114,9 +110,7 @@ contract Optimist_Initializer is Test { ...@@ -114,9 +110,7 @@ contract Optimist_Initializer is Test {
assertTrue(optimist.isOnAllowList(_about)); assertTrue(optimist.isOnAllowList(_about));
} }
/** /// @notice Coinbase Quest attestor creates an attestation for an address.
* @notice Coinbase Quest attestor creates an attestation for an address.
*/
function _attestCoinbaseQuest(address _about) internal { function _attestCoinbaseQuest(address _about) internal {
bytes32 attestationKey = optimistAllowlist.COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY(); bytes32 attestationKey = optimistAllowlist.COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY();
AttestationStation.AttestationData[] AttestationStation.AttestationData[]
...@@ -137,9 +131,7 @@ contract Optimist_Initializer is Test { ...@@ -137,9 +131,7 @@ contract Optimist_Initializer is Test {
assertTrue(optimist.isOnAllowList(_about)); assertTrue(optimist.isOnAllowList(_about));
} }
/** /// @notice Issues invite, then claims it using the claimer's address.
* @notice Issues invite, then claims it using the claimer's address.
*/
function _inviteAndClaim(address _about) internal { function _inviteAndClaim(address _about) internal {
uint256 inviterPrivateKey = 0xbeefbeef; uint256 inviterPrivateKey = 0xbeefbeef;
address inviter = vm.addr(inviterPrivateKey); address inviter = vm.addr(inviterPrivateKey);
...@@ -179,9 +171,7 @@ contract Optimist_Initializer is Test { ...@@ -179,9 +171,7 @@ contract Optimist_Initializer is Test {
assertTrue(optimist.isOnAllowList(_about)); assertTrue(optimist.isOnAllowList(_about));
} }
/** /// @notice Mocks the allowlistAttestor to always return true for a given address.
* @notice Mocks the allowlistAttestor to always return true for a given address.
*/
function _mockAllowlistTrueFor(address _claimer) internal { function _mockAllowlistTrueFor(address _claimer) internal {
vm.mockCall( vm.mockCall(
address(optimistAllowlist), address(optimistAllowlist),
...@@ -192,9 +182,7 @@ contract Optimist_Initializer is Test { ...@@ -192,9 +182,7 @@ contract Optimist_Initializer is Test {
assertTrue(optimist.isOnAllowList(_claimer)); assertTrue(optimist.isOnAllowList(_claimer));
} }
/** /// @notice Returns address as uint256.
* @notice Returns address as uint256.
*/
function _getTokenId(address _owner) internal pure returns (uint256) { function _getTokenId(address _owner) internal pure returns (uint256) {
return uint256(uint160(address(_owner))); return uint256(uint160(address(_owner)));
} }
...@@ -245,9 +233,7 @@ contract Optimist_Initializer is Test { ...@@ -245,9 +233,7 @@ contract Optimist_Initializer is Test {
} }
contract OptimistTest is Optimist_Initializer { contract OptimistTest is Optimist_Initializer {
/** /// @notice Check that constructor and initializer parameters are correctly set.
* @notice Check that constructor and initializer parameters are correctly set.
*/
function test_initialize_succeeds() external { function test_initialize_succeeds() external {
// expect name to be set // expect name to be set
assertEq(optimist.name(), name); assertEq(optimist.name(), name);
...@@ -259,10 +245,8 @@ contract OptimistTest is Optimist_Initializer { ...@@ -259,10 +245,8 @@ contract OptimistTest is Optimist_Initializer {
assertEq(optimist.version(), "2.0.0"); assertEq(optimist.version(), "2.0.0");
} }
/** /// @notice Bob should be able to mint an NFT if he is allowlisted
* @notice Bob should be able to mint an NFT if he is allowlisted /// by the allowlistAttestor and has a balance of 0.
* by the allowlistAttestor and has a balance of 0.
*/
function test_mint_afterAllowlistAttestation_succeeds() external { function test_mint_afterAllowlistAttestation_succeeds() external {
// bob should start with 0 balance // bob should start with 0 balance
assertEq(optimist.balanceOf(bob), 0); assertEq(optimist.balanceOf(bob), 0);
...@@ -287,10 +271,8 @@ contract OptimistTest is Optimist_Initializer { ...@@ -287,10 +271,8 @@ contract OptimistTest is Optimist_Initializer {
assertEq(optimist.balanceOf(bob), 1); assertEq(optimist.balanceOf(bob), 1);
} }
/** /// @notice Bob should be able to mint an NFT if he claimed an invite through OptimistInviter
* @notice Bob should be able to mint an NFT if he claimed an invite through OptimistInviter /// and has a balance of 0.
* and has a balance of 0.
*/
function test_mint_afterInviteClaimed_succeeds() external { function test_mint_afterInviteClaimed_succeeds() external {
// bob should start with 0 balance // bob should start with 0 balance
assertEq(optimist.balanceOf(bob), 0); assertEq(optimist.balanceOf(bob), 0);
...@@ -315,10 +297,8 @@ contract OptimistTest is Optimist_Initializer { ...@@ -315,10 +297,8 @@ contract OptimistTest is Optimist_Initializer {
assertEq(optimist.balanceOf(bob), 1); assertEq(optimist.balanceOf(bob), 1);
} }
/** /// @notice Bob should be able to mint an NFT if he has an attestation from Coinbase Quest
* @notice Bob should be able to mint an NFT if he has an attestation from Coinbase Quest /// attestor and has a balance of 0.
* attestor and has a balance of 0.
*/
function test_mint_afterCoinbaseQuestAttestation_succeeds() external { function test_mint_afterCoinbaseQuestAttestation_succeeds() external {
// bob should start with 0 balance // bob should start with 0 balance
assertEq(optimist.balanceOf(bob), 0); assertEq(optimist.balanceOf(bob), 0);
...@@ -343,9 +323,7 @@ contract OptimistTest is Optimist_Initializer { ...@@ -343,9 +323,7 @@ contract OptimistTest is Optimist_Initializer {
assertEq(optimist.balanceOf(bob), 1); assertEq(optimist.balanceOf(bob), 1);
} }
/** /// @notice Multiple valid attestations should allow Bob to mint.
* @notice Multiple valid attestations should allow Bob to mint.
*/
function test_mint_afterMultipleAttestations_succeeds() external { function test_mint_afterMultipleAttestations_succeeds() external {
// bob should start with 0 balance // bob should start with 0 balance
assertEq(optimist.balanceOf(bob), 0); assertEq(optimist.balanceOf(bob), 0);
...@@ -376,9 +354,7 @@ contract OptimistTest is Optimist_Initializer { ...@@ -376,9 +354,7 @@ contract OptimistTest is Optimist_Initializer {
assertEq(optimist.balanceOf(bob), 1); assertEq(optimist.balanceOf(bob), 1);
} }
/** /// @notice Sally should be able to mint a token on behalf of bob.
* @notice Sally should be able to mint a token on behalf of bob.
*/
function test_mint_secondaryMinter_succeeds() external { function test_mint_secondaryMinter_succeeds() external {
_mockAllowlistTrueFor(bob); _mockAllowlistTrueFor(bob);
...@@ -394,18 +370,14 @@ contract OptimistTest is Optimist_Initializer { ...@@ -394,18 +370,14 @@ contract OptimistTest is Optimist_Initializer {
assertEq(optimist.balanceOf(bob), 1); assertEq(optimist.balanceOf(bob), 1);
} }
/** /// @notice Bob should not be able to mint an NFT if he is not allowlisted.
* @notice Bob should not be able to mint an NFT if he is not allowlisted.
*/
function test_mint_forNonAllowlistedClaimer_reverts() external { function test_mint_forNonAllowlistedClaimer_reverts() external {
vm.prank(bob); vm.prank(bob);
vm.expectRevert("Optimist: address is not on allowList"); vm.expectRevert("Optimist: address is not on allowList");
optimist.mint(bob); optimist.mint(bob);
} }
/** /// @notice Bob's tx should revert if he already minted.
* @notice Bob's tx should revert if he already minted.
*/
function test_mint_forAlreadyMintedClaimer_reverts() external { function test_mint_forAlreadyMintedClaimer_reverts() external {
_attestAllowlist(bob); _attestAllowlist(bob);
...@@ -421,9 +393,7 @@ contract OptimistTest is Optimist_Initializer { ...@@ -421,9 +393,7 @@ contract OptimistTest is Optimist_Initializer {
optimist.mint(bob); optimist.mint(bob);
} }
/** /// @notice The baseURI should be set by attestation station by the baseURIAttestor.
* @notice The baseURI should be set by attestation station by the baseURIAttestor.
*/
function test_baseURI_returnsCorrectBaseURI_succeeds() external { function test_baseURI_returnsCorrectBaseURI_succeeds() external {
_attestBaseURI(base_uri); _attestBaseURI(base_uri);
...@@ -440,9 +410,7 @@ contract OptimistTest is Optimist_Initializer { ...@@ -440,9 +410,7 @@ contract OptimistTest is Optimist_Initializer {
assertEq(optimist.baseURI(), base_uri); assertEq(optimist.baseURI(), base_uri);
} }
/** /// @notice tokenURI should return the token uri for a minted token.
* @notice tokenURI should return the token uri for a minted token.
*/
function test_tokenURI_returnsCorrectTokenURI_succeeds() external { function test_tokenURI_returnsCorrectTokenURI_succeeds() external {
// we are using true but it can be any non empty value // we are using true but it can be any non empty value
_attestBaseURI(base_uri); _attestBaseURI(base_uri);
...@@ -460,9 +428,7 @@ contract OptimistTest is Optimist_Initializer { ...@@ -460,9 +428,7 @@ contract OptimistTest is Optimist_Initializer {
); );
} }
/** /// @notice Should return the token id of the owner.
* @notice Should return the token id of the owner.
*/
function test_tokenIdOfAddress_returnsOwnerID_succeeds() external { function test_tokenIdOfAddress_returnsOwnerID_succeeds() external {
uint256 willTokenId = 1024; uint256 willTokenId = 1024;
address will = address(1024); address will = address(1024);
...@@ -474,9 +440,7 @@ contract OptimistTest is Optimist_Initializer { ...@@ -474,9 +440,7 @@ contract OptimistTest is Optimist_Initializer {
assertEq(optimist.tokenIdOfAddress(will), willTokenId); assertEq(optimist.tokenIdOfAddress(will), willTokenId);
} }
/** /// @notice transferFrom should revert since Optimist is a SBT.
* @notice transferFrom should revert since Optimist is a SBT.
*/
function test_transferFrom_soulbound_reverts() external { function test_transferFrom_soulbound_reverts() external {
_mockAllowlistTrueFor(bob); _mockAllowlistTrueFor(bob);
...@@ -499,9 +463,7 @@ contract OptimistTest is Optimist_Initializer { ...@@ -499,9 +463,7 @@ contract OptimistTest is Optimist_Initializer {
optimist.safeTransferFrom(bob, sally, _getTokenId(bob), bytes("0x")); optimist.safeTransferFrom(bob, sally, _getTokenId(bob), bytes("0x"));
} }
/** /// @notice approve should revert since Optimist is a SBT.
* @notice approve should revert since Optimist is a SBT.
*/
function test_approve_soulbound_reverts() external { function test_approve_soulbound_reverts() external {
_mockAllowlistTrueFor(bob); _mockAllowlistTrueFor(bob);
...@@ -517,9 +479,7 @@ contract OptimistTest is Optimist_Initializer { ...@@ -517,9 +479,7 @@ contract OptimistTest is Optimist_Initializer {
assertEq(optimist.getApproved(_getTokenId(bob)), address(0)); assertEq(optimist.getApproved(_getTokenId(bob)), address(0));
} }
/** /// @notice setApprovalForAll should revert since Optimist is a SBT.
* @notice setApprovalForAll should revert since Optimist is a SBT.
*/
function test_setApprovalForAll_soulbound_reverts() external { function test_setApprovalForAll_soulbound_reverts() external {
_mockAllowlistTrueFor(bob); _mockAllowlistTrueFor(bob);
...@@ -539,9 +499,7 @@ contract OptimistTest is Optimist_Initializer { ...@@ -539,9 +499,7 @@ contract OptimistTest is Optimist_Initializer {
); );
} }
/** /// @notice Only owner should be able to burn token.
* @notice Only owner should be able to burn token.
*/
function test_burn_byOwner_succeeds() external { function test_burn_byOwner_succeeds() external {
_mockAllowlistTrueFor(bob); _mockAllowlistTrueFor(bob);
...@@ -557,9 +515,7 @@ contract OptimistTest is Optimist_Initializer { ...@@ -557,9 +515,7 @@ contract OptimistTest is Optimist_Initializer {
assertEq(optimist.balanceOf(bob), 0); assertEq(optimist.balanceOf(bob), 0);
} }
/** /// @notice Non-owner attempting to burn token should revert.
* @notice Non-owner attempting to burn token should revert.
*/
function test_burn_byNonOwner_reverts() external { function test_burn_byNonOwner_reverts() external {
_mockAllowlistTrueFor(bob); _mockAllowlistTrueFor(bob);
...@@ -576,20 +532,16 @@ contract OptimistTest is Optimist_Initializer { ...@@ -576,20 +532,16 @@ contract OptimistTest is Optimist_Initializer {
assertEq(optimist.balanceOf(bob), 1); assertEq(optimist.balanceOf(bob), 1);
} }
/** /// @notice Should support ERC-721 interface.
* @notice Should support ERC-721 interface.
*/
function test_supportsInterface_returnsCorrectInterfaceForERC721_succeeds() external { function test_supportsInterface_returnsCorrectInterfaceForERC721_succeeds() external {
bytes4 iface721 = type(IERC721).interfaceId; bytes4 iface721 = type(IERC721).interfaceId;
// check that it supports ERC-721 interface // check that it supports ERC-721 interface
assertEq(optimist.supportsInterface(iface721), true); assertEq(optimist.supportsInterface(iface721), true);
} }
/** /// @notice Checking that multi-call using the invite & claim flow works correctly, since the
* @notice Checking that multi-call using the invite & claim flow works correctly, since the /// frontend will be making multicalls to improve UX. The OptimistInviter.claimInvite
* frontend will be making multicalls to improve UX. The OptimistInviter.claimInvite /// and Optimist.mint will be batched
* and Optimist.mint will be batched
*/
function test_multicall_batchingClaimAndMint_succeeds() external { function test_multicall_batchingClaimAndMint_succeeds() external {
uint256 inviterPrivateKey = 0xbeefbeef; uint256 inviterPrivateKey = 0xbeefbeef;
address inviter = vm.addr(inviterPrivateKey); address inviter = vm.addr(inviterPrivateKey);
......
//SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
/* Testing utilities */ // Testing utilities
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { AttestationStation } from "../periphery/op-nft/AttestationStation.sol"; import { AttestationStation } from "../periphery/op-nft/AttestationStation.sol";
import { OptimistAllowlist } from "../periphery/op-nft/OptimistAllowlist.sol"; import { OptimistAllowlist } from "../periphery/op-nft/OptimistAllowlist.sol";
...@@ -106,10 +106,7 @@ contract OptimistAllowlist_Initializer is Test { ...@@ -106,10 +106,7 @@ contract OptimistAllowlist_Initializer is Test {
optimistInviter.claimInvite(claimer, claimableInvite, signature); 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) function _getSignature(uint256 _signingPrivateKey, bytes32 _digest)
internal internal
pure pure
...@@ -149,42 +146,32 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer { ...@@ -149,42 +146,32 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer {
assertEq(optimistAllowlist.version(), "1.0.0"); 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 { function test_isAllowedToMint_withoutAnyAttestations_fails() external {
assertFalse(optimistAllowlist.isAllowedToMint(bob)); 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 { function test_isAllowedToMint_fromAllowlistAttestor_succeeds() external {
attestAllowlist(bob); attestAllowlist(bob);
assertTrue(optimistAllowlist.isAllowedToMint(bob)); assertTrue(optimistAllowlist.isAllowedToMint(bob));
} }
/** /// @notice After receiving a valid attestation from the Coinbase Quest attestor,
* @notice After receiving a valid attestation from the Coinbase Quest attestor, /// the account should be able to mint.
* the account should be able to mint.
*/
function test_isAllowedToMint_fromCoinbaseQuestAttestor_succeeds() external { function test_isAllowedToMint_fromCoinbaseQuestAttestor_succeeds() external {
attestCoinbaseQuest(bob); attestCoinbaseQuest(bob);
assertTrue(optimistAllowlist.isAllowedToMint(bob)); assertTrue(optimistAllowlist.isAllowedToMint(bob));
} }
/** /// @notice Account that received an attestation from the OptimistInviter contract by going
* @notice Account that received an attestation from the OptimistInviter contract by going /// through the claim invite flow should be able to mint.
* through the claim invite flow should be able to mint.
*/
function test_isAllowedToMint_fromInvite_succeeds() external { function test_isAllowedToMint_fromInvite_succeeds() external {
inviteAndClaim(bob); inviteAndClaim(bob);
assertTrue(optimistAllowlist.isAllowedToMint(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 { function test_isAllowedToMint_fromWrongAllowlistAttestor_fails() external {
// Ted is not the allowlist attestor // Ted is not the allowlist attestor
vm.prank(ted); vm.prank(ted);
...@@ -196,9 +183,7 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer { ...@@ -196,9 +183,7 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer {
assertFalse(optimistAllowlist.isAllowedToMint(bob)); 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 { function test_isAllowedToMint_fromWrongCoinbaseQuestAttestor_fails() external {
// Ted is not the coinbase quest attestor // Ted is not the coinbase quest attestor
vm.prank(ted); vm.prank(ted);
...@@ -210,10 +195,8 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer { ...@@ -210,10 +195,8 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer {
assertFalse(optimistAllowlist.isAllowedToMint(bob)); assertFalse(optimistAllowlist.isAllowedToMint(bob));
} }
/** /// @notice Claiming an invite on the non-official OptimistInviter contract should not allow
* @notice Claiming an invite on the non-official OptimistInviter contract should not allow /// minting.
* minting.
*/
function test_isAllowedToMint_fromWrongOptimistInviter_fails() external { function test_isAllowedToMint_fromWrongOptimistInviter_fails() external {
vm.prank(ted); vm.prank(ted);
attestationStation.attest( attestationStation.attest(
...@@ -224,9 +207,7 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer { ...@@ -224,9 +207,7 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer {
assertFalse(optimistAllowlist.isAllowedToMint(bob)); 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 { function test_isAllowedToMint_withMultipleAttestations_succeeds() external {
attestAllowlist(bob); attestAllowlist(bob);
attestCoinbaseQuest(bob); attestCoinbaseQuest(bob);
...@@ -246,9 +227,7 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer { ...@@ -246,9 +227,7 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer {
assertTrue(optimistAllowlist.isAllowedToMint(bob)); 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 { function test_isAllowedToMint_fromAllowlistAttestorWithFalsyValue_fails() external {
// First sends correct attestation // First sends correct attestation
attestAllowlist(bob); attestAllowlist(bob);
...@@ -264,9 +243,7 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer { ...@@ -264,9 +243,7 @@ contract OptimistAllowlistTest is OptimistAllowlist_Initializer {
assertFalse(optimistAllowlist.isAllowedToMint(bob)); 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 { function test_isAllowedToMint_fromCoinbaseQuestAttestorWithFalsyValue_fails() external {
// First sends correct attestation // First sends correct attestation
attestAllowlist(bob); attestAllowlist(bob);
......
//SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
/* Testing utilities */ // Testing utilities
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { AttestationStation } from "../periphery/op-nft/AttestationStation.sol"; import { AttestationStation } from "../periphery/op-nft/AttestationStation.sol";
import { OptimistInviter } from "../periphery/op-nft/OptimistInviter.sol"; import { OptimistInviter } from "../periphery/op-nft/OptimistInviter.sol";
...@@ -69,9 +69,7 @@ contract OptimistInviter_Initializer is Test { ...@@ -69,9 +69,7 @@ contract OptimistInviter_Initializer is Test {
_initializeContracts(); _initializeContracts();
} }
/** /// @notice Instantiates an AttestationStation, and an OptimistInviter.
* @notice Instantiates an AttestationStation, and an OptimistInviter.
*/
function _initializeContracts() internal { function _initializeContracts() internal {
attestationStation = new AttestationStation(); attestationStation = new AttestationStation();
...@@ -88,16 +86,12 @@ contract OptimistInviter_Initializer is Test { ...@@ -88,16 +86,12 @@ contract OptimistInviter_Initializer is Test {
vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp); vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp);
} }
/** /// @notice Returns a user's current invite count, as stored in the AttestationStation.
* @notice Returns a user's current invite count, as stored in the AttestationStation.
*/
function _getInviteCount(address _issuer) internal view returns (uint256) { function _getInviteCount(address _issuer) internal view returns (uint256) {
return optimistInviter.inviteCounts(_issuer); return optimistInviter.inviteCounts(_issuer);
} }
/** /// @notice Returns true if claimer has the proper attestation from OptimistInviter to mint.
* @notice Returns true if claimer has the proper attestation from OptimistInviter to mint.
*/
function _hasMintAttestation(address _claimer) internal view returns (bool) { function _hasMintAttestation(address _claimer) internal view returns (bool) {
bytes memory attestation = attestationStation.attestations( bytes memory attestation = attestationStation.attestations(
address(optimistInviter), address(optimistInviter),
...@@ -107,10 +101,7 @@ contract OptimistInviter_Initializer is Test { ...@@ -107,10 +101,7 @@ contract OptimistInviter_Initializer is Test {
return attestation.length > 0; return attestation.length > 0;
} }
/** /// @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) function _getSignature(uint256 _signingPrivateKey, bytes32 _digest)
internal internal
pure pure
...@@ -122,10 +113,8 @@ contract OptimistInviter_Initializer is Test { ...@@ -122,10 +113,8 @@ contract OptimistInviter_Initializer is Test {
return signature; return signature;
} }
/** /// @notice Signs a claimable invite with the given private key and returns the signature using
* @notice Signs a claimable invite with the given private key and returns the signature using /// correct EIP712 domain separator.
* correct EIP712 domain separator.
*/
function _issueInviteAs(uint256 _privateKey) function _issueInviteAs(uint256 _privateKey)
internal internal
returns (OptimistInviter.ClaimableInvite memory, bytes memory) returns (OptimistInviter.ClaimableInvite memory, bytes memory)
...@@ -140,11 +129,9 @@ contract OptimistInviter_Initializer is Test { ...@@ -140,11 +129,9 @@ contract OptimistInviter_Initializer is Test {
); );
} }
/** /// @notice Signs a claimable invite with the given private key and returns the signature using
* @notice Signs a claimable invite with the given private key and returns the signature using /// the given EIP712 domain separator. This assumes that the issuer's address is the
* the given EIP712 domain separator. This assumes that the issuer's address is the /// corresponding public key to _issuerPrivateKey.
* corresponding public key to _issuerPrivateKey.
*/
function _issueInviteWithEIP712Domain( function _issueInviteWithEIP712Domain(
uint256 _issuerPrivateKey, uint256 _issuerPrivateKey,
bytes memory _eip712Name, bytes memory _eip712Name,
...@@ -170,9 +157,7 @@ contract OptimistInviter_Initializer is Test { ...@@ -170,9 +157,7 @@ contract OptimistInviter_Initializer is Test {
); );
} }
/** /// @notice Commits a signature and claimer address to the OptimistInviter contract.
* @notice Commits a signature and claimer address to the OptimistInviter contract.
*/
function _commitInviteAs(address _as, bytes memory _signature) internal { function _commitInviteAs(address _as, bytes memory _signature) internal {
vm.prank(_as); vm.prank(_as);
bytes32 hashedSignature = keccak256(abi.encode(_as, _signature)); bytes32 hashedSignature = keccak256(abi.encode(_as, _signature));
...@@ -182,11 +167,9 @@ contract OptimistInviter_Initializer is Test { ...@@ -182,11 +167,9 @@ contract OptimistInviter_Initializer is Test {
assertEq(optimistInviter.commitmentTimestamps(hashedSignature), block.timestamp); assertEq(optimistInviter.commitmentTimestamps(hashedSignature), block.timestamp);
} }
/** /// @notice Signs a claimable invite with the given private key. The claimer commits then claims
* @notice Signs a claimable invite with the given private key. The claimer commits then claims /// the invite. Checks that all expected events are emitted and that state is updated
* the invite. Checks that all expected events are emitted and that state is updated /// correctly. Returns the signature and invite for use in tests.
* correctly. Returns the signature and invite for use in tests.
*/
function _issueThenClaimShouldSucceed(uint256 _issuerPrivateKey, address _claimer) function _issueThenClaimShouldSucceed(uint256 _issuerPrivateKey, address _claimer)
internal internal
returns (OptimistInviter.ClaimableInvite memory, bytes memory) returns (OptimistInviter.ClaimableInvite memory, bytes memory)
...@@ -236,10 +219,8 @@ contract OptimistInviter_Initializer is Test { ...@@ -236,10 +219,8 @@ contract OptimistInviter_Initializer is Test {
return (claimableInvite, signature); return (claimableInvite, signature);
} }
/** /// @notice Issues 3 invites to the given address. Checks that all expected events are emitted
* @notice Issues 3 invites to the given address. Checks that all expected events are emitted /// and that state is updated correctly.
* and that state is updated correctly.
*/
function _grantInvitesTo(address _to) internal { function _grantInvitesTo(address _to) internal {
address[] memory addresses = new address[](1); address[] memory addresses = new address[](1);
addresses[0] = _to; addresses[0] = _to;
...@@ -267,11 +248,9 @@ contract OptimistInviterTest is OptimistInviter_Initializer { ...@@ -267,11 +248,9 @@ contract OptimistInviterTest is OptimistInviter_Initializer {
assertEq(optimistInviter.version(), "1.0.0"); assertEq(optimistInviter.version(), "1.0.0");
} }
/** /// @notice Alice the admin should be able to give Bob, Sally, and Carol 3 invites, and the
* @notice Alice the admin should be able to give Bob, Sally, and Carol 3 invites, and the /// OptimistInviter contract should increment invite counts on inviteCounts and issue
* OptimistInviter contract should increment invite counts on inviteCounts and issue /// 'optimist.can-invite' attestations.
* 'optimist.can-invite' attestations.
*/
function test_grantInvites_adminAddingInvites_succeeds() external { function test_grantInvites_adminAddingInvites_succeeds() external {
address[] memory addresses = new address[](3); address[] memory addresses = new address[](3);
addresses[0] = bob; addresses[0] = bob;
...@@ -310,9 +289,7 @@ contract OptimistInviterTest is OptimistInviter_Initializer { ...@@ -310,9 +289,7 @@ contract OptimistInviterTest is OptimistInviter_Initializer {
assertEq(_getInviteCount(address(carolERC1271Wallet)), 3); assertEq(_getInviteCount(address(carolERC1271Wallet)), 3);
} }
/** /// @notice Bob, who is not the invite granter, should not be able to issue invites.
* @notice Bob, who is not the invite granter, should not be able to issue invites.
*/
function test_grantInvites_nonAdminAddingInvites_reverts() external { function test_grantInvites_nonAdminAddingInvites_reverts() external {
address[] memory addresses = new address[](2); address[] memory addresses = new address[](2);
addresses[0] = bob; addresses[0] = bob;
...@@ -323,9 +300,7 @@ contract OptimistInviterTest is OptimistInviter_Initializer { ...@@ -323,9 +300,7 @@ contract OptimistInviterTest is OptimistInviter_Initializer {
optimistInviter.setInviteCounts(addresses, 3); optimistInviter.setInviteCounts(addresses, 3);
} }
/** /// @notice Sally should be able to commit an invite given by by Bob.
* @notice Sally should be able to commit an invite given by by Bob.
*/
function test_commitInvite_committingForYourself_succeeds() external { function test_commitInvite_committingForYourself_succeeds() external {
_grantInvitesTo(bob); _grantInvitesTo(bob);
(, bytes memory signature) = _issueInviteAs(bobPrivateKey); (, bytes memory signature) = _issueInviteAs(bobPrivateKey);
...@@ -337,9 +312,7 @@ contract OptimistInviterTest is OptimistInviter_Initializer { ...@@ -337,9 +312,7 @@ contract OptimistInviterTest is OptimistInviter_Initializer {
assertEq(optimistInviter.commitmentTimestamps(hashedSignature), block.timestamp); assertEq(optimistInviter.commitmentTimestamps(hashedSignature), block.timestamp);
} }
/** /// @notice Sally should be able to Bob's for a different claimer, Eve.
* @notice Sally should be able to Bob's for a different claimer, Eve.
*/
function test_commitInvite_committingForSomeoneElse_succeeds() external { function test_commitInvite_committingForSomeoneElse_succeeds() external {
_grantInvitesTo(bob); _grantInvitesTo(bob);
(, bytes memory signature) = _issueInviteAs(bobPrivateKey); (, bytes memory signature) = _issueInviteAs(bobPrivateKey);
...@@ -351,9 +324,7 @@ contract OptimistInviterTest is OptimistInviter_Initializer { ...@@ -351,9 +324,7 @@ contract OptimistInviterTest is OptimistInviter_Initializer {
assertEq(optimistInviter.commitmentTimestamps(hashedSignature), block.timestamp); assertEq(optimistInviter.commitmentTimestamps(hashedSignature), block.timestamp);
} }
/** /// @notice Attempting to commit the same hash twice should revert. This prevents griefing.
* @notice Attempting to commit the same hash twice should revert. This prevents griefing.
*/
function test_commitInvite_committingSameHashTwice_reverts() external { function test_commitInvite_committingSameHashTwice_reverts() external {
_grantInvitesTo(bob); _grantInvitesTo(bob);
(, bytes memory signature) = _issueInviteAs(bobPrivateKey); (, bytes memory signature) = _issueInviteAs(bobPrivateKey);
...@@ -368,18 +339,14 @@ contract OptimistInviterTest is OptimistInviter_Initializer { ...@@ -368,18 +339,14 @@ contract OptimistInviterTest is OptimistInviter_Initializer {
optimistInviter.commitInvite(hashedSignature); optimistInviter.commitInvite(hashedSignature);
} }
/** /// @notice Bob issues signature, and Sally claims the invite. Bob's invite count should be
* @notice Bob issues signature, and Sally claims the invite. Bob's invite count should be /// decremented, and Sally should be able to mint.
* decremented, and Sally should be able to mint.
*/
function test_claimInvite_succeeds() external { function test_claimInvite_succeeds() external {
_grantInvitesTo(bob); _grantInvitesTo(bob);
_issueThenClaimShouldSucceed(bobPrivateKey, sally); _issueThenClaimShouldSucceed(bobPrivateKey, sally);
} }
/** /// @notice Bob issues signature, and Ted commits the invite for Sally. Eve claims for Sally.
* @notice Bob issues signature, and Ted commits the invite for Sally. Eve claims for Sally.
*/
function test_claimInvite_claimForSomeoneElse_succeeds() external { function test_claimInvite_claimForSomeoneElse_succeeds() external {
_grantInvitesTo(bob); _grantInvitesTo(bob);
( (
...@@ -428,9 +395,7 @@ contract OptimistInviterTest is OptimistInviter_Initializer { ...@@ -428,9 +395,7 @@ contract OptimistInviterTest is OptimistInviter_Initializer {
optimistInviter.claimInvite(sally, claimableInvite, signature); optimistInviter.claimInvite(sally, claimableInvite, signature);
} }
/** /// @notice Signature issued for previous versions of the contract should fail.
* @notice Signature issued for previous versions of the contract should fail.
*/
function test_claimInvite_usingSignatureIssuedForDifferentVersion_reverts() external { function test_claimInvite_usingSignatureIssuedForDifferentVersion_reverts() external {
_grantInvitesTo(bob); _grantInvitesTo(bob);
( (
...@@ -452,10 +417,8 @@ contract OptimistInviterTest is OptimistInviter_Initializer { ...@@ -452,10 +417,8 @@ contract OptimistInviterTest is OptimistInviter_Initializer {
optimistInviter.claimInvite(sally, claimableInvite, signature); optimistInviter.claimInvite(sally, claimableInvite, signature);
} }
/** /// @notice Replay attack for signature issued for contract on different chain (ie. mainnet)
* @notice Replay attack for signature issued for contract on different chain (ie. mainnet) /// should fail.
* should fail.
*/
function test_claimInvite_usingSignatureIssuedForDifferentChain_reverts() external { function test_claimInvite_usingSignatureIssuedForDifferentChain_reverts() external {
_grantInvitesTo(bob); _grantInvitesTo(bob);
( (
...@@ -477,10 +440,8 @@ contract OptimistInviterTest is OptimistInviter_Initializer { ...@@ -477,10 +440,8 @@ contract OptimistInviterTest is OptimistInviter_Initializer {
optimistInviter.claimInvite(sally, claimableInvite, signature); optimistInviter.claimInvite(sally, claimableInvite, signature);
} }
/** /// @notice Replay attack for signature issued for instantiation of the OptimistInviter contract
* @notice Replay attack for signature issued for instantiation of the OptimistInviter contract /// on a different address should fail.
* on a different address should fail.
*/
function test_claimInvite_usingSignatureIssuedForDifferentContract_reverts() external { function test_claimInvite_usingSignatureIssuedForDifferentContract_reverts() external {
_grantInvitesTo(bob); _grantInvitesTo(bob);
( (
...@@ -502,9 +463,7 @@ contract OptimistInviterTest is OptimistInviter_Initializer { ...@@ -502,9 +463,7 @@ contract OptimistInviterTest is OptimistInviter_Initializer {
optimistInviter.claimInvite(sally, claimableInvite, signature); optimistInviter.claimInvite(sally, claimableInvite, signature);
} }
/** /// @notice Attempting to claim again using the same signature again should fail.
* @notice Attempting to claim again using the same signature again should fail.
*/
function test_claimInvite_replayingUsedNonce_reverts() external { function test_claimInvite_replayingUsedNonce_reverts() external {
_grantInvitesTo(bob); _grantInvitesTo(bob);
...@@ -527,11 +486,9 @@ contract OptimistInviterTest is OptimistInviter_Initializer { ...@@ -527,11 +486,9 @@ contract OptimistInviterTest is OptimistInviter_Initializer {
optimistInviter.claimInvite(carol, claimableInvite, signature); optimistInviter.claimInvite(carol, claimableInvite, signature);
} }
/** /// @notice Issuing signatures through a contract that implements ERC1271 should succeed (ie.
* @notice Issuing signatures through a contract that implements ERC1271 should succeed (ie. /// Gnosis Safe or other smart contract wallets). Carol is using a ERC1271 contract
* Gnosis Safe or other smart contract wallets). Carol is using a ERC1271 contract /// wallet that is simply backed by her private key.
* wallet that is simply backed by her private key.
*/
function test_claimInvite_usingERC1271Wallet_succeeds() external { function test_claimInvite_usingERC1271Wallet_succeeds() external {
_grantInvitesTo(address(carolERC1271Wallet)); _grantInvitesTo(address(carolERC1271Wallet));
...@@ -560,10 +517,8 @@ contract OptimistInviterTest is OptimistInviter_Initializer { ...@@ -560,10 +517,8 @@ contract OptimistInviterTest is OptimistInviter_Initializer {
assertEq(_getInviteCount(address(carolERC1271Wallet)), 2); assertEq(_getInviteCount(address(carolERC1271Wallet)), 2);
} }
/** /// @notice Claimer must commit the signature before claiming the invite. Sally attempts to
* @notice Claimer must commit the signature before claiming the invite. Sally attempts to /// claim the Bob's invite without committing the signature first.
* claim the Bob's invite without committing the signature first.
*/
function test_claimInvite_withoutCommittingHash_reverts() external { function test_claimInvite_withoutCommittingHash_reverts() external {
_grantInvitesTo(bob); _grantInvitesTo(bob);
( (
...@@ -576,9 +531,7 @@ contract OptimistInviterTest is OptimistInviter_Initializer { ...@@ -576,9 +531,7 @@ contract OptimistInviterTest is OptimistInviter_Initializer {
optimistInviter.claimInvite(sally, claimableInvite, signature); optimistInviter.claimInvite(sally, claimableInvite, signature);
} }
/** /// @notice Using a signature that doesn't correspond to the claimable invite should fail.
* @notice Using a signature that doesn't correspond to the claimable invite should fail.
*/
function test_claimInvite_withIncorrectSignature_reverts() external { function test_claimInvite_withIncorrectSignature_reverts() external {
_grantInvitesTo(carol); _grantInvitesTo(carol);
_grantInvitesTo(bob); _grantInvitesTo(bob);
...@@ -598,10 +551,8 @@ contract OptimistInviterTest is OptimistInviter_Initializer { ...@@ -598,10 +551,8 @@ contract OptimistInviterTest is OptimistInviter_Initializer {
optimistInviter.claimInvite(sally, bobClaimableInvite, carolSignature); optimistInviter.claimInvite(sally, bobClaimableInvite, carolSignature);
} }
/** /// @notice Attempting to use a signature from a issuer who never was granted invites should
* @notice Attempting to use a signature from a issuer who never was granted invites should /// fail.
* fail.
*/
function test_claimInvite_whenIssuerNeverReceivedInvites_reverts() external { function test_claimInvite_whenIssuerNeverReceivedInvites_reverts() external {
// Bob was never granted any invites, but issues an invite for Eve // Bob was never granted any invites, but issues an invite for Eve
( (
...@@ -617,13 +568,10 @@ contract OptimistInviterTest is OptimistInviter_Initializer { ...@@ -617,13 +568,10 @@ contract OptimistInviterTest is OptimistInviter_Initializer {
optimistInviter.claimInvite(sally, claimableInvite, signature); optimistInviter.claimInvite(sally, claimableInvite, signature);
} }
/** /// @notice Attempting to use a signature from a issuer who has no more invites should fail.
* @notice Attempting to use a signature from a issuer who has no more invites should fail. /// Bob has 3 invites, but issues 4 invites for Sally, Carol, Ted, and Eve. Only the
* Bob has 3 invites, but issues 4 invites for Sally, Carol, Ted, and Eve. Only the /// first 3 invites should be claimable. The last claimer, Eve, should not be able to
* first 3 invites should be claimable. The last claimer, Eve, should not be able to /// claim the invite.
* claim the invite.
*
*/
function test_claimInvite_whenIssuerHasNoInvitesLeft_reverts() external { function test_claimInvite_whenIssuerHasNoInvitesLeft_reverts() external {
_grantInvitesTo(bob); _grantInvitesTo(bob);
......
...@@ -3,10 +3,8 @@ pragma solidity ^0.8.0; ...@@ -3,10 +3,8 @@ pragma solidity ^0.8.0;
import { Bytes32AddressLib } from "@rari-capital/solmate/src/utils/Bytes32AddressLib.sol"; import { Bytes32AddressLib } from "@rari-capital/solmate/src/utils/Bytes32AddressLib.sol";
/** /// @title LibRLP
* @title LibRLP /// @notice Via https://github.com/Rari-Capital/solmate/issues/207.
* @notice Via https://github.com/Rari-Capital/solmate/issues/207.
*/
library LibRLP { library LibRLP {
using Bytes32AddressLib for bytes32; using Bytes32AddressLib for bytes32;
......
//SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
/* Testing utilities */ // Testing utilities
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
import { CallRecorder } from "./Helpers.sol"; import { CallRecorder } from "./Helpers.sol";
import { Reverter } from "./Helpers.sol"; import { Reverter } from "./Helpers.sol";
...@@ -34,12 +34,12 @@ contract Transactor_Initializer is Test { ...@@ -34,12 +34,12 @@ contract Transactor_Initializer is Test {
} }
contract TransactorTest is Transactor_Initializer { contract TransactorTest is Transactor_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 { function test_constructor_succeeds() external {
assertEq(address(alice), transactor.owner()); assertEq(address(alice), transactor.owner());
} }
// Tests CALL, should do a call to target /// @notice Tests CALL, should do a call to target
function test_call_succeeds() external { function test_call_succeeds() external {
// Initialize call data // Initialize call data
bytes memory data = abi.encodeWithSelector(callRecorded.record.selector); bytes memory data = abi.encodeWithSelector(callRecorded.record.selector);
...@@ -49,7 +49,7 @@ contract TransactorTest is Transactor_Initializer { ...@@ -49,7 +49,7 @@ contract TransactorTest is Transactor_Initializer {
transactor.CALL(address(callRecorded), data, 200_000 wei); transactor.CALL(address(callRecorded), data, 200_000 wei);
} }
// It should revert if called by non-owner /// @notice It should revert if called by non-owner
function test_call_unauthorized_reverts() external { function test_call_unauthorized_reverts() external {
// Initialize call data // Initialize call data
bytes memory data = abi.encodeWithSelector(callRecorded.record.selector); bytes memory data = abi.encodeWithSelector(callRecorded.record.selector);
...@@ -59,6 +59,7 @@ contract TransactorTest is Transactor_Initializer { ...@@ -59,6 +59,7 @@ contract TransactorTest is Transactor_Initializer {
transactor.CALL(address(callRecorded), data, 200_000 wei); transactor.CALL(address(callRecorded), data, 200_000 wei);
} }
/// @notice Deletate call succeeds.
function test_delegateCall_succeeds() external { function test_delegateCall_succeeds() external {
// Initialize call data // Initialize call data
bytes memory data = abi.encodeWithSelector(reverter.doRevert.selector); bytes memory data = abi.encodeWithSelector(reverter.doRevert.selector);
...@@ -68,7 +69,7 @@ contract TransactorTest is Transactor_Initializer { ...@@ -68,7 +69,7 @@ contract TransactorTest is Transactor_Initializer {
transactor.DELEGATECALL(address(reverter), data); transactor.DELEGATECALL(address(reverter), data);
} }
// It should revert if called by non-owner /// @notice It should revert if called by non-owner
function test_delegateCall_unauthorized_reverts() external { function test_delegateCall_unauthorized_reverts() external {
// Initialize call data // Initialize call data
bytes memory data = abi.encodeWithSelector(reverter.doRevert.selector); bytes memory data = abi.encodeWithSelector(reverter.doRevert.selector);
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
...@@ -7,9 +8,7 @@ import { AddressAliasHelper } from "../../vendor/AddressAliasHelper.sol"; ...@@ -7,9 +8,7 @@ import { AddressAliasHelper } from "../../vendor/AddressAliasHelper.sol";
contract AddressAliasHelper_Converter { contract AddressAliasHelper_Converter {
bool public failedRoundtrip; bool public failedRoundtrip;
/** /// @dev Allows the actor to convert L1 to L2 addresses and vice versa.
* @dev Allows the actor to convert L1 to L2 addresses and vice versa.
*/
function convertRoundTrip(address addr) external { function convertRoundTrip(address addr) external {
// Alias our address // Alias our address
address aliasedAddr = AddressAliasHelper.applyL1ToL2Alias(addr); address aliasedAddr = AddressAliasHelper.applyL1ToL2Alias(addr);
...@@ -39,12 +38,11 @@ contract AddressAliasHelper_AddressAliasing_Invariant is StdInvariant, Test { ...@@ -39,12 +38,11 @@ contract AddressAliasHelper_AddressAliasing_Invariant is StdInvariant, Test {
targetSelector(selector); targetSelector(selector);
} }
/** /// @custom:invariant Address aliases are always able to be undone.
* @custom:invariant Address aliases are always able to be undone. ///
* /// Asserts that an address that has been aliased with
* Asserts that an address that has been aliased with `applyL1ToL2Alias` can always /// `applyL1ToL2Alias` can always be unaliased with
* be unaliased with `undoL1ToL2Alias`. /// `undoL1ToL2Alias`.
*/
function invariant_round_trip_aliasing() external { function invariant_round_trip_aliasing() external {
// ASSERTION: The round trip aliasing done in testRoundTrip(...) should never fail. // ASSERTION: The round trip aliasing done in testRoundTrip(...) should never fail.
assertEq(actor.failedRoundtrip(), false); assertEq(actor.failedRoundtrip(), false);
......
...@@ -16,10 +16,8 @@ contract Burn_EthBurner is StdUtils { ...@@ -16,10 +16,8 @@ contract Burn_EthBurner is StdUtils {
vm = _vm; vm = _vm;
} }
/** /// @notice Takes an integer amount of eth to burn through the Burn library and
* @notice Takes an integer amount of eth to burn through the Burn library and /// updates the contract state if an incorrect amount of eth moved from the contract
* updates the contract state if an incorrect amount of eth moved from the contract
*/
function burnEth(uint256 _value) external { function burnEth(uint256 _value) external {
uint256 preBurnvalue = bound(_value, 0, type(uint128).max); uint256 preBurnvalue = bound(_value, 0, type(uint128).max);
...@@ -59,12 +57,10 @@ contract Burn_BurnEth_Invariant is StdInvariant, Test { ...@@ -59,12 +57,10 @@ contract Burn_BurnEth_Invariant is StdInvariant, Test {
targetSelector(selector); targetSelector(selector);
} }
/** /// @custom:invariant `eth(uint256)` always burns the exact amount of eth passed.
* @custom:invariant `eth(uint256)` always burns the exact amount of eth passed. ///
* /// Asserts that when `Burn.eth(uint256)` is called, it always
* Asserts that when `Burn.eth(uint256)` is called, it always burns the exact amount /// burns the exact amount of ETH passed to the function.
* of ETH passed to the function.
*/
function invariant_burn_eth() external { function invariant_burn_eth() external {
// ASSERTION: The amount burned should always match the amount passed exactly // ASSERTION: The amount burned should always match the amount passed exactly
assertEq(actor.failedEthBurn(), false); assertEq(actor.failedEthBurn(), false);
......
...@@ -16,11 +16,9 @@ contract Burn_GasBurner is StdUtils { ...@@ -16,11 +16,9 @@ contract Burn_GasBurner is StdUtils {
vm = _vm; vm = _vm;
} }
/** /// @notice Takes an integer amount of gas to burn through the Burn library and
* @notice Takes an integer amount of gas to burn through the Burn library and /// updates the contract state if at least that amount of gas was not burned
* updates the contract state if at least that amount of gas was not burned /// by the library
* by the library
*/
function burnGas(uint256 _value) external { function burnGas(uint256 _value) external {
// cap the value to the max resource limit // cap the value to the max resource limit
uint256 MAX_RESOURCE_LIMIT = 8_000_000; uint256 MAX_RESOURCE_LIMIT = 8_000_000;
...@@ -59,12 +57,10 @@ contract Burn_BurnGas_Invariant is StdInvariant, Test { ...@@ -59,12 +57,10 @@ contract Burn_BurnGas_Invariant is StdInvariant, Test {
targetSelector(selector); targetSelector(selector);
} }
/** /// @custom:invariant `gas(uint256)` always burns at least the amount of gas passed.
* @custom:invariant `gas(uint256)` always burns at least the amount of gas passed. ///
* /// Asserts that when `Burn.gas(uint256)` is called, it always burns
* Asserts that when `Burn.gas(uint256)` is called, it always burns at least the amount /// at least the amount of gas passed to the function.
* of gas passed to the function.
*/
function invariant_burn_gas() external { function invariant_burn_gas() external {
// ASSERTION: The amount burned should always match the amount passed exactly // ASSERTION: The amount burned should always match the amount passed exactly
assertEq(actor.failedGasBurn(), false); assertEq(actor.failedGasBurn(), false);
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { StdUtils } from "forge-std/StdUtils.sol"; import { StdUtils } from "forge-std/StdUtils.sol";
...@@ -36,9 +37,8 @@ contract RelayActor is StdUtils { ...@@ -36,9 +37,8 @@ contract RelayActor is StdUtils {
doFail = _doFail; doFail = _doFail;
} }
/** /// @notice Relays a message to the `L1CrossDomainMessenger` with a random `version`,
* Relays a message to the `L1CrossDomainMessenger` with a random `version`, and `_message`. /// and `_message`.
*/
function relay( function relay(
uint8 _version, uint8 _version,
uint8 _value, uint8 _value,
...@@ -143,21 +143,19 @@ contract XDM_MinGasLimits_Succeeds is XDM_MinGasLimits { ...@@ -143,21 +143,19 @@ contract XDM_MinGasLimits_Succeeds is XDM_MinGasLimits {
super.init(false); super.init(false);
} }
/** /// @custom:invariant A call to `relayMessage` should succeed if at least the minimum gas limit
* @custom:invariant A call to `relayMessage` should succeed if at least the minimum gas limit /// can be supplied to the target context, there is enough gas to complete
* can be supplied to the target context, there is enough gas to complete /// execution of `relayMessage` after the target context's execution is
* execution of `relayMessage` after the target context's execution is /// finished, and the target context did not revert.
* finished, and the target context did not revert. ///
* /// There are two minimum gas limits here:
* There are two minimum gas limits here: ///
* /// - The outer min gas limit is for the call from the `OptimismPortal` to the
* - The outer min gas limit is for the call from the `OptimismPortal` to the /// `L1CrossDomainMessenger`, and it can be retrieved by calling the xdm's
* `L1CrossDomainMessenger`, and it can be retrieved by calling the xdm's `baseGas` function /// `baseGas` function with the `message` and inner limit.
* with the `message` and inner limit. ///
* /// - The inner min gas limit is for the call from the
* - The inner min gas limit is for the call from the `L1CrossDomainMessenger` to the target /// `L1CrossDomainMessenger` to the target contract.
* contract.
*/
function invariant_minGasLimits() external { function invariant_minGasLimits() external {
uint256 length = actor.numHashes(); uint256 length = actor.numHashes();
for (uint256 i = 0; i < length; ++i) { for (uint256 i = 0; i < length; ++i) {
...@@ -177,22 +175,20 @@ contract XDM_MinGasLimits_Reverts is XDM_MinGasLimits { ...@@ -177,22 +175,20 @@ contract XDM_MinGasLimits_Reverts is XDM_MinGasLimits {
super.init(true); super.init(true);
} }
/** /// @custom:invariant A call to `relayMessage` should assign the message hash to the
* @custom:invariant A call to `relayMessage` should assign the message hash to the /// `failedMessages` mapping if not enough gas is supplied to forward
* `failedMessages` mapping if not enough gas is supplied to forward /// `minGasLimit` to the target context or if there is not enough gas to
* `minGasLimit` to the target context or if there is not enough gas to /// complete execution of `relayMessage` after the target context's execution
* complete execution of `relayMessage` after the target context's execution /// is finished.
* is finished. ///
* /// There are two minimum gas limits here:
* There are two minimum gas limits here: ///
* /// - The outer min gas limit is for the call from the `OptimismPortal` to the
* - The outer min gas limit is for the call from the `OptimismPortal` to the /// `L1CrossDomainMessenger`, and it can be retrieved by calling the xdm's
* `L1CrossDomainMessenger`, and it can be retrieved by calling the xdm's `baseGas` function /// `baseGas` function with the `message` and inner limit.
* with the `message` and inner limit. ///
* /// - The inner min gas limit is for the call from the
* - The inner min gas limit is for the call from the `L1CrossDomainMessenger` to the target /// `L1CrossDomainMessenger` to the target contract.
* contract.
*/
function invariant_minGasLimits() external { function invariant_minGasLimits() external {
uint256 length = actor.numHashes(); uint256 length = actor.numHashes();
for (uint256 i = 0; i < length; ++i) { for (uint256 i = 0; i < length; ++i) {
......
...@@ -9,11 +9,9 @@ contract Encoding_Converter { ...@@ -9,11 +9,9 @@ contract Encoding_Converter {
bool public failedRoundtripAToB; bool public failedRoundtripAToB;
bool public failedRoundtripBToA; bool public failedRoundtripBToA;
/** /// @notice Takes a pair of integers to be encoded into a versioned nonce with the
* @notice Takes a pair of integers to be encoded into a versioned nonce with the /// Encoding library and then decoded and updates the test contract's state
* Encoding library and then decoded and updates the test contract's state /// indicating if the round trip encoding failed.
* indicating if the round trip encoding failed.
*/
function convertRoundTripAToB(uint240 _nonce, uint16 _version) external { function convertRoundTripAToB(uint240 _nonce, uint16 _version) external {
// Encode the nonce and version // Encode the nonce and version
uint256 encodedVersionedNonce = Encoding.encodeVersionedNonce(_nonce, _version); uint256 encodedVersionedNonce = Encoding.encodeVersionedNonce(_nonce, _version);
...@@ -30,11 +28,9 @@ contract Encoding_Converter { ...@@ -30,11 +28,9 @@ contract Encoding_Converter {
} }
} }
/** /// @notice Takes an integer representing a packed version and nonce and attempts
* @notice Takes an integer representing a packed version and nonce and attempts /// to decode them using the Encoding library before re-encoding and updates
* to decode them using the Encoding library before re-encoding and updates /// the test contract's state indicating if the round trip encoding failed.
* the test contract's state indicating if the round trip encoding failed.
*/
function convertRoundTripBToA(uint256 _versionedNonce) external { function convertRoundTripBToA(uint256 _versionedNonce) external {
// Decode the nonce and version // Decode the nonce and version
uint240 decodedNonce; uint240 decodedNonce;
...@@ -68,22 +64,19 @@ contract Encoding_Invariant is StdInvariant, Test { ...@@ -68,22 +64,19 @@ contract Encoding_Invariant is StdInvariant, Test {
targetSelector(selector); targetSelector(selector);
} }
/** /// @custom:invariant `convertRoundTripAToB` never fails.
* @custom:invariant `convertRoundTripAToB` never fails. ///
* /// Asserts that a raw versioned nonce can be encoded / decoded
* Asserts that a raw versioned nonce can be encoded / decoded to reach the same raw value. /// to reach the same raw value.
*/
function invariant_round_trip_encoding_AToB() external { function invariant_round_trip_encoding_AToB() external {
// ASSERTION: The round trip encoding done in testRoundTripAToB(...) // ASSERTION: The round trip encoding done in testRoundTripAToB(...)
assertEq(actor.failedRoundtripAToB(), false); assertEq(actor.failedRoundtripAToB(), false);
} }
/** /// @custom:invariant `convertRoundTripBToA` never fails.
* @custom:invariant `convertRoundTripBToA` never fails. ///
* /// Asserts that an encoded versioned nonce can always be decoded /
* Asserts that an encoded versioned nonce can always be decoded / re-encoded to reach /// re-encoded to reach the same encoded value.
* the same encoded value.
*/
function invariant_round_trip_encoding_BToA() external { function invariant_round_trip_encoding_BToA() external {
// ASSERTION: The round trip encoding done in testRoundTripBToA should never // ASSERTION: The round trip encoding done in testRoundTripBToA should never
// fail. // fail.
......
...@@ -11,10 +11,9 @@ contract Hash_CrossDomainHasher { ...@@ -11,10 +11,9 @@ contract Hash_CrossDomainHasher {
bool public failedCrossDomainHashV0; bool public failedCrossDomainHashV0;
bool public failedCrossDomainHashV1; bool public failedCrossDomainHashV1;
/** /// @notice Takes the necessary parameters to perform a cross domain hash with a randomly
* @notice Takes the necessary parameters to perform a cross domain hash with a randomly /// generated version. Only schema versions 0 and 1 are supported and all others
* generated version. Only schema versions 0 and 1 are supported and all others should revert. /// should revert.
*/
function hashCrossDomainMessageHighVersion( function hashCrossDomainMessageHighVersion(
uint16 _version, uint16 _version,
uint240 _nonce, uint240 _nonce,
...@@ -37,10 +36,9 @@ contract Hash_CrossDomainHasher { ...@@ -37,10 +36,9 @@ contract Hash_CrossDomainHasher {
} }
} }
/** /// @notice Takes the necessary parameters to perform a cross domain hash using the v0 schema
* @notice Takes the necessary parameters to perform a cross domain hash using the v0 schema /// and compares the output of a call to the unversioned function to the v0 function
* and compares the output of a call to the unversioned function to the v0 function directly /// directly.
*/
function hashCrossDomainMessageV0( function hashCrossDomainMessageV0(
uint240 _nonce, uint240 _nonce,
address _sender, address _sender,
...@@ -75,10 +73,9 @@ contract Hash_CrossDomainHasher { ...@@ -75,10 +73,9 @@ contract Hash_CrossDomainHasher {
} }
} }
/** /// @notice Takes the necessary parameters to perform a cross domain hash using the v1 schema
* @notice Takes the necessary parameters to perform a cross domain hash using the v1 schema /// and compares the output of a call to the unversioned function to the v1 function
* and compares the output of a call to the unversioned function to the v1 function directly /// directly.
*/
function hashCrossDomainMessageV1( function hashCrossDomainMessageV1(
uint240 _nonce, uint240 _nonce,
address _sender, address _sender,
...@@ -133,36 +130,31 @@ contract Hashing_Invariant is StdInvariant, Test { ...@@ -133,36 +130,31 @@ contract Hashing_Invariant is StdInvariant, Test {
targetSelector(selector); targetSelector(selector);
} }
/** /// @custom:invariant `hashCrossDomainMessage` reverts if `version` is > `1`.
* @custom:invariant `hashCrossDomainMessage` reverts if `version` is > `1`. ///
* /// The `hashCrossDomainMessage` function should always revert if
* The `hashCrossDomainMessage` function should always revert if the `version` passed is > `1`. /// the `version` passed is > `1`.
*/
function invariant_hash_xdomain_msg_high_version() external { function invariant_hash_xdomain_msg_high_version() external {
// ASSERTION: The round trip aliasing done in testRoundTrip(...) should never fail. // ASSERTION: The round trip aliasing done in testRoundTrip(...) should never fail.
assertFalse(actor.failedCrossDomainHashHighVersion()); assertFalse(actor.failedCrossDomainHashHighVersion());
} }
/** /// @custom:invariant `version` = `0`: `hashCrossDomainMessage` and `hashCrossDomainMessageV0`
* @custom:invariant `version` = `0`: `hashCrossDomainMessage` and `hashCrossDomainMessageV0` /// are equivalent.
* are equivalent. ///
* /// If the version passed is 0, `hashCrossDomainMessage` and
* If the version passed is 0, `hashCrossDomainMessage` and `hashCrossDomainMessageV0` should be /// `hashCrossDomainMessageV0` should be equivalent.
* equivalent.
*/
function invariant_hash_xdomain_msg_0() external { function invariant_hash_xdomain_msg_0() external {
// ASSERTION: A call to hashCrossDomainMessage and hashCrossDomainMessageV0 // ASSERTION: A call to hashCrossDomainMessage and hashCrossDomainMessageV0
// should always match when the version passed is 0 // should always match when the version passed is 0
assertFalse(actor.failedCrossDomainHashV0()); assertFalse(actor.failedCrossDomainHashV0());
} }
/** /// @custom:invariant `version` = `1`: `hashCrossDomainMessage` and `hashCrossDomainMessageV1`
* @custom:invariant `version` = `1`: `hashCrossDomainMessage` and `hashCrossDomainMessageV1` /// are equivalent.
* are equivalent. ///
* /// If the version passed is 1, `hashCrossDomainMessage` and
* If the version passed is 1, `hashCrossDomainMessage` and `hashCrossDomainMessageV1` should be /// `hashCrossDomainMessageV1` should be equivalent.
* equivalent.
*/
function invariant_hash_xdomain_msg_1() external { function invariant_hash_xdomain_msg_1() external {
// ASSERTION: A call to hashCrossDomainMessage and hashCrossDomainMessageV1 // ASSERTION: A call to hashCrossDomainMessage and hashCrossDomainMessageV1
// should always match when the version passed is 1 // should always match when the version passed is 1
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { L2OutputOracle_Initializer } from "../CommonTest.t.sol"; import { L2OutputOracle_Initializer } from "../CommonTest.t.sol";
...@@ -13,9 +14,7 @@ contract L2OutputOracle_Proposer { ...@@ -13,9 +14,7 @@ contract L2OutputOracle_Proposer {
vm = _vm; vm = _vm;
} }
/** /// @dev Allows the actor to propose an L2 output to the `L2OutputOracle`
* @dev Allows the actor to propose an L2 output to the `L2OutputOracle`
*/
function proposeL2Output( function proposeL2Output(
bytes32 _outputRoot, bytes32 _outputRoot,
uint256 _l2BlockNumber, uint256 _l2BlockNumber,
...@@ -49,13 +48,11 @@ contract L2OutputOracle_MonotonicBlockNumIncrease_Invariant is L2OutputOracle_In ...@@ -49,13 +48,11 @@ contract L2OutputOracle_MonotonicBlockNumIncrease_Invariant is L2OutputOracle_In
targetSelector(selector); targetSelector(selector);
} }
/** /// @custom:invariant The block number of the output root proposals should monotonically
* @custom:invariant The block number of the output root proposals should monotonically /// increase.
* increase. ///
* /// When a new output is submitted, it should never be allowed to
* When a new output is submitted, it should never be allowed to correspond to a block /// correspond to a block number that is less than the current output.
* number that is less than the current output.
*/
function invariant_monotonicBlockNumIncrease() external { function invariant_monotonicBlockNumIncrease() external {
// Assert that the block number of proposals must monotonically increase. // Assert that the block number of proposals must monotonically increase.
assertTrue(oracle.nextBlockNumber() >= oracle.latestBlockNumber()); assertTrue(oracle.nextBlockNumber() >= oracle.latestBlockNumber());
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { StdUtils } from "forge-std/Test.sol"; import { StdUtils } from "forge-std/Test.sol";
...@@ -148,13 +149,11 @@ contract OptimismPortal_Deposit_Invariant is Portal_Initializer { ...@@ -148,13 +149,11 @@ contract OptimismPortal_Deposit_Invariant is Portal_Initializer {
targetSelector(selector); targetSelector(selector);
} }
/** /// @custom:invariant Deposits of any value should always succeed unless
* @custom:invariant Deposits of any value should always succeed unless /// `_to` = `address(0)` or `_isCreation` = `true`.
* `_to` = `address(0)` or `_isCreation` = `true`. ///
* /// All deposits, barring creation transactions and transactions
* All deposits, barring creation transactions and transactions sent to `address(0)`, /// sent to `address(0)`, should always succeed.
* should always succeed.
*/
function invariant_deposit_completes() external { function invariant_deposit_completes() external {
assertEq(actor.failedToComplete(), false); assertEq(actor.failedToComplete(), false);
} }
...@@ -178,13 +177,11 @@ contract OptimismPortal_CannotTimeTravel is OptimismPortal_Invariant_Harness { ...@@ -178,13 +177,11 @@ contract OptimismPortal_CannotTimeTravel is OptimismPortal_Invariant_Harness {
excludeSender(address(multisig)); excludeSender(address(multisig));
} }
/** /// @custom:invariant `finalizeWithdrawalTransaction` should revert if the finalization
* @custom:invariant `finalizeWithdrawalTransaction` should revert if the finalization /// period has not elapsed.
* period has not elapsed. ///
* /// A withdrawal that has been proven should not be able to be finalized
* A withdrawal that has been proven should not be able to be finalized until after /// until after the finalization period has elapsed.
* the finalization period has elapsed.
*/
function invariant_cannotFinalizeBeforePeriodHasPassed() external { function invariant_cannotFinalizeBeforePeriodHasPassed() external {
vm.expectRevert("OptimismPortal: proven withdrawal finalization period has not elapsed"); vm.expectRevert("OptimismPortal: proven withdrawal finalization period has not elapsed");
op.finalizeWithdrawalTransaction(_defaultTx); op.finalizeWithdrawalTransaction(_defaultTx);
...@@ -215,13 +212,11 @@ contract OptimismPortal_CannotFinalizeTwice is OptimismPortal_Invariant_Harness ...@@ -215,13 +212,11 @@ contract OptimismPortal_CannotFinalizeTwice is OptimismPortal_Invariant_Harness
excludeSender(address(multisig)); excludeSender(address(multisig));
} }
/** /// @custom:invariant `finalizeWithdrawalTransaction` should revert if the withdrawal
* @custom:invariant `finalizeWithdrawalTransaction` should revert if the withdrawal /// has already been finalized.
* has already been finalized. ///
* /// Ensures that there is no chain of calls that can be made that
* Ensures that there is no chain of calls that can be made that allows a withdrawal /// allows a withdrawal to be finalized twice.
* to be finalized twice.
*/
function invariant_cannotFinalizeTwice() external { function invariant_cannotFinalizeTwice() external {
vm.expectRevert("OptimismPortal: withdrawal has already been finalized"); vm.expectRevert("OptimismPortal: withdrawal has already been finalized");
op.finalizeWithdrawalTransaction(_defaultTx); op.finalizeWithdrawalTransaction(_defaultTx);
...@@ -249,14 +244,13 @@ contract OptimismPortal_CanAlwaysFinalizeAfterWindow is OptimismPortal_Invariant ...@@ -249,14 +244,13 @@ contract OptimismPortal_CanAlwaysFinalizeAfterWindow is OptimismPortal_Invariant
excludeSender(address(multisig)); excludeSender(address(multisig));
} }
/** /// @custom:invariant A withdrawal should **always** be able to be finalized
* @custom:invariant A withdrawal should **always** be able to be finalized /// `FINALIZATION_PERIOD_SECONDS` after it was successfully proven.
* `FINALIZATION_PERIOD_SECONDS` after it was successfully proven. ///
* /// This invariant asserts that there is no chain of calls that can
* This invariant asserts that there is no chain of calls that can be made that /// be made that will prevent a withdrawal from being finalized
* will prevent a withdrawal from being finalized exactly `FINALIZATION_PERIOD_SECONDS` /// exactly `FINALIZATION_PERIOD_SECONDS` after it was successfully
* after it was successfully proven. /// proven.
*/
function invariant_canAlwaysFinalize() external { function invariant_canAlwaysFinalize() external {
uint256 bobBalanceBefore = address(bob).balance; uint256 bobBalanceBefore = address(bob).balance;
......
...@@ -45,10 +45,8 @@ contract ResourceMetering_User is StdUtils, ResourceMetering { ...@@ -45,10 +45,8 @@ contract ResourceMetering_User is StdUtils, ResourceMetering {
return rcfg; return rcfg;
} }
/** /// @notice Takes the necessary parameters to allow us to burn arbitrary amounts of gas to test
* @notice Takes the necessary parameters to allow us to burn arbitrary amounts of gas to test /// the underlying resource metering/gas market logic
* the underlying resource metering/gas market logic
*/
function burn(uint256 _gasToBurn, bool _raiseBaseFee) public { function burn(uint256 _gasToBurn, bool _raiseBaseFee) public {
// Part 1: we cache the current param values and do some basic checks on them. // Part 1: we cache the current param values and do some basic checks on them.
uint256 cachedPrevBaseFee = uint256(params.prevBaseFee); uint256 cachedPrevBaseFee = uint256(params.prevBaseFee);
...@@ -169,78 +167,67 @@ contract ResourceMetering_Invariant is StdInvariant, Test { ...@@ -169,78 +167,67 @@ contract ResourceMetering_Invariant is StdInvariant, Test {
targetSelector(selector); targetSelector(selector);
} }
/** /// @custom:invariant The base fee should increase if the last block used more
* @custom:invariant The base fee should increase if the last block used more /// than the target amount of gas.
* than the target amount of gas ///
* /// If the last block used more than the target amount of gas
* If the last block used more than the target amount of gas (and there were no /// (and there were no empty blocks in between), ensure this
* empty blocks in between), ensure this block's baseFee increased, but not by /// block's baseFee increased, but not by more than the max amount
* more than the max amount per block. /// per block.
*/
function invariant_high_usage_raise_baseFee() external { function invariant_high_usage_raise_baseFee() external {
assertFalse(actor.failedRaiseBaseFee()); assertFalse(actor.failedRaiseBaseFee());
} }
/** /// @custom:invariant The base fee should decrease if the last block used less
* @custom:invariant The base fee should decrease if the last block used less /// than the target amount of gas.
* than the target amount of gas ///
* /// If the previous block used less than the target amount of gas,
* If the previous block used less than the target amount of gas, the base fee should decrease, /// the base fee should decrease, but not more than the max amount.
* but not more than the max amount.
*/
function invariant_low_usage_lower_baseFee() external { function invariant_low_usage_lower_baseFee() external {
assertFalse(actor.failedLowerBaseFee()); assertFalse(actor.failedLowerBaseFee());
} }
/** /// @custom:invariant A block's base fee should never be below `MINIMUM_BASE_FEE`.
* @custom:invariant A block's base fee should never be below `MINIMUM_BASE_FEE` ///
* /// This test asserts that a block's base fee can never drop
* This test asserts that a block's base fee can never drop below the /// below the `MINIMUM_BASE_FEE` threshold.
* `MINIMUM_BASE_FEE` threshold.
*/
function invariant_never_below_min_baseFee() external { function invariant_never_below_min_baseFee() external {
assertFalse(actor.failedNeverBelowMinBaseFee()); assertFalse(actor.failedNeverBelowMinBaseFee());
} }
/** /// @custom:invariant A block can never consume more than `MAX_RESOURCE_LIMIT` gas.
* @custom:invariant A block can never consume more than `MAX_RESOURCE_LIMIT` gas. ///
* /// This test asserts that a block can never consume more than
* This test asserts that a block can never consume more than the `MAX_RESOURCE_LIMIT` /// the `MAX_RESOURCE_LIMIT` gas threshold.
* gas threshold.
*/
function invariant_never_above_max_gas_limit() external { function invariant_never_above_max_gas_limit() external {
assertFalse(actor.failedMaxGasPerBlock()); assertFalse(actor.failedMaxGasPerBlock());
} }
/** /// @custom:invariant The base fee can never be raised more than the max base fee change.
* @custom:invariant The base fee can never be raised more than the max base fee change. ///
* /// After a block consumes more gas than the target gas, the base fee
* After a block consumes more gas than the target gas, the base fee cannot be raised /// cannot be raised more than the maximum amount allowed. The max base
* more than the maximum amount allowed. The max base fee change (per-block) is derived /// fee change (per-block) is derived as follows:
* as follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR` /// `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR`
*/
function invariant_never_exceed_max_increase() external { function invariant_never_exceed_max_increase() external {
assertFalse(actor.failedMaxRaiseBaseFeePerBlock()); assertFalse(actor.failedMaxRaiseBaseFeePerBlock());
} }
/** /// @custom:invariant The base fee can never be lowered more than the max base fee change.
* @custom:invariant The base fee can never be lowered more than the max base fee change. ///
* /// After a block consumes less than the target gas, the base fee cannot
* After a block consumes less than the target gas, the base fee cannot be lowered more /// be lowered more than the maximum amount allowed. The max base fee
* than the maximum amount allowed. The max base fee change (per-block) is derived as /// change (per-block) is derived as follows:
*follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR` /// `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR`
*/
function invariant_never_exceed_max_decrease() external { function invariant_never_exceed_max_decrease() external {
assertFalse(actor.failedMaxLowerBaseFeePerBlock()); assertFalse(actor.failedMaxLowerBaseFeePerBlock());
} }
/** /// @custom:invariant The `maxBaseFeeChange` calculation over multiple blocks can never
* @custom:invariant The `maxBaseFeeChange` calculation over multiple blocks can never /// underflow.
* underflow. ///
* /// When calculating the `maxBaseFeeChange` after multiple empty blocks,
* When calculating the `maxBaseFeeChange` after multiple empty blocks, the calculation /// the calculation should never be allowed to underflow.
* should never be allowed to underflow.
*/
function invariant_never_underflow() external { function invariant_never_underflow() external {
assertFalse(actor.underflow()); assertFalse(actor.underflow());
} }
......
...@@ -23,13 +23,11 @@ contract SafeCall_Succeeds_Invariants is Test { ...@@ -23,13 +23,11 @@ contract SafeCall_Succeeds_Invariants is Test {
vm.deal(address(actor), type(uint128).max); vm.deal(address(actor), type(uint128).max);
} }
/** /// @custom:invariant If `callWithMinGas` performs a call, then it must always
* @custom:invariant If `callWithMinGas` performs a call, then it must always /// provide at least the specified minimum gas limit to the subcontext.
* provide at least the specified minimum gas limit to the subcontext. ///
* /// If the check for remaining gas in `SafeCall.callWithMinGas` passes, the
* If the check for remaining gas in `SafeCall.callWithMinGas` passes, the /// subcontext of the call below it must be provided at least `minGas` gas.
* subcontext of the call below it must be provided at least `minGas` gas.
*/
function invariant_callWithMinGas_alwaysForwardsMinGas_succeeds() public { function invariant_callWithMinGas_alwaysForwardsMinGas_succeeds() public {
assertEq(actor.numCalls(), 0, "no failed calls allowed"); assertEq(actor.numCalls(), 0, "no failed calls allowed");
} }
...@@ -56,14 +54,12 @@ contract SafeCall_Fails_Invariants is Test { ...@@ -56,14 +54,12 @@ contract SafeCall_Fails_Invariants is Test {
vm.deal(address(actor), type(uint128).max); vm.deal(address(actor), type(uint128).max);
} }
/** /// @custom:invariant `callWithMinGas` reverts if there is not enough gas to pass
* @custom:invariant `callWithMinGas` reverts if there is not enough gas to pass /// to the subcontext.
* to the subcontext. ///
* /// If there is not enough gas in the callframe to ensure that
* If there is not enough gas in the callframe to ensure that `callWithMinGas` /// `callWithMinGas` can provide the specified minimum gas limit
* can provide the specified minimum gas limit to the subcontext of the call, /// to the subcontext of the call, then `callWithMinGas` must revert.
* then `callWithMinGas` must revert.
*/
function invariant_callWithMinGas_neverForwardsMinGas_reverts() public { function invariant_callWithMinGas_neverForwardsMinGas_reverts() public {
assertEq(actor.numCalls(), 0, "no successful calls allowed"); assertEq(actor.numCalls(), 0, "no successful calls allowed");
} }
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol"; import { Test } from "forge-std/Test.sol";
...@@ -37,10 +38,8 @@ contract SystemConfig_GasLimitLowerBound_Invariant is Test { ...@@ -37,10 +38,8 @@ contract SystemConfig_GasLimitLowerBound_Invariant is Test {
targetSelector(selector); targetSelector(selector);
} }
/** /// @custom:invariant The gas limit of the `SystemConfig` contract can never be lower
* @custom:invariant The gas limit of the `SystemConfig` contract can never be lower /// than the hard-coded lower bound.
* than the hard-coded lower bound.
*/
function invariant_gasLimitLowerBound() external { function invariant_gasLimitLowerBound() external {
assertTrue(config.gasLimit() >= config.minimumGasLimit()); assertTrue(config.gasLimit() >= config.minimumGasLimit());
} }
......
# `AddressAliasHelper` Invariants # `AddressAliasHelper` Invariants
## Address aliases are always able to be undone. ## 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
# `Burn.Eth` Invariants # `Burn.Eth` Invariants
## `eth(uint256)` always burns the exact amount of eth passed. ## `eth(uint256)` always burns the exact amount of eth passed.
**Test:** [`Burn.Eth.t.sol#L68`](../contracts/test/invariants/Burn.Eth.t.sol#L68) **Test:** [`Burn.Eth.t.sol#L64`](../contracts/test/invariants/Burn.Eth.t.sol#L64)
Asserts that when `Burn.eth(uint256)` is called, it always burns the exact amount of ETH passed to the function. Asserts that when `Burn.eth(uint256)` is called, it always burns the exact amount of ETH passed to the function.
\ No newline at end of file
# `Burn.Gas` Invariants # `Burn.Gas` Invariants
## `gas(uint256)` always burns at least the amount of gas passed. ## `gas(uint256)` always burns at least the amount of gas passed.
**Test:** [`Burn.Gas.t.sol#L68`](../contracts/test/invariants/Burn.Gas.t.sol#L68) **Test:** [`Burn.Gas.t.sol#L64`](../contracts/test/invariants/Burn.Gas.t.sol#L64)
Asserts that when `Burn.gas(uint256)` is called, it always burns at least the amount of gas passed to the function. Asserts that when `Burn.gas(uint256)` is called, it always burns at least the amount of gas passed to the function.
\ No newline at end of file
# `CrossDomainMessenger` Invariants # `CrossDomainMessenger` Invariants
## A call to `relayMessage` should succeed if at least the minimum gas limit can be supplied to the target context, there is enough gas to complete execution of `relayMessage` after the target context's execution is finished, and the target context did not revert. ## A call to `relayMessage` should succeed if at least the minimum gas limit can be supplied to the target context, there is enough gas to complete execution of `relayMessage` after the target context's execution is finished, and the target context did not revert.
**Test:** [`CrossDomainMessenger.t.sol#L161`](../contracts/test/invariants/CrossDomainMessenger.t.sol#L161) **Test:** [`CrossDomainMessenger.t.sol#L159`](../contracts/test/invariants/CrossDomainMessenger.t.sol#L159)
There are two minimum gas limits here: There are two minimum gas limits here:
- The outer min gas limit is for the call from the `OptimismPortal` to the `L1CrossDomainMessenger`, and it can be retrieved by calling the xdm's `baseGas` function with the `message` and inner limit. - The outer min gas limit is for the call from the `OptimismPortal` to the `L1CrossDomainMessenger`, and it can be retrieved by calling the xdm's `baseGas` function with the `message` and inner limit.
- The inner min gas limit is for the call from the `L1CrossDomainMessenger` to the target contract. - The inner min gas limit is for the call from the `L1CrossDomainMessenger` to the target contract.
## A call to `relayMessage` should assign the message hash to the `failedMessages` mapping if not enough gas is supplied to forward `minGasLimit` to the target context or if there is not enough gas to complete execution of `relayMessage` after the target context's execution is finished. ## A call to `relayMessage` should assign the message hash to the `failedMessages` mapping if not enough gas is supplied to forward `minGasLimit` to the target context or if there is not enough gas to complete execution of `relayMessage` after the target context's execution is finished.
**Test:** [`CrossDomainMessenger.t.sol#L196`](../contracts/test/invariants/CrossDomainMessenger.t.sol#L196) **Test:** [`CrossDomainMessenger.t.sol#L192`](../contracts/test/invariants/CrossDomainMessenger.t.sol#L192)
There are two minimum gas limits here: There are two minimum gas limits here:
- The outer min gas limit is for the call from the `OptimismPortal` to the `L1CrossDomainMessenger`, and it can be retrieved by calling the xdm's `baseGas` function with the `message` and inner limit. - The outer min gas limit is for the call from the `OptimismPortal` to the `L1CrossDomainMessenger`, and it can be retrieved by calling the xdm's `baseGas` function with the `message` and inner limit.
- The inner min gas limit is for the call from the `L1CrossDomainMessenger` to the target contract. - The inner min gas limit is for the call from the `L1CrossDomainMessenger` to the target contract.
\ No newline at end of file
# `Encoding` Invariants # `Encoding` Invariants
## `convertRoundTripAToB` never fails. ## `convertRoundTripAToB` never fails.
**Test:** [`Encoding.t.sol#L76`](../contracts/test/invariants/Encoding.t.sol#L76) **Test:** [`Encoding.t.sol#L71`](../contracts/test/invariants/Encoding.t.sol#L71)
Asserts that a raw versioned nonce can be encoded / decoded to reach the same raw value. Asserts that a raw versioned nonce can be encoded / decoded to reach the same raw value.
## `convertRoundTripBToA` never fails. ## `convertRoundTripBToA` never fails.
**Test:** [`Encoding.t.sol#L87`](../contracts/test/invariants/Encoding.t.sol#L87) **Test:** [`Encoding.t.sol#L80`](../contracts/test/invariants/Encoding.t.sol#L80)
Asserts that an encoded versioned nonce can always be decoded / re-encoded to reach the same encoded value. Asserts that an encoded versioned nonce can always be decoded / re-encoded to reach the same encoded value.
\ No newline at end of file
# `Hashing` Invariants # `Hashing` Invariants
## `hashCrossDomainMessage` reverts if `version` is > `1`. ## `hashCrossDomainMessage` reverts if `version` is > `1`.
**Test:** [`Hashing.t.sol#L141`](../contracts/test/invariants/Hashing.t.sol#L141) **Test:** [`Hashing.t.sol#L137`](../contracts/test/invariants/Hashing.t.sol#L137)
The `hashCrossDomainMessage` function should always revert if the `version` passed is > `1`. The `hashCrossDomainMessage` function should always revert if the `version` passed is > `1`.
## `version` = `0`: `hashCrossDomainMessage` and `hashCrossDomainMessageV0` are equivalent. ## `version` = `0`: `hashCrossDomainMessage` and `hashCrossDomainMessageV0` are equivalent.
**Test:** [`Hashing.t.sol#L153`](../contracts/test/invariants/Hashing.t.sol#L153) **Test:** [`Hashing.t.sol#L147`](../contracts/test/invariants/Hashing.t.sol#L147)
If the version passed is 0, `hashCrossDomainMessage` and `hashCrossDomainMessageV0` should be equivalent. If the version passed is 0, `hashCrossDomainMessage` and `hashCrossDomainMessageV0` should be equivalent.
## `version` = `1`: `hashCrossDomainMessage` and `hashCrossDomainMessageV1` are equivalent. ## `version` = `1`: `hashCrossDomainMessage` and `hashCrossDomainMessageV1` are equivalent.
**Test:** [`Hashing.t.sol#L166`](../contracts/test/invariants/Hashing.t.sol#L166) **Test:** [`Hashing.t.sol#L158`](../contracts/test/invariants/Hashing.t.sol#L158)
If the version passed is 1, `hashCrossDomainMessage` and `hashCrossDomainMessageV1` should be equivalent. If the version passed is 1, `hashCrossDomainMessage` and `hashCrossDomainMessageV1` should be equivalent.
\ No newline at end of file
# `L2OutputOracle` Invariants # `L2OutputOracle` Invariants
## The block number of the output root proposals should monotonically increase. ## The block number of the output root proposals should monotonically increase.
**Test:** [`L2OutputOracle.t.sol#L59`](../contracts/test/invariants/L2OutputOracle.t.sol#L59) **Test:** [`L2OutputOracle.t.sol#L56`](../contracts/test/invariants/L2OutputOracle.t.sol#L56)
When a new output is submitted, it should never be allowed to correspond to a block number that is less than the current output. When a new output is submitted, it should never be allowed to correspond to a block number that is less than the current output.
\ No newline at end of file
# `OptimismPortal` Invariants # `OptimismPortal` Invariants
## Deposits of any value should always succeed unless `_to` = `address(0)` or `_isCreation` = `true`. ## Deposits of any value should always succeed unless `_to` = `address(0)` or `_isCreation` = `true`.
**Test:** [`OptimismPortal.t.sol#L158`](../contracts/test/invariants/OptimismPortal.t.sol#L158) **Test:** [`OptimismPortal.t.sol#L157`](../contracts/test/invariants/OptimismPortal.t.sol#L157)
All deposits, barring creation transactions and transactions sent to `address(0)`, should always succeed. All deposits, barring creation transactions and transactions sent to `address(0)`, should always succeed.
## `finalizeWithdrawalTransaction` should revert if the finalization period has not elapsed. ## `finalizeWithdrawalTransaction` should revert if the finalization period has not elapsed.
**Test:** [`OptimismPortal.t.sol#L188`](../contracts/test/invariants/OptimismPortal.t.sol#L188) **Test:** [`OptimismPortal.t.sol#L185`](../contracts/test/invariants/OptimismPortal.t.sol#L185)
A withdrawal that has been proven should not be able to be finalized until after the finalization period has elapsed. A withdrawal that has been proven should not be able to be finalized until after the finalization period has elapsed.
## `finalizeWithdrawalTransaction` should revert if the withdrawal has already been finalized. ## `finalizeWithdrawalTransaction` should revert if the withdrawal has already been finalized.
**Test:** [`OptimismPortal.t.sol#L225`](../contracts/test/invariants/OptimismPortal.t.sol#L225) **Test:** [`OptimismPortal.t.sol#L220`](../contracts/test/invariants/OptimismPortal.t.sol#L220)
Ensures that there is no chain of calls that can be made that allows a withdrawal to be finalized twice. Ensures that there is no chain of calls that can be made that allows a withdrawal to be finalized twice.
## A withdrawal should **always** be able to be finalized `FINALIZATION_PERIOD_SECONDS` after it was successfully proven. ## A withdrawal should **always** be able to be finalized `FINALIZATION_PERIOD_SECONDS` after it was successfully proven.
**Test:** [`OptimismPortal.t.sol#L260`](../contracts/test/invariants/OptimismPortal.t.sol#L260) **Test:** [`OptimismPortal.t.sol#L254`](../contracts/test/invariants/OptimismPortal.t.sol#L254)
This invariant asserts that there is no chain of calls that can be made that will prevent a withdrawal from being finalized exactly `FINALIZATION_PERIOD_SECONDS` after it was successfully proven. This invariant asserts that there is no chain of calls that can be made that will prevent a withdrawal from being finalized exactly `FINALIZATION_PERIOD_SECONDS` after it was successfully proven.
\ No newline at end of file
# `ResourceMetering` Invariants # `ResourceMetering` Invariants
## The base fee should increase if the last block used more than the target amount of gas ## The base fee should increase if the last block used more than the target amount of gas.
**Test:** [`ResourceMetering.t.sol#L180`](../contracts/test/invariants/ResourceMetering.t.sol#L180) **Test:** [`ResourceMetering.t.sol#L177`](../contracts/test/invariants/ResourceMetering.t.sol#L177)
If the last block used more than the target amount of gas (and there were no empty blocks in between), ensure this block's baseFee increased, but not by more than the max amount per block. If the last block used more than the target amount of gas (and there were no empty blocks in between), ensure this block's baseFee increased, but not by more than the max amount per block.
## The base fee should decrease if the last block used less than the target amount of gas.
## The base fee should decrease if the last block used less than the target amount of gas **Test:** [`ResourceMetering.t.sol#L186`](../contracts/test/invariants/ResourceMetering.t.sol#L186)
**Test:** [`ResourceMetering.t.sol#L191`](../contracts/test/invariants/ResourceMetering.t.sol#L191)
If the previous block used less than the target amount of gas, the base fee should decrease, but not more than the max amount. If the previous block used less than the target amount of gas, the base fee should decrease, but not more than the max amount.
## A block's base fee should never be below `MINIMUM_BASE_FEE`.
## A block's base fee should never be below `MINIMUM_BASE_FEE` **Test:** [`ResourceMetering.t.sol#L194`](../contracts/test/invariants/ResourceMetering.t.sol#L194)
**Test:** [`ResourceMetering.t.sol#L201`](../contracts/test/invariants/ResourceMetering.t.sol#L201)
This test asserts that a block's base fee can never drop below the `MINIMUM_BASE_FEE` threshold. This test asserts that a block's base fee can never drop below the `MINIMUM_BASE_FEE` threshold.
## A block can never consume more than `MAX_RESOURCE_LIMIT` gas. ## A block can never consume more than `MAX_RESOURCE_LIMIT` gas.
**Test:** [`ResourceMetering.t.sol#L211`](../contracts/test/invariants/ResourceMetering.t.sol#L211) **Test:** [`ResourceMetering.t.sol#L202`](../contracts/test/invariants/ResourceMetering.t.sol#L202)
This test asserts that a block can never consume more than the `MAX_RESOURCE_LIMIT` gas threshold. This test asserts that a block can never consume more than the `MAX_RESOURCE_LIMIT` gas threshold.
## The base fee can never be raised more than the max base fee change. ## The base fee can never be raised more than the max base fee change.
**Test:** [`ResourceMetering.t.sol#L222`](../contracts/test/invariants/ResourceMetering.t.sol#L222) **Test:** [`ResourceMetering.t.sol#L212`](../contracts/test/invariants/ResourceMetering.t.sol#L212)
After a block consumes more gas than the target gas, the base fee cannot be raised more than the maximum amount allowed. The max base fee change (per-block) is derived as follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR` After a block consumes more gas than the target gas, the base fee cannot be raised more than the maximum amount allowed. The max base fee change (per-block) is derived as follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR`
## The base fee can never be lowered more than the max base fee change. ## The base fee can never be lowered more than the max base fee change.
**Test:** [`ResourceMetering.t.sol#L233`](../contracts/test/invariants/ResourceMetering.t.sol#L233) **Test:** [`ResourceMetering.t.sol#L222`](../contracts/test/invariants/ResourceMetering.t.sol#L222)
After a block consumes less than the target gas, the base fee cannot be lowered more than the maximum amount allowed. The max base fee change (per-block) is derived as follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR` After a block consumes less than the target gas, the base fee cannot be lowered more than the maximum amount allowed. The max base fee change (per-block) is derived as follows: `prevBaseFee / BASE_FEE_MAX_CHANGE_DENOMINATOR`
## The `maxBaseFeeChange` calculation over multiple blocks can never underflow. ## The `maxBaseFeeChange` calculation over multiple blocks can never underflow.
**Test:** [`ResourceMetering.t.sol#L244`](../contracts/test/invariants/ResourceMetering.t.sol#L244) **Test:** [`ResourceMetering.t.sol#L231`](../contracts/test/invariants/ResourceMetering.t.sol#L231)
When calculating the `maxBaseFeeChange` after multiple empty blocks, the calculation should never be allowed to underflow. When calculating the `maxBaseFeeChange` after multiple empty blocks, the calculation should never be allowed to underflow.
\ No newline at end of file
# `SafeCall` Invariants # `SafeCall` Invariants
## If `callWithMinGas` performs a call, then it must always provide at least the specified minimum gas limit to the subcontext. ## If `callWithMinGas` performs a call, then it must always provide at least the specified minimum gas limit to the subcontext.
**Test:** [`SafeCall.t.sol#L33`](../contracts/test/invariants/SafeCall.t.sol#L33) **Test:** [`SafeCall.t.sol#L31`](../contracts/test/invariants/SafeCall.t.sol#L31)
If the check for remaining gas in `SafeCall.callWithMinGas` passes, the subcontext of the call below it must be provided at least `minGas` gas. If the check for remaining gas in `SafeCall.callWithMinGas` passes, the subcontext of the call below it must be provided at least `minGas` gas.
## `callWithMinGas` reverts if there is not enough gas to pass to the subcontext. ## `callWithMinGas` reverts if there is not enough gas to pass to the subcontext.
**Test:** [`SafeCall.t.sol#L67`](../contracts/test/invariants/SafeCall.t.sol#L67) **Test:** [`SafeCall.t.sol#L63`](../contracts/test/invariants/SafeCall.t.sol#L63)
If there is not enough gas in the callframe to ensure that `callWithMinGas` can provide the specified minimum gas limit to the subcontext of the call, then `callWithMinGas` must revert. If there is not enough gas in the callframe to ensure that `callWithMinGas` can provide the specified minimum gas limit to the subcontext of the call, then `callWithMinGas` must revert.
\ No newline at end of file
...@@ -11,8 +11,6 @@ const BASE_INVARIANTS_DIR = path.join( ...@@ -11,8 +11,6 @@ const BASE_INVARIANTS_DIR = path.join(
const BASE_DOCS_DIR = path.join(__dirname, '..', 'invariant-docs') const BASE_DOCS_DIR = path.join(__dirname, '..', 'invariant-docs')
const BASE_INVARIANT_GH_URL = '../contracts/test/invariants/' const BASE_INVARIANT_GH_URL = '../contracts/test/invariants/'
const NATSPEC_INV = '@custom:invariant' const NATSPEC_INV = '@custom:invariant'
const BLOCK_COMMENT_PREFIX_REGEX = /\*(\/)?/
const BLOCK_COMMENT_HEADER_REGEX = /\*\s(.)+/
// Represents an invariant test contract // Represents an invariant test contract
type Contract = { type Contract = {
...@@ -30,10 +28,8 @@ type InvariantDoc = { ...@@ -30,10 +28,8 @@ type InvariantDoc = {
const writtenFiles = [] const writtenFiles = []
/** // Lazy-parses all test files in the `contracts/test/invariants` directory
* Lazy-parses all test files in the `contracts/test/invariants` directory to generate documentation // to generate documentation on all invariant tests.
* on all invariant tests.
*/
const docGen = (dir: string): void => { const docGen = (dir: string): void => {
// Grab all files within the invariants test dir // Grab all files within the invariants test dir
const files = fs.readdirSync(dir) const files = fs.readdirSync(dir)
...@@ -58,45 +54,36 @@ const docGen = (dir: string): void => { ...@@ -58,45 +54,36 @@ const docGen = (dir: string): void => {
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
let line = lines[i] let line = lines[i]
if (line.startsWith('/**')) { // We have an invariant doc
// We are at the beginning of a new doc comment. Reset the `currentDoc`. if (line.startsWith(`/// ${NATSPEC_INV}`)) {
currentDoc = {} // Assign the header of the invariant doc.
// TODO: Handle ambiguous case for `INVARIANT: ` prefix.
currentDoc = {
header: line.replace(`/// ${NATSPEC_INV}`, '').trim(),
desc: '',
}
// Move on to the next line // If the header is multi-line, continue appending to the `currentDoc`'s header.
line = lines[++i] line = lines[++i]
while (line.startsWith(`///`) && line.trim() !== '///') {
currentDoc.header += ` ${line.replace(`///`, '').trim()}`
line = lines[++i]
}
// Process the description
while ((line = lines[++i]).startsWith('///')) {
line = line.replace('///', '').trim()
// We have an invariant doc // If the line has any contents, insert it into the desc.
if (line.startsWith(`* ${NATSPEC_INV}`)) { // Otherwise, consider it a linebreak.
// Assign the header of the invariant doc. currentDoc.desc += line.length > 0 ? `${line} ` : '\n'
// TODO: Handle ambiguous case for `INVARIANT: ` prefix.
// TODO: Handle multi-line headers.
currentDoc = {
header: line.replace(`* ${NATSPEC_INV}`, '').trim(),
desc: '',
}
// If the header is multi-line, continue appending to the `currentDoc`'s header.
while (BLOCK_COMMENT_HEADER_REGEX.test((line = lines[++i]))) {
currentDoc.header += ` ${line
.replace(BLOCK_COMMENT_PREFIX_REGEX, '')
.trim()}`
}
// Process the description
while ((line = lines[++i]).startsWith('*')) {
line = line.replace(BLOCK_COMMENT_PREFIX_REGEX, '').trim()
// If the line has any contents, insert it into the desc.
// Otherwise, consider it a linebreak.
currentDoc.desc += line.length > 0 ? `${line} ` : '\n'
}
// Set the line number of the test
currentDoc.lineNo = i + 1
// Add the doc to the contract
contract.docs.push(currentDoc)
} }
// Set the line number of the test
currentDoc.lineNo = i + 1
// Add the doc to the contract
contract.docs.push(currentDoc)
} }
} }
...@@ -133,9 +120,7 @@ const docGen = (dir: string): void => { ...@@ -133,9 +120,7 @@ const docGen = (dir: string): void => {
) )
} }
/** // Generate a table of contents for all invariant docs and place it in the README.
* Generate a table of contents for all invariant docs and place it in the README.
*/
const tocGen = (): void => { const tocGen = (): void => {
const autoTOCPrefix = '<!-- START autoTOC -->\n' const autoTOCPrefix = '<!-- START autoTOC -->\n'
const autoTOCPostfix = '<!-- END autoTOC -->\n' const autoTOCPostfix = '<!-- END autoTOC -->\n'
...@@ -165,9 +150,7 @@ const tocGen = (): void => { ...@@ -165,9 +150,7 @@ const tocGen = (): void => {
) )
} }
/** // Render a `Contract` object into valid markdown.
* Render a `Contract` object into valid markdown.
*/
const renderContractDoc = (contract: Contract, header: boolean): string => { const renderContractDoc = (contract: Contract, header: boolean): string => {
const _header = header ? `# \`${contract.name}\` Invariants\n` : '' const _header = header ? `# \`${contract.name}\` Invariants\n` : ''
const docs = contract.docs const docs = contract.docs
......
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