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

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

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