diff --git a/.editorconfig b/.editorconfig index 88a8315408..0f8740f805 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,6 +15,7 @@ indent_size = 4 [*.go] indent_size = 4 indent_style = tab +import_style = go [*.yml] quote_type = single diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 8f7fb24b88..cce4831793 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -25,7 +25,7 @@ jobs: outputs: # Expose matched filters as job 'packages' output variable packages_deps: ${{ steps.filter_go_deps.outputs.changed_modules }} - packages_nodeps: ${{ steps.filter_go_nodeps.outputs.changed_modules }} + packages_nodeps: ${{ steps.filter_go_nodeps.outputs.changed_modules }} package_count_deps: ${{ steps.length.outputs.FILTER_LENGTH_DEPS }} package_count_nodeps: ${{ steps.length.outputs.FILTER_LENGTH_NODEPS }} solidity_changes: ${{ steps.filter_solidity.outputs.any_changed }} @@ -160,12 +160,34 @@ jobs: PYROSCOPE_PATH: '${{ steps.pyroscope-path.outputs.PYROSCOPE_PATH }}' APPLICATION_NAME: ${{ steps.coverage.outputs.flag }} + - name: Precompile Tests + working-directory: ${{ matrix.package }} + run: go test -run=nope ./... + env: + # no other containers are running at this point so we can use all available memory for compilation + GOMEMLIMIT: 5GiB + GOGC: -1 + - name: Test uses: nick-fields/retry@v2 with: - command: cd ${{matrix.package}} && $PYROSCOPE_PATH exec --apiKey=${{ secrets.PYROSCOPE_CLOUD_TOKEN }} -- go test -parallel 1 -coverpkg=./... ./... -coverprofile=profile.cov - max_attempts: 2 - timeout_minutes: 20 + command: | + set -e + cd ${{ matrix.package }} + echo "mode: atomic" > coverage.txt + for pkg in $(go list ./...); do + #$PYROSCOPE_PATH exec --apiKey=${{ secrets.PYROSCOPE_CLOUD_TOKEN }} -- go test -coverpkg="$pkg" -coverprofile=profile.cov $pkg + go test -coverpkg="$pkg" -coverprofile=profile.cov $pkg + if [ -f profile.cov ]; then + tail -n +2 profile.cov >> coverage.txt + rm profile.cov + fi + done + cp coverage.txt profile.cov + docker ps + on_retry_command: docker rm -f $(docker ps -a -q) + max_attempts: 5 + timeout_minutes: 60 env: PYROSCOPE_PATH: '${{ steps.pyroscope-path.outputs.PYROSCOPE_PATH }}' ENABLE_MYSQL_TEST: true @@ -180,6 +202,29 @@ jobs: ETHEREUM_RPC_URI: ${{ secrets.ETHEREUM_RPC_URI }} GOPROXY: https://proxy.golang.org + # TODO: these 3 steps should be moved into a reusable action. + # also,should be noted these aren't comprehensive, as many of the containers are automatically removed + # and we'd need something like this for all logs: https://github.com/synapsecns/sanguine/pull/1280#discussion_r1320889916 + # but this should be better than nothing as long as you don't assume the logs are complete + # + # there's a good case to have this in addition to the suggestions referenced in the comment above because + # logs are not collected there in the case of sigterms (because logs stop piping when app breaks), which are caught here + # + # in any case this should be considered an early implementation + - name: Collect docker logs + if: always() + uses: jwalton/gh-docker-logs@v2 + with: + dest: '/tmp/logs' + - name: Tar logs + if: failure() + run: tar cvzf ./logs.tgz /tmp/logs + - name: Upload logs to GitHub + if: failure() + uses: actions/upload-artifact@master + with: + name: ${{matrix.package}}-logs.tgz + path: ./logs.tgz - name: Send Coverage (Codecov) uses: Wandalen/wretry.action@v1.0.36 @@ -278,7 +323,6 @@ jobs: GOMEMLIMIT: 6GiB GOGC: -1 - issue_number: # this is needed to prevent us from hitting the github api rate limit name: Get The Issue Number @@ -293,7 +337,6 @@ jobs: - uses: jwalton/gh-find-current-pr@v1 id: find_pr - # check if we need to rerun go generate as a result of solidity changes. Note, this will only run on solidity changes. # TODO: consolidate w/ go change check. This will run twice on agents check-generation-solidity: @@ -431,7 +474,6 @@ jobs: run: yarn install --immutable if: ${{ contains(matrix.package, 'agents') }} - # Generate flattened files - name: Run flattener run: npx lerna exec npm run build:go @@ -458,7 +500,6 @@ jobs: restore-keys: | ${{ runner.os }}-go-generate-${{matrix.package}} - - name: Cache Linuxbrew uses: actions/cache@v3 if: ${{ contains(matrix.package, 'scribe') }} @@ -485,18 +526,17 @@ jobs: - name: Run Make Generate CI Deps (Scribe) working-directory: ${{matrix.package}}/ - if: ${{ contains(matrix.package, 'scribe') }} + if: ${{ contains(matrix.package, 'scribe') }} run: | export PATH=$PATH:$(go env GOPATH)/bin make generate-ci || exit 0 - name: Run Make Generate CI (Scribe) working-directory: ${{matrix.package}}/ - if: ${{ contains(matrix.package, 'scribe') }} + if: ${{ contains(matrix.package, 'scribe') }} run: | make generate-ci - # See if we need to rerun go generate # TODO: consider implementing https://github.com/golang/go/issues/20520 to speed up process if possible - name: Try Go Generate diff --git a/agents/agents/agentsintegration/agentsintegration_test.go b/agents/agents/agentsintegration/agentsintegration_test.go index f931e20650..b483a900a8 100644 --- a/agents/agents/agentsintegration/agentsintegration_test.go +++ b/agents/agents/agentsintegration/agentsintegration_test.go @@ -6,7 +6,6 @@ import ( "testing" "time" - "github.com/Flaque/filet" awsTime "github.com/aws/smithy-go/time" "github.com/brianvoe/gofakeit/v6" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -25,6 +24,8 @@ import ( "github.com/synapsecns/sanguine/services/scribe/client" scribeConfig "github.com/synapsecns/sanguine/services/scribe/config" "github.com/synapsecns/sanguine/services/scribe/service" + + "github.com/Flaque/filet" ) func RemoveAgentsTempFile(t *testing.T, fileName string) { @@ -211,7 +212,7 @@ func (u *AgentsIntegrationSuite) TestAgentsE2E() { Equal(u.T(), encodedNotaryTestConfig, decodedAgentConfigBackToEncodedBytes) - guard, err := guard.NewGuard(u.GetTestContext(), guardTestConfig, omniRPCClient, u.GuardTestDB, u.GuardMetrics) + guard, err := guard.NewGuard(u.GetTestContext(), guardTestConfig, omniRPCClient, scribeClient.ScribeClient, u.GuardTestDB, u.GuardMetrics) Nil(u.T(), err) tips := types.NewTips(big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)) @@ -251,7 +252,9 @@ func (u *AgentsIntegrationSuite) TestAgentsE2E() { go func() { // we don't check errors here since this will error on cancellation at the end of the test err = guard.Start(u.GetTestContext()) - u.Nil(err) + if !testDone { + u.Nil(err) + } }() u.Eventually(func() bool { diff --git a/agents/agents/exampleagent/exampleagent_test.go b/agents/agents/exampleagent/exampleagent_test.go index cc7a307436..ac9976a035 100644 --- a/agents/agents/exampleagent/exampleagent_test.go +++ b/agents/agents/exampleagent/exampleagent_test.go @@ -12,7 +12,7 @@ func (u ExampleAgentSuite) TestExampleAgentSimulatedTestSuite() { Nil(u.T(), err) Equal(u.T(), uint32(u.TestBackendDestination.GetChainID()), notaryStatus.Domain) - notaryStatusFromEVM, err := u.SummitDomainClient.BondingManager().GetAgentStatus(u.GetTestContext(), u.NotaryBondedSigner) + notaryStatusFromEVM, err := u.SummitDomainClient.BondingManager().GetAgentStatus(u.GetTestContext(), u.NotaryBondedSigner.Address()) Nil(u.T(), err) Equal(u.T(), notaryStatusFromEVM.Domain(), uint32(u.TestBackendDestination.GetChainID())) @@ -20,7 +20,7 @@ func (u ExampleAgentSuite) TestExampleAgentSimulatedTestSuite() { Nil(u.T(), err) Equal(u.T(), uint32(0), guardStatus.Domain) - guardStatusFromEVM, err := u.SummitDomainClient.BondingManager().GetAgentStatus(u.GetTestContext(), u.GuardBondedSigner) + guardStatusFromEVM, err := u.SummitDomainClient.BondingManager().GetAgentStatus(u.GetTestContext(), u.GuardBondedSigner.Address()) Nil(u.T(), err) Equal(u.T(), guardStatusFromEVM.Domain(), uint32(0)) } diff --git a/agents/agents/executor/cmd/commands.go b/agents/agents/executor/cmd/commands.go index 9efecab762..3d1e56723a 100644 --- a/agents/agents/executor/cmd/commands.go +++ b/agents/agents/executor/cmd/commands.go @@ -119,8 +119,8 @@ var ExecutorRunCommand = &cli.Command{ case "embedded": eventDB, err := scribeAPI.InitDB( ctx, - executorConfig.ScribeConfig.EmbeddedDBConfig.Type, - executorConfig.ScribeConfig.EmbeddedDBConfig.Source, + executorConfig.DBConfig.Type, + executorConfig.DBConfig.Source, handler, false, ) @@ -157,7 +157,7 @@ var ExecutorRunCommand = &cli.Command{ embedded := client.NewEmbeddedScribe( executorConfig.ScribeConfig.EmbeddedDBConfig.Type, - executorConfig.ScribeConfig.EmbeddedDBConfig.Source, + executorConfig.DBConfig.Source, handler, ) diff --git a/agents/agents/executor/db/suite_test.go b/agents/agents/executor/db/suite_test.go index cacc306f2e..a9237ca250 100644 --- a/agents/agents/executor/db/suite_test.go +++ b/agents/agents/executor/db/suite_test.go @@ -3,6 +3,12 @@ package db_test import ( "database/sql" "fmt" + "os" + "sync" + "sync/atomic" + "testing" + "time" + "github.com/Flaque/filet" . "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -10,16 +16,12 @@ import ( "github.com/synapsecns/sanguine/agents/agents/executor/db/sql/mysql" "github.com/synapsecns/sanguine/agents/agents/executor/db/sql/sqlite" "github.com/synapsecns/sanguine/agents/agents/executor/metadata" + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/dbcommon" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/core/testsuite" "gorm.io/gorm/schema" - "os" - "sync" - "sync/atomic" - "testing" - "time" ) type DBSuite struct { @@ -41,10 +43,18 @@ func NewEventDBSuite(tb testing.TB) *DBSuite { func (t *DBSuite) SetupSuite() { t.TestSuite.SetupSuite() - localmetrics.SetupTestJaeger(t.GetSuiteContext(), t.T()) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(t.GetSuiteContext(), t.T()) + metricsHandler = metrics.Jaeger + } var err error - t.metrics, err = metrics.NewByType(t.GetSuiteContext(), metadata.BuildInfo(), metrics.Jaeger) + t.metrics, err = metrics.NewByType(t.GetSuiteContext(), metadata.BuildInfo(), metricsHandler) Nil(t.T(), err) } diff --git a/agents/agents/executor/executor.go b/agents/agents/executor/executor.go index 8d276f723b..bbd8855d51 100644 --- a/agents/agents/executor/executor.go +++ b/agents/agents/executor/executor.go @@ -65,6 +65,8 @@ type chainExecutor struct { rpcClient Backend // boundDestination is a bound destination contract. boundDestination domains.DestinationContract + // boundOrigin is a bound origin contract. + boundOrigin domains.OriginContract } // Executor is the executor agent. @@ -73,8 +75,6 @@ type Executor struct { config executor.Config // executorDB is the executor agent database. executorDB db.ExecutorDB - // scribeClient is the client to the Scribe gRPC server. - scribeClient client.ScribeClient // grpcClient is the gRPC client. grpcClient pbscribe.ScribeServiceClient // grpcConn is the gRPC connection. @@ -87,6 +87,8 @@ type Executor struct { handler metrics.Handler // txSubmitter is the transaction submitter. txSubmitter submitter.TransactionSubmitter + // NowFunc returns the current time. + NowFunc func() time.Time } // logOrderInfo is a struct to keep track of the order of a log. @@ -198,6 +200,11 @@ func NewExecutor(ctx context.Context, config executor.Config, executorDB db.Exec return nil, fmt.Errorf("could not bind destination contract: %w", err) } + boundOrigin, err := evm.NewOriginContract(ctx, chainClient, common.HexToAddress(chain.OriginAddress)) + if err != nil { + return nil, fmt.Errorf("could not bind origin contract: %w", err) + } + tree, err := newTreeFromDB(ctx, chain.ChainID, executorDB) if err != nil { return nil, fmt.Errorf("could not get tree from db: %w", err) @@ -219,19 +226,20 @@ func NewExecutor(ctx context.Context, config executor.Config, executorDB db.Exec merkleTree: tree, rpcClient: evmClient, boundDestination: boundDestination, + boundOrigin: boundOrigin, } } return &Executor{ config: config, executorDB: executorDB, - scribeClient: scribeClient, grpcConn: conn, grpcClient: grpcClient, signer: executorSigner, chainExecutors: chainExecutors, handler: handler, txSubmitter: txSubmitter, + NowFunc: time.Now, }, nil } @@ -784,7 +792,7 @@ func (e Executor) executeExecutable(parentCtx context.Context, chainID uint32) ( backoffInterval = time.Duration(e.config.ExecuteInterval) * time.Second page := 1 - currentTime := uint64(time.Now().Unix()) + currentTime := uint64(e.NowFunc().Unix()) messageMask := db.DBMessage{ ChainID: &chainID, diff --git a/agents/agents/executor/executor_test.go b/agents/agents/executor/executor_test.go index 2a022d9c67..f060c7e8b9 100644 --- a/agents/agents/executor/executor_test.go +++ b/agents/agents/executor/executor_test.go @@ -612,7 +612,7 @@ func (e *ExecutorSuite) TestExecutor() { gasData := types.NewGasData(uint16(1), uint16(1), uint16(1), uint16(1), uint16(1), uint16(1)) originState := types.NewState(rootB32, chainID, nonce, big.NewInt(1), big.NewInt(1), gasData) randomGasData := types.NewGasData(gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16()) - randomState := types.NewState(common.BigToHash(big.NewInt(gofakeit.Int64())), chainID+1, gofakeit.Uint32(), big.NewInt(gofakeit.Int64()), big.NewInt(gofakeit.Int64()), randomGasData) + randomState := types.NewState(common.BigToHash(big.NewInt(int64(gofakeit.Int32()))), chainID+1, gofakeit.Uint32(), big.NewInt(int64(gofakeit.Int32())), big.NewInt(int64(gofakeit.Int32())), randomGasData) originSnapshot := types.NewSnapshot([]types.State{originState, randomState}) snapshotRoot, proofs, err := originSnapshot.SnapshotRootAndProofs() @@ -682,22 +682,22 @@ func (e *ExecutorSuite) TestSetMinimumTime() { common.BigToHash(big.NewInt(gofakeit.Int64())), chainID, 1, - big.NewInt(gofakeit.Int64()), - big.NewInt(gofakeit.Int64()), + big.NewInt(int64(gofakeit.Int32())), + big.NewInt(int64(gofakeit.Int32())), types.NewGasData(gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16())) state1 := types.NewState( common.BigToHash(big.NewInt(gofakeit.Int64())), chainID, 2, - big.NewInt(gofakeit.Int64()), - big.NewInt(gofakeit.Int64()), + big.NewInt(int64(gofakeit.Int32())), + big.NewInt(int64(gofakeit.Int32())), types.NewGasData(gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16())) state2 := types.NewState( common.BigToHash(big.NewInt(gofakeit.Int64())), chainID, 5, - big.NewInt(gofakeit.Int64()), - big.NewInt(gofakeit.Int64()), + big.NewInt(int64(gofakeit.Int32())), + big.NewInt(int64(gofakeit.Int32())), types.NewGasData(gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16())) snapshot0 := types.NewSnapshot([]types.State{state0}) @@ -992,7 +992,6 @@ func (e *ExecutorSuite) TestSendManagerMessage() { txContext := e.TestBackendSummit.GetTxContext(e.GetTestContext(), e.SummitMetadata.OwnerPtr()) tx, err = e.SummitDomainClient.Inbox().SubmitSnapshot( txContext.TransactOpts, - e.GuardUnbondedSigner, encodedSnapshot, guardSnapshotSignature, ) @@ -1005,7 +1004,6 @@ func (e *ExecutorSuite) TestSendManagerMessage() { e.Nil(err) tx, err = e.SummitDomainClient.Inbox().SubmitSnapshot( txContext.TransactOpts, - e.NotaryUnbondedSigner, encodedSnapshot, notarySnapshotSignature, ) diff --git a/agents/agents/executor/executor_utils.go b/agents/agents/executor/executor_utils.go index 37a46e85e2..585c5a02a9 100644 --- a/agents/agents/executor/executor_utils.go +++ b/agents/agents/executor/executor_utils.go @@ -6,6 +6,7 @@ import ( "math/big" "time" + "github.com/ethereum/go-ethereum/common" ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/jpillora/backoff" "github.com/synapsecns/sanguine/agents/contracts/inbox" @@ -41,10 +42,12 @@ func (e Executor) logToAttestation(log ethTypes.Log, chainID uint32, summitAttes return nil, fmt.Errorf("could not parse attestation") } } else { - attestation, ok = e.chainExecutors[chainID].lightInboxParser.ParseAttestationAccepted(log) - if !ok { - return nil, fmt.Errorf("could not parse attestation") + attestationMetadata, err := e.chainExecutors[chainID].lightInboxParser.ParseAttestationAccepted(log) + if err != nil { + return nil, fmt.Errorf("could not parse attestation: %w", err) } + + attestation = attestationMetadata.Attestation } if attestation == nil { @@ -57,17 +60,17 @@ func (e Executor) logToAttestation(log ethTypes.Log, chainID uint32, summitAttes // logToSnapshot converts the log to a snapshot. func (e Executor) logToSnapshot(log ethTypes.Log, chainID uint32) (types.Snapshot, error) { - snapshot, domain, ok := e.chainExecutors[chainID].inboxParser.ParseSnapshotAccepted(log) - if !ok { - return nil, fmt.Errorf("could not parse snapshot") + snapshotMetadata, err := e.chainExecutors[chainID].inboxParser.ParseSnapshotAccepted(log) + if err != nil { + return nil, fmt.Errorf("could not parse snapshot: %w", err) } - if snapshot == nil || domain == 0 { + if snapshotMetadata.Snapshot == nil || snapshotMetadata.AgentDomain == 0 { //nolint:nilnil return nil, nil } - return snapshot, nil + return snapshotMetadata.Snapshot, nil } func (e Executor) logToInterface(log ethTypes.Log, chainID uint32) (any, error) { @@ -151,6 +154,25 @@ func (e Executor) processMessage(ctx context.Context, message types.Message, log // processAttestation processes and stores an attestation. func (e Executor) processSnapshot(ctx context.Context, snapshot types.Snapshot, logBlockNumber uint64) error { + for _, state := range snapshot.States() { + statePayload, err := state.Encode() + if err != nil { + return fmt.Errorf("could not encode state: %w", err) + } + // Verify that the state is valid w.r.t. Origin. + valid, err := e.chainExecutors[state.Origin()].boundOrigin.IsValidState( + ctx, + statePayload, + ) + if err != nil { + return fmt.Errorf("could not check validity of state: %w", err) + } + if !valid { + stateRoot := state.Root() + logger.Infof("snapshot has invalid state. Origin: %d. SnapshotRoot: %s", state.Origin(), common.BytesToHash(stateRoot[:]).String()) + return nil + } + } snapshotRoot, proofs, err := snapshot.SnapshotRootAndProofs() if err != nil { return fmt.Errorf("could not get snapshot root and proofs: %w", err) diff --git a/agents/agents/executor/suite_test.go b/agents/agents/executor/suite_test.go index 197c97e0b5..46d5d313e6 100644 --- a/agents/agents/executor/suite_test.go +++ b/agents/agents/executor/suite_test.go @@ -6,7 +6,6 @@ import ( "github.com/stretchr/testify/suite" agentsTestutil "github.com/synapsecns/sanguine/agents/testutil" - "github.com/synapsecns/sanguine/ethergo/chain/chainwatcher" ) // ExecutorSuite tests the executor agent. @@ -24,8 +23,6 @@ func NewExecutorSuite(tb testing.TB) *ExecutorSuite { } func (e *ExecutorSuite) SetupTest() { - chainwatcher.PollInterval = time.Second - e.SimulatedBackendsTestSuite.SetupTest() e.SetTestTimeout(time.Minute * 5) } diff --git a/agents/agents/guard/cmd/commands.go b/agents/agents/guard/cmd/commands.go index b3342ff1b8..57cbc4c2d3 100644 --- a/agents/agents/guard/cmd/commands.go +++ b/agents/agents/guard/cmd/commands.go @@ -2,16 +2,6 @@ package cmd import ( "context" - "github.com/synapsecns/sanguine/agents/agents/guard/db" - "github.com/synapsecns/sanguine/agents/agents/guard/db/sql/mysql" - "github.com/synapsecns/sanguine/agents/agents/guard/db/sql/sqlite" - "github.com/synapsecns/sanguine/agents/agents/guard/metadata" - "github.com/synapsecns/sanguine/core/dbcommon" - "github.com/synapsecns/sanguine/core/metrics" - omnirpcClient "github.com/synapsecns/sanguine/services/omnirpc/client" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" - "gorm.io/gorm/schema" "os" "sync/atomic" "time" @@ -22,7 +12,22 @@ import ( "github.com/phayes/freeport" "github.com/synapsecns/sanguine/agents/agents/guard" "github.com/synapsecns/sanguine/agents/agents/guard/api" + "github.com/synapsecns/sanguine/agents/agents/guard/db" + "github.com/synapsecns/sanguine/agents/agents/guard/db/sql/mysql" + "github.com/synapsecns/sanguine/agents/agents/guard/db/sql/sqlite" + "github.com/synapsecns/sanguine/agents/agents/guard/metadata" + "github.com/synapsecns/sanguine/core/dbcommon" + "github.com/synapsecns/sanguine/core/metrics" + omnirpcClient "github.com/synapsecns/sanguine/services/omnirpc/client" + scribeAPI "github.com/synapsecns/sanguine/services/scribe/api" + "github.com/synapsecns/sanguine/services/scribe/backend" + "github.com/synapsecns/sanguine/services/scribe/client" + scribeCmd "github.com/synapsecns/sanguine/services/scribe/cmd" + "github.com/synapsecns/sanguine/services/scribe/service" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" + "gorm.io/gorm/schema" // used to embed markdown. _ "embed" @@ -97,6 +102,8 @@ var GuardRunCommand = &cli.Command{ Description: "runs the guard service", Flags: []cli.Flag{configFlag, metricsPortFlag, debugFlag}, Action: func(c *cli.Context) error { + var scribeClient client.ScribeClient + handler, err := metrics.NewFromEnv(c.Context, metadata.BuildInfo()) if err != nil { return fmt.Errorf("failed to create metrics handler: %w", err) @@ -120,9 +127,73 @@ var GuardRunCommand = &cli.Command{ for shouldRetryAtomic.Load() { shouldRetryAtomic.Store(false) - g, _ := errgroup.WithContext(c.Context) + g, ctx := errgroup.WithContext(c.Context) + + switch guardConfig.ScribeConfig.Type { + case "embedded": + eventDB, err := scribeAPI.InitDB( + ctx, + guardConfig.DBConfig.Type, + guardConfig.DBConfig.Source, + handler, + false, + ) + if err != nil { + return fmt.Errorf("failed to initialize database: %w", err) + } + + scribeClients := make(map[uint32][]backend.ScribeBackend) - guard, err := guard.NewGuard(c.Context, guardConfig, baseOmniRPCClient, guardDB, handler) + for _, client := range guardConfig.ScribeConfig.EmbeddedScribeConfig.Chains { + for confNum := 1; confNum <= scribeCmd.MaxConfirmations; confNum++ { + backendClient, err := backend.DialBackend(ctx, fmt.Sprintf("%s/%d/rpc/%d", guardConfig.BaseOmnirpcURL, confNum, client.ChainID), handler) + if err != nil { + return fmt.Errorf("could not start client for %s", fmt.Sprintf("%s/1/rpc/%d", guardConfig.BaseOmnirpcURL, client.ChainID)) + } + + scribeClients[client.ChainID] = append(scribeClients[client.ChainID], backendClient) + } + } + + scribe, err := service.NewScribe(eventDB, scribeClients, guardConfig.ScribeConfig.EmbeddedScribeConfig, handler) + if err != nil { + return fmt.Errorf("failed to initialize scribe: %w", err) + } + + g.Go(func() error { + err := scribe.Start(ctx) + if err != nil { + return fmt.Errorf("failed to start scribe: %w", err) + } + + return nil + }) + embedded := client.NewEmbeddedScribe( + guardConfig.DBConfig.Type, + guardConfig.DBConfig.Source, + handler, + ) + + g.Go(func() error { + err := embedded.Start(c.Context) + if err != nil { + return fmt.Errorf("failed to start embedded scribe: %w", err) + } + + return nil + }) + + scribeClient = embedded.ScribeClient + case "remote": + scribeClient = client.NewRemoteScribe( + uint16(guardConfig.ScribeConfig.Port), + guardConfig.ScribeConfig.URL, + handler, + ).ScribeClient + default: + return fmt.Errorf("invalid scribe type: %s", guardConfig.ScribeConfig.Type) + } + guard, err := guard.NewGuard(c.Context, guardConfig, baseOmniRPCClient, scribeClient, guardDB, handler) if err != nil { return fmt.Errorf("failed to create guard: %w", err) } diff --git a/agents/agents/guard/db/agent_root_test.go b/agents/agents/guard/db/agent_root_test.go new file mode 100644 index 0000000000..68b245d7a5 --- /dev/null +++ b/agents/agents/guard/db/agent_root_test.go @@ -0,0 +1,44 @@ +package db_test + +import ( + "math/big" + + "github.com/brianvoe/gofakeit/v6" + "github.com/ethereum/go-ethereum/common" + . "github.com/stretchr/testify/assert" + "github.com/synapsecns/sanguine/agents/agents/guard/db" +) + +func (t *DBSuite) TestGetSummitBlockNumberForRoot() { + t.RunOnAllDBs(func(testDB db.GuardDB) { + // Store two agent roots. + agentRootA := common.BigToHash(big.NewInt(gofakeit.Int64())) + agentRootB := common.BigToHash(big.NewInt(gofakeit.Int64())) + + blockNumberA := gofakeit.Uint64() + blockNumberB := gofakeit.Uint64() + + err := testDB.StoreAgentRoot( + t.GetTestContext(), + agentRootA, + blockNumberA, + ) + Nil(t.T(), err) + + err = testDB.StoreAgentRoot( + t.GetTestContext(), + agentRootB, + blockNumberB, + ) + Nil(t.T(), err) + + // Call GetSummitBlockNumberForRoot for each agent root. + blockNumber, err := testDB.GetSummitBlockNumberForRoot(t.GetTestContext(), agentRootA.String()) + Nil(t.T(), err) + Equal(t.T(), blockNumberA, blockNumber) + + blockNumber, err = testDB.GetSummitBlockNumberForRoot(t.GetTestContext(), agentRootB.String()) + Nil(t.T(), err) + Equal(t.T(), blockNumberB, blockNumber) + }) +} diff --git a/agents/agents/guard/db/crosstable_test.go b/agents/agents/guard/db/crosstable_test.go new file mode 100644 index 0000000000..4f4e1d5da7 --- /dev/null +++ b/agents/agents/guard/db/crosstable_test.go @@ -0,0 +1,85 @@ +package db_test + +import ( + "math/big" + + "github.com/brianvoe/gofakeit/v6" + "github.com/ethereum/go-ethereum/common" + . "github.com/stretchr/testify/assert" + "github.com/synapsecns/sanguine/agents/agents/guard/db" + "github.com/synapsecns/sanguine/agents/types" +) + +func (t *DBSuite) TestGetRelayableAgentStatuses() { + // Test by setting up multiple addresses and agent roots in the database. Then, once each agent tree is stored with + // the agent root and address, get the agent trees that match up with a specific chain ID. + t.RunOnAllDBs(func(testDB db.GuardDB) { + addressA := common.BigToAddress(big.NewInt(gofakeit.Int64())) + addressB := common.BigToAddress(big.NewInt(gofakeit.Int64())) + addressC := common.BigToAddress(big.NewInt(gofakeit.Int64())) + + agentRootA := common.BigToHash(big.NewInt(gofakeit.Int64())) + agentRootB := common.BigToHash(big.NewInt(gofakeit.Int64())) + agentRootC := common.BigToHash(big.NewInt(gofakeit.Int64())) + + // Insert three rows into the `AgentTree` table. + err := testDB.StoreAgentTree( + t.GetTestContext(), + agentRootA, + addressA, + gofakeit.Uint64(), + [][32]byte{{gofakeit.Uint8()}}, + ) + Nil(t.T(), err) + err = testDB.StoreAgentTree( + t.GetTestContext(), + agentRootB, + addressB, + gofakeit.Uint64(), + [][32]byte{{gofakeit.Uint8()}}, + ) + Nil(t.T(), err) + err = testDB.StoreAgentTree( + t.GetTestContext(), + agentRootC, + addressC, + gofakeit.Uint64(), + [][32]byte{{gofakeit.Uint8()}}, + ) + Nil(t.T(), err) + + // Insert three rows into `RelayableAgentStatus`, two will have matching agent address to `AgentTree` rows and with status `Queued`. + chainA := gofakeit.Uint32() + chainB := chainA + 1 + err = testDB.StoreRelayableAgentStatus( + t.GetTestContext(), + addressA, + types.AgentFlagUnknown, + types.AgentFlagActive, + chainA, + ) + Nil(t.T(), err) + err = testDB.StoreRelayableAgentStatus( + t.GetTestContext(), + addressB, + types.AgentFlagUnknown, + types.AgentFlagActive, + chainA, + ) + Nil(t.T(), err) + err = testDB.StoreRelayableAgentStatus( + t.GetTestContext(), + addressC, + types.AgentFlagUnknown, + types.AgentFlagActive, + chainB, + ) + Nil(t.T(), err) + + // Get the matching agent tree from the database. + agentTrees, err := testDB.GetRelayableAgentStatuses(t.GetTestContext(), chainA) + Nil(t.T(), err) + + Equal(t.T(), 2, len(agentTrees)) + }) +} diff --git a/agents/agents/guard/db/guard_db.go b/agents/agents/guard/db/guard_db.go index 87736842cd..ffcf6a3d2e 100644 --- a/agents/agents/guard/db/guard_db.go +++ b/agents/agents/guard/db/guard_db.go @@ -1,8 +1,59 @@ package db -import submitterDB "github.com/synapsecns/sanguine/ethergo/submitter/db" +import ( + "context" + + "github.com/ethereum/go-ethereum/common" + agentTypes "github.com/synapsecns/sanguine/agents/types" + submitterDB "github.com/synapsecns/sanguine/ethergo/submitter/db" +) + +// GuardDBWriter is the interface for writing to the guard's database. +type GuardDBWriter interface { + // StoreRelayableAgentStatus stores a relayable agent status. + StoreRelayableAgentStatus( + ctx context.Context, + agentAddress common.Address, + staleFlag agentTypes.AgentFlagType, + updatedFlag agentTypes.AgentFlagType, + domain uint32, + ) error + + // UpdateAgentStatusRelayedState updates the relayed state for a relayable agent status. + UpdateAgentStatusRelayedState( + ctx context.Context, + agentAddress common.Address, + state agentTypes.AgentStatusRelayedState, + ) error + + // StoreAgentTree stores an agent tree. + StoreAgentTree( + ctx context.Context, + agentRoot [32]byte, + agentAddress common.Address, + blockNumber uint64, + proof [][32]byte, + ) error + + // StoreAgentRoot stores an agent root. + StoreAgentRoot( + ctx context.Context, + agentRoot [32]byte, + blockNumber uint64, + ) error +} + +// GuardDBReader is the interface for reading from the guard's database. +type GuardDBReader interface { + // GetRelayableAgentStatuses gets eligible parameters for the updateAgentStatus() contract call. + GetRelayableAgentStatuses(ctx context.Context, chainID uint32) ([]agentTypes.AgentTree, error) + // GetSummitBlockNumberForRoot gets the summit block number for a given agent root. + GetSummitBlockNumberForRoot(ctx context.Context, agentRoot string) (uint64, error) +} // GuardDB is the interface for the guard's database. type GuardDB interface { + GuardDBWriter + GuardDBReader SubmitterDB() submitterDB.Service } diff --git a/agents/agents/guard/db/sql/base/agent_root.go b/agents/agents/guard/db/sql/base/agent_root.go new file mode 100644 index 0000000000..4e2dd7061b --- /dev/null +++ b/agents/agents/guard/db/sql/base/agent_root.go @@ -0,0 +1,53 @@ +package base + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "gorm.io/gorm/clause" +) + +// StoreAgentRoot stores an agent root. +func (s Store) StoreAgentRoot( + ctx context.Context, + agentRoot [32]byte, + blockNumber uint64, +) error { + dbAgentRoot := common.BytesToHash(agentRoot[:]).String() + + dbTx := s.DB().WithContext(ctx). + Clauses(clause.OnConflict{ + Columns: []clause.Column{ + {Name: AgentRootFieldName}, + }, + DoNothing: true, + }). + Create(&AgentRoot{ + AgentRoot: dbAgentRoot, + BlockNumber: blockNumber, + }) + + if dbTx.Error != nil { + return fmt.Errorf("failed to store agent root: %w", dbTx.Error) + } + + return nil +} + +// GetSummitBlockNumberForRoot gets the summit block number for a given agent root. +func (s Store) GetSummitBlockNumberForRoot(ctx context.Context, agentRoot string) (uint64, error) { + var blockNumber uint64 + err := s.DB().WithContext(ctx).Debug(). + Model(&AgentRoot{}). + Where(fmt.Sprintf("%s = ?", AgentRootFieldName), agentRoot). + Select(fmt.Sprintf("MIN(%s)", BlockNumberFieldName)). + Row(). + Scan(&blockNumber) + + if err != nil { + return blockNumber, fmt.Errorf("failed to get summit block number for root: %w", err) + } + + return blockNumber, nil +} diff --git a/agents/agents/guard/db/sql/base/agent_tree.go b/agents/agents/guard/db/sql/base/agent_tree.go new file mode 100644 index 0000000000..0fc95a90a0 --- /dev/null +++ b/agents/agents/guard/db/sql/base/agent_tree.go @@ -0,0 +1,46 @@ +package base + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "gorm.io/gorm/clause" +) + +// StoreAgentTree stores an agent tree. +func (s Store) StoreAgentTree( + ctx context.Context, + agentRoot [32]byte, + agentAddress common.Address, + blockNumber uint64, + proof [][32]byte, +) error { + dbAgentRoot := common.BytesToHash(agentRoot[:]).String() + + proofJSON, err := json.Marshal(proof) + if err != nil { + return fmt.Errorf("failed to marshal proof: %w", err) + } + + dbTx := s.DB().WithContext(ctx). + Clauses(clause.OnConflict{ + Columns: []clause.Column{ + {Name: AgentRootFieldName}, {Name: AgentAddressFieldName}, + }, + DoNothing: true, + }). + Create(&AgentTree{ + AgentRoot: dbAgentRoot, + AgentAddress: agentAddress.String(), + BlockNumber: blockNumber, + Proof: proofJSON, + }) + + if dbTx.Error != nil { + return fmt.Errorf("failed to store agent tree: %w", dbTx.Error) + } + + return nil +} diff --git a/agents/agents/guard/db/sql/base/base.go b/agents/agents/guard/db/sql/base/base.go index e73ca70a4e..3fe73e7f51 100644 --- a/agents/agents/guard/db/sql/base/base.go +++ b/agents/agents/guard/db/sql/base/base.go @@ -33,6 +33,7 @@ func (s Store) SubmitterDB() submitterDB.Service { // GetAllModels gets all models to migrate. // see: https://medium.com/@SaifAbid/slice-interfaces-8c78f8b6345d for an explanation of why we can't do this at initialization time func GetAllModels() (allModels []interface{}) { + allModels = append(allModels, &RelayableAgentStatus{}, &AgentTree{}, &AgentRoot{}) allModels = append(allModels, txdb.GetAllModels()...) return allModels } diff --git a/agents/agents/guard/db/sql/base/crosstable.go b/agents/agents/guard/db/sql/base/crosstable.go new file mode 100644 index 0000000000..1a5396c272 --- /dev/null +++ b/agents/agents/guard/db/sql/base/crosstable.go @@ -0,0 +1,83 @@ +package base + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/imkira/go-interpol" + agentTypes "github.com/synapsecns/sanguine/agents/types" + "github.com/synapsecns/sanguine/core/dbcommon" +) + +type agentTreeWithStatus struct { + AgentTree + AgentDomain uint32 + UpdatedAgentFlag agentTypes.AgentFlagType +} + +// GetRelayableAgentStatuses gets the parameters for updating the agent status with the following steps: +// 1. Load the `AgentTree` table. +// 2. Filter the rows where the `AgentStatusRelayedState` is `Queued`, and the `Domain` is the given `chainID`. +// 3. Outer join the `AgentTree` table on the `RelayableAgentStatus` table on the `AgentAddress` fields. +// 4. Return all fields in the `AgentTree` table as well as `UpdatedFlag` from the `RelayableAgentStatus` table. +func (s Store) GetRelayableAgentStatuses(ctx context.Context, chainID uint32) ([]agentTypes.AgentTree, error) { + agentTreesTableName, err := dbcommon.GetModelName(s.DB(), &AgentTree{}) + if err != nil { + return nil, fmt.Errorf("failed to get agent trees table name: %w", err) + } + + relayableAgentStatusesTableName, err := dbcommon.GetModelName(s.DB(), &RelayableAgentStatus{}) + if err != nil { + return nil, fmt.Errorf("failed to get relayable agent statuses table name: %w", err) + } + + query, err := interpol.WithMap( + ` + SELECT aTable.*, rTable.{updatedFlag} + FROM {agentTreesTable} AS aTable + JOIN ( + SELECT * FROM {relayableAgentStatusesTable} + WHERE {agentStatusRelayedState} = ? + AND {domain} = ? + ) AS rTable + ON aTable.{agentAddress} = rTable.{agentAddress} + `, + map[string]string{ + "domain": DomainFieldName, + "updatedFlag": UpdatedFlagFieldName, + "agentTreesTable": agentTreesTableName, + "relayableAgentStatusesTable": relayableAgentStatusesTableName, + "agentStatusRelayedState": AgentStatusRelayedStateFieldName, + "agentAddress": AgentAddressFieldName, + }) + if err != nil { + return nil, fmt.Errorf("failed to interpolate query: %w", err) + } + + var dbAgentTrees []agentTreeWithStatus + err = s.DB().WithContext(ctx).Raw(query, agentTypes.Queued, chainID).Scan(&dbAgentTrees).Error + if err != nil { + return nil, fmt.Errorf("failed to get agent trees: %w", err) + } + + // Convert DB fields to agent types. + agentTrees := []agentTypes.AgentTree{} + for _, tree := range dbAgentTrees { + var proofBytes [][32]byte + err = json.Unmarshal(tree.Proof, &proofBytes) + if err != nil { + return nil, fmt.Errorf("could not unmarshal proof: %w", err) + } + agentTrees = append(agentTrees, agentTypes.AgentTree{ + AgentRoot: tree.AgentRoot, + AgentAddress: common.HexToAddress(tree.AgentAddress), + AgentDomain: chainID, + UpdatedAgentFlag: tree.UpdatedAgentFlag, + BlockNumber: tree.BlockNumber, + Proof: proofBytes, + }) + } + return agentTrees, nil +} diff --git a/agents/agents/guard/db/sql/base/model.go b/agents/agents/guard/db/sql/base/model.go new file mode 100644 index 0000000000..2f6454f2b2 --- /dev/null +++ b/agents/agents/guard/db/sql/base/model.go @@ -0,0 +1,70 @@ +package base + +import ( + "encoding/json" + + agentTypes "github.com/synapsecns/sanguine/agents/types" + "github.com/synapsecns/sanguine/core/dbcommon" +) + +// define common field names. See package docs for an explanation of why we have to do this. +// note: some models share names. In cases where they do, we run the check against all names. +// This is cheap because it's only done at startup. +func init() { + namer := dbcommon.NewNamer(GetAllModels()) + AgentRootFieldName = namer.GetConsistentName("AgentRoot") + AgentAddressFieldName = namer.GetConsistentName("AgentAddress") + BlockNumberFieldName = namer.GetConsistentName("BlockNumber") + AgentStatusRelayedStateFieldName = namer.GetConsistentName("AgentStatusRelayedState") + DomainFieldName = namer.GetConsistentName("Domain") + UpdatedFlagFieldName = namer.GetConsistentName("UpdatedFlag") +} + +var ( + // AgentRootFieldName is the field name of the agent root. + AgentRootFieldName string + // AgentAddressFieldName gets the agent address field name. + AgentAddressFieldName string + // BlockNumberFieldName gets the agent block number field name. + BlockNumberFieldName string + // AgentStatusRelayedStateFieldName gets the relayable agent status field name. + AgentStatusRelayedStateFieldName string + // DomainFieldName gets the agent domain field name. + DomainFieldName string + // UpdatedFlagFieldName gets the updated flag field name. + UpdatedFlagFieldName string +) + +// RelayableAgentStatus is used for tracking agent statuses that are out of +// sync and need to be relayed to a remote chain. +type RelayableAgentStatus struct { + AgentAddress string `gorm:"column:agent_address"` + // StaleFlag is the old flag that needs to be updated. + StaleFlag agentTypes.AgentFlagType `gorm:"column:stale_flag"` + // UpdatedFlag is the new flag value that should be relayed. + UpdatedFlag agentTypes.AgentFlagType `gorm:"column:updated_flag"` + // Domain is the domain of the agent status. + Domain uint32 `gorm:"column:domain"` + // AgentStatusRelayedState is the state of the relayable agent status. + AgentStatusRelayedState agentTypes.AgentStatusRelayedState `gorm:"column:agent_status_relayed_state"` +} + +// AgentTree is the state of an agent tree on Summit. +type AgentTree struct { + // AgentRoot is the root of the agent tree. + AgentRoot string `gorm:"column:agent_root;primaryKey"` + // AgentAddress is the address of the agent for the Merkle proof. + AgentAddress string `gorm:"column:agent_address;primaryKey"` + // BlockNumber is the block number that the agent tree was updated (on summit). + BlockNumber uint64 `gorm:"column:block_number"` + // Proof is the agent tree proof. + Proof json.RawMessage `gorm:"column:proof"` +} + +// AgentRoot is the state of the agent roots on summit. +type AgentRoot struct { + // AgentRoot is the root of the agent tree. + AgentRoot string `gorm:"column:agent_root;primaryKey"` + // BlockNumber is the block number that the agent tree was updated. + BlockNumber uint64 `gorm:"column:block_number"` +} diff --git a/agents/agents/guard/db/sql/base/relayable_agent_status.go b/agents/agents/guard/db/sql/base/relayable_agent_status.go new file mode 100644 index 0000000000..a4267f4a63 --- /dev/null +++ b/agents/agents/guard/db/sql/base/relayable_agent_status.go @@ -0,0 +1,53 @@ +package base + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" + agentTypes "github.com/synapsecns/sanguine/agents/types" +) + +// StoreRelayableAgentStatus stores a relayable agent status. +func (s Store) StoreRelayableAgentStatus( + ctx context.Context, + agentAddress common.Address, + staleFlag agentTypes.AgentFlagType, + updatedFlag agentTypes.AgentFlagType, + domain uint32, +) error { + dbTx := s.DB().WithContext(ctx). + Create(&RelayableAgentStatus{ + AgentAddress: agentAddress.String(), + StaleFlag: staleFlag, + UpdatedFlag: updatedFlag, + Domain: domain, + AgentStatusRelayedState: agentTypes.Queued, + }) + + if dbTx.Error != nil { + return fmt.Errorf("failed to store relayable agent status: %w", dbTx.Error) + } + + return nil +} + +// UpdateAgentStatusRelayedState updates the state for a RelayableAgentStatus. +func (s Store) UpdateAgentStatusRelayedState( + ctx context.Context, + agentAddress common.Address, + state agentTypes.AgentStatusRelayedState, +) error { + mask := RelayableAgentStatus{ + AgentAddress: agentAddress.String(), + } + + dbTx := s.DB().WithContext(ctx). + Model(&RelayableAgentStatus{}). + Where(mask). + Update(AgentStatusRelayedStateFieldName, state) + if dbTx.Error != nil { + return fmt.Errorf("failed to update relayed state: %w", dbTx.Error) + } + return nil +} diff --git a/agents/agents/guard/db/suite_test.go b/agents/agents/guard/db/suite_test.go new file mode 100644 index 0000000000..c8d6d22b7f --- /dev/null +++ b/agents/agents/guard/db/suite_test.go @@ -0,0 +1,120 @@ +package db_test + +import ( + "database/sql" + "fmt" + "os" + "sync" + "testing" + "time" + + . "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + "gorm.io/gorm/schema" + + "github.com/Flaque/filet" + "github.com/synapsecns/sanguine/agents/agents/guard/db" + "github.com/synapsecns/sanguine/agents/agents/guard/db/sql/mysql" + "github.com/synapsecns/sanguine/agents/agents/guard/db/sql/sqlite" + "github.com/synapsecns/sanguine/agents/agents/guard/metadata" + "github.com/synapsecns/sanguine/core" + "github.com/synapsecns/sanguine/core/dbcommon" + "github.com/synapsecns/sanguine/core/metrics" + "github.com/synapsecns/sanguine/core/metrics/localmetrics" + "github.com/synapsecns/sanguine/core/testsuite" +) + +type DBSuite struct { + *testsuite.TestSuite + dbs []db.GuardDB + metrics metrics.Handler +} + +// NewEventDBSuite creates a new EventDBSuite. +func NewEventDBSuite(tb testing.TB) *DBSuite { + tb.Helper() + return &DBSuite{ + TestSuite: testsuite.NewTestSuite(tb), + dbs: []db.GuardDB{}, + } +} + +func (t *DBSuite) SetupSuite() { + t.TestSuite.SetupSuite() + + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(t.GetSuiteContext(), t.T()) + metricsHandler = metrics.Jaeger + } + + var err error + t.metrics, err = metrics.NewByType(t.GetSuiteContext(), metadata.BuildInfo(), metricsHandler) + Nil(t.T(), err) +} + +func (t *DBSuite) SetupTest() { + t.TestSuite.SetupTest() + + sqliteStore, err := sqlite.NewSqliteStore(t.GetTestContext(), filet.TmpDir(t.T(), ""), t.metrics, false) + Nil(t.T(), err) + + t.dbs = []db.GuardDB{sqliteStore} + t.setupMysqlDB() +} + +func (t *DBSuite) setupMysqlDB() { + // skip if mysql test disabled, this really only needs to be run in ci + + // skip if mysql test disabled + if os.Getenv(dbcommon.EnableMysqlTestVar) == "" { + return + } + // sets up the conn string to the default database + connString := dbcommon.GetTestConnString() + // sets up the mysql db + testDB, err := sql.Open("mysql", connString) + Nil(t.T(), err) + // close the db once the connection is done + defer func() { + Nil(t.T(), testDB.Close()) + }() + + // override the naming strategy to prevent tests from messing with each other. + // todo this should be solved via a proper teardown process or transactions. + mysql.NamingStrategy = schema.NamingStrategy{ + TablePrefix: fmt.Sprintf("test%d_%d_", t.GetTestID(), time.Now().Unix()), + } + + mysql.MaxIdleConns = 10 + + // create the sql store + mysqlStore, err := mysql.NewMysqlStore(t.GetTestContext(), connString, t.metrics) + Nil(t.T(), err) + // add the db + t.dbs = append(t.dbs, mysqlStore) +} + +func (t *DBSuite) RunOnAllDBs(testFunc func(testDB db.GuardDB)) { + t.T().Helper() + + wg := sync.WaitGroup{} + for _, testDB := range t.dbs { + wg.Add(1) + // capture the value + go func(testDB db.GuardDB) { + defer wg.Done() + testFunc(testDB) + }(testDB) + } + wg.Wait() +} + +// TestDBSuite tests the db suite. +func TestEventDBSuite(t *testing.T) { + suite.Run(t, NewEventDBSuite(t)) +} diff --git a/agents/agents/guard/fraud.go b/agents/agents/guard/fraud.go new file mode 100644 index 0000000000..7dee240103 --- /dev/null +++ b/agents/agents/guard/fraud.go @@ -0,0 +1,792 @@ +package guard + +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/synapsecns/sanguine/agents/types" + "github.com/synapsecns/sanguine/core/retry" +) + +// handleSnapshot checks a snapshot for invalid states. +// If an invalid state is found, initiate slashing and submit a state report. +// +//nolint:cyclop,gocognit +func (g Guard) handleSnapshot(ctx context.Context, log ethTypes.Log) error { + fraudSnapshot, err := g.inboxParser.ParseSnapshotAccepted(log) + if err != nil { + return fmt.Errorf("could not parse snapshot accepted: %w", err) + } + + // Verify each state in the snapshot. + for si, s := range fraudSnapshot.Snapshot.States() { + stateIndex, state := si, s + isSlashable, err := g.isStateSlashable(ctx, state) + if err != nil { + return fmt.Errorf("could not handle state: %w", err) + } + if !isSlashable { + continue + } + + // Initiate slashing on origin. + _, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(state.Origin())), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { + tx, err = g.domains[state.Origin()].LightInbox().VerifyStateWithSnapshot( + transactor, + int64(stateIndex), + fraudSnapshot.Payload, + fraudSnapshot.Signature, + ) + if err != nil { + return nil, fmt.Errorf("could not verify state with snapshot: %w", err) + } + + return + }) + if err != nil { + return fmt.Errorf("could not submit VerifyStateWithSnapshot tx: %w", err) + } + + // Check if we should submit the state report. + shouldSubmit, err := g.shouldSubmitStateReport(ctx, fraudSnapshot) + if err != nil { + return fmt.Errorf("could not check if should submit state report: %w", err) + } + if !shouldSubmit { + return nil + } + + // Submit the state report to summit. + srSignature, _, _, err := state.SignState(ctx, g.bondedSigner) + if err != nil { + return fmt.Errorf("could not sign state: %w", err) + } + ok, err := g.prepareStateReport(ctx, fraudSnapshot.Agent, g.summitDomainID) + if err != nil { + return fmt.Errorf("could not prepare state report on summit: %w", err) + } + if !ok { + continue + } + _, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(g.summitDomainID)), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { + tx, err = g.domains[g.summitDomainID].Inbox().SubmitStateReportWithSnapshot( + transactor, + int64(stateIndex), + srSignature, + fraudSnapshot.Payload, + fraudSnapshot.Signature, + ) + if err != nil { + return nil, fmt.Errorf("could not submit state report with snapshot to summit: %w", err) + } + + return + }) + if err != nil { + return fmt.Errorf("could not submit SubmitStateReportWithSnapshot tx: %w", err) + } + + // Submit the state report to the remote chain. + ok, err = g.prepareStateReport(ctx, fraudSnapshot.Agent, fraudSnapshot.AgentDomain) + if err != nil { + return fmt.Errorf("could not prepare state report on summit: %w", err) + } + if !ok { + continue + } + _, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(fraudSnapshot.AgentDomain)), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { + tx, err = g.domains[fraudSnapshot.AgentDomain].LightInbox().SubmitStateReportWithSnapshot( + transactor, + int64(stateIndex), + srSignature, + fraudSnapshot.Payload, + fraudSnapshot.Signature, + ) + if err != nil { + return nil, fmt.Errorf("could not submit state report with snapshot to agent domain %d: %w", fraudSnapshot.AgentDomain, err) + } + + return + }) + if err != nil { + return fmt.Errorf("could not submit SubmitStateReportWithSnapshot tx: %w", err) + } + } + + return nil +} + +// Only submit a state report if we are not on Summit, and the snapshot +// agent is not currently in dispute. +func (g Guard) shouldSubmitStateReport(ctx context.Context, snapshot *types.FraudSnapshot) (bool, error) { + var disputeStatus types.DisputeStatus + var err error + contractCall := func(ctx context.Context) error { + disputeStatus, err = g.domains[g.summitDomainID].BondingManager().GetDisputeStatus(ctx, snapshot.Agent) + if err != nil { + return fmt.Errorf("could not get dispute status: %w", err) + } + + return nil + } + err = retry.WithBackoff(ctx, contractCall, g.retryConfig...) + if err != nil { + return false, fmt.Errorf("could not get dispute status: %w", err) + } + + isNotary := snapshot.AgentDomain != 0 + isNotInDispute := disputeStatus.Flag() == types.DisputeFlagNone + shouldSubmit := isNotary && isNotInDispute + return shouldSubmit, nil +} + +// isStateSlashable checks if a state is slashable, i.e. if the state is valid on the +// Origin, and if the agent is in a slashable status. +func (g Guard) isStateSlashable(ctx context.Context, state types.State) (bool, error) { + statePayload, err := state.Encode() + if err != nil { + return false, fmt.Errorf("could not encode state: %w", err) + } + + // Verify that the state is valid w.r.t. Origin. + var isValid bool + contractCall := func(ctx context.Context) error { + isValid, err = g.domains[state.Origin()].Origin().IsValidState(ctx, statePayload) + if err != nil { + return fmt.Errorf("could not check validity of state: %w", err) + } + + return nil + } + err = retry.WithBackoff(ctx, contractCall, g.retryConfig...) + if err != nil { + return false, fmt.Errorf("could not check validity of state: %w", err) + } + return !isValid, nil +} + +// handleAttestation checks whether an attestation is valid. +// If invalid, initiate slashing and/or submit a fraud report. +func (g Guard) handleAttestation(ctx context.Context, log ethTypes.Log) error { + fraudAttestation, err := g.lightInboxParser.ParseAttestationAccepted(log) + if err != nil { + return fmt.Errorf("could not parse attestation accepted: %w", err) + } + + var isValid bool + contractCall := func(ctx context.Context) error { + isValid, err = g.domains[g.summitDomainID].Summit().IsValidAttestation(ctx, fraudAttestation.Payload) + if err != nil { + return fmt.Errorf("could not check validity of attestation: %w", err) + } + + return nil + } + err = retry.WithBackoff(ctx, contractCall, g.retryConfig...) + if err != nil { + return fmt.Errorf("could not check validity of attestation: %w", err) + } + + if isValid { + return g.handleValidAttestation(ctx, fraudAttestation) + } + + return g.handleInvalidAttestation(ctx, fraudAttestation) +} + +// handleValidAttestation handles an attestation that is valid, but may +// attest to a snapshot that contains an invalid state. +// +//nolint:cyclop,gocognit +func (g Guard) handleValidAttestation(ctx context.Context, fraudAttestation *types.FraudAttestation) error { + // Fetch the attested snapshot. + var snapshot types.Snapshot + var err error + contractCall := func(ctx context.Context) error { + snapshot, err = g.domains[g.summitDomainID].Summit().GetNotarySnapshot(ctx, fraudAttestation.Payload) + if err != nil { + return fmt.Errorf("could not get snapshot: %w", err) + } + + return nil + } + err = retry.WithBackoff(ctx, contractCall, g.retryConfig...) + if err != nil { + return fmt.Errorf("could not get snapshot: %w", err) + } + + snapPayload, err := snapshot.Encode() + if err != nil { + return fmt.Errorf("could not encode snapshot: %w", err) + } + + // Verify each state in the snapshot. + for si, s := range snapshot.States() { + stateIndex, state := si, s + isSlashable, err := g.isStateSlashable(ctx, state) + if err != nil { + return fmt.Errorf("could not check if state is slashable: %w", err) + } + if !isSlashable { + continue + } + + // Initiate slashing on origin. + _, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(state.Origin())), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { + tx, err = g.domains[state.Origin()].LightInbox().VerifyStateWithAttestation( + transactor, + int64(stateIndex), + snapPayload, + fraudAttestation.Payload, + fraudAttestation.Signature, + ) + if err != nil { + return nil, fmt.Errorf("could not verify state with attestation: %w", err) + } + + return + }) + + if err != nil { + return fmt.Errorf("could not submit VerifyStateWithAttestation tx: %w", err) + } + + // Submit the state report on summit. + srSignature, _, _, err := state.SignState(ctx, g.bondedSigner) + if err != nil { + return fmt.Errorf("could not sign state: %w", err) + } + ok, err := g.prepareStateReport(ctx, fraudAttestation.Notary, g.summitDomainID) + if err != nil { + return fmt.Errorf("could not prepare state report on summit: %w", err) + } + if !ok { + continue + } + _, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(g.summitDomainID)), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { + tx, err = g.domains[g.summitDomainID].Inbox().SubmitStateReportWithAttestation( + transactor, + int64(stateIndex), + srSignature, + snapPayload, + fraudAttestation.Payload, + fraudAttestation.Signature, + ) + if err != nil { + return nil, fmt.Errorf("could not submit state report with attestation on summit: %w", err) + } + + return + }) + if err != nil { + return fmt.Errorf("could not submit SubmitStateReportWithAttestation tx: %w", err) + } + + // Submit the state report on the remote chain. + ok, err = g.prepareStateReport(ctx, fraudAttestation.Notary, fraudAttestation.AgentDomain) + if err != nil { + return fmt.Errorf("could not prepare state report on remote: %w", err) + } + if !ok { + continue + } + _, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(fraudAttestation.AgentDomain)), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { + tx, err = g.domains[fraudAttestation.AgentDomain].LightInbox().SubmitStateReportWithAttestation( + transactor, + int64(stateIndex), + srSignature, + snapPayload, + fraudAttestation.Payload, + fraudAttestation.Signature, + ) + if err != nil { + return nil, fmt.Errorf("could not submit state report with attestation on agent domain %d: %w", fraudAttestation.AgentDomain, err) + } + + return + }) + if err != nil { + return fmt.Errorf("could not submit SubmitStateReportWithAttestation tx: %w", err) + } + } + + return nil +} + +// prepareStateReport checks if the given agent is in a slashable status (Active or Unstaking), +// and relays the agent status from Summit to the given chain if necessary. +// +//nolint:cyclop +func (g Guard) prepareStateReport(ctx context.Context, agent common.Address, chainID uint32) (ok bool, err error) { + var agentStatus types.AgentStatus + //nolint:nestif + if chainID == g.summitDomainID { + contractCall := func(ctx context.Context) error { + agentStatus, err = g.domains[chainID].BondingManager().GetAgentStatus(ctx, agent) + if err != nil { + return fmt.Errorf("could not get agent status: %w", err) + } + + return nil + } + err = retry.WithBackoff(ctx, contractCall, g.retryConfig...) + if err != nil { + return false, fmt.Errorf("could not get agent status: %w", err) + } + } else { + contractCall := func(ctx context.Context) error { + agentStatus, err = g.domains[chainID].LightManager().GetAgentStatus(ctx, agent) + if err != nil { + return fmt.Errorf("could not get agent status: %w", err) + } + + return nil + } + err = retry.WithBackoff(ctx, contractCall, g.retryConfig...) + if err != nil { + return false, fmt.Errorf("could not get agent status: %w", err) + } + } + if err != nil { + return false, fmt.Errorf("could not get agent status: %w", err) + } + + //nolint:exhaustive + switch agentStatus.Flag() { + case types.AgentFlagUnknown: + if chainID == g.summitDomainID { + return false, fmt.Errorf("cannot submit state report for Unknown agent on summit") + } + // Update the agent status to active using the last known root on remote chain. + err = g.guardDB.StoreRelayableAgentStatus( + ctx, + agent, + types.AgentFlagUnknown, + types.AgentFlagActive, + chainID, + ) + if err != nil { + return false, fmt.Errorf("could not store relayable agent status: %w", err) + } + err = g.updateAgentStatus(ctx, chainID) + if err != nil { + return false, err + } + return true, nil + case types.AgentFlagActive, types.AgentFlagUnstaking: + return true, nil + default: + return false, nil + } +} + +// handleInvalidAttestation handles an invalid attestation by initiating slashing on summit, +// then submitting an attestation fraud report on the accused agent's Domain. +func (g Guard) handleInvalidAttestation(ctx context.Context, fraudAttestation *types.FraudAttestation) error { + // Initiate slashing for invalid attestation. + _, err := g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(g.summitDomainID)), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { + tx, err = g.domains[g.summitDomainID].Inbox().VerifyAttestation( + transactor, + fraudAttestation.Payload, + fraudAttestation.Signature, + ) + if err != nil { + return nil, fmt.Errorf("could not verify attestation: %w", err) + } + + return + }) + if err != nil { + return fmt.Errorf("could not submit VerifyAttestation tx: %w", err) + } + + // Submit a fraud report by calling `submitAttestationReport()` on the remote chain. + arSignature, _, _, err := fraudAttestation.Attestation.SignAttestation(ctx, g.bondedSigner, false) + if err != nil { + return fmt.Errorf("could not sign attestation: %w", err) + } + arSignatureEncoded, err := types.EncodeSignature(arSignature) + if err != nil { + return fmt.Errorf("could not encode signature: %w", err) + } + _, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(fraudAttestation.AgentDomain)), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { + tx, err = g.domains[fraudAttestation.AgentDomain].LightInbox().SubmitAttestationReport( + transactor, + fraudAttestation.Payload, + arSignatureEncoded, + fraudAttestation.Signature, + ) + if err != nil { + return nil, fmt.Errorf("could not submit attestation report: %w", err) + } + + return + }) + if err != nil { + return fmt.Errorf("could not submit SubmitAttestationReport tx: %w", err) + } + + return nil +} + +//nolint:cyclop +func (g Guard) handleReceipt(ctx context.Context, log ethTypes.Log) error { + fraudReceipt, err := g.inboxParser.ParseReceiptAccepted(log) + if err != nil { + return fmt.Errorf("could not parse receipt accepted: %w", err) + } + + // Validate the receipt. + receipt, err := types.DecodeReceipt(fraudReceipt.RcptPayload) + if err != nil { + return fmt.Errorf("could not decode receipt: %w", err) + } + + var isValid bool + contractCall := func(ctx context.Context) error { + isValid, err = g.domains[receipt.Destination()].Destination().IsValidReceipt(ctx, fraudReceipt.RcptPayload) + if err != nil { + return fmt.Errorf("could not check validity of attestation: %w", err) + } + + return nil + } + err = retry.WithBackoff(ctx, contractCall, g.retryConfig...) + if err != nil { + return fmt.Errorf("could not check validity of attestation: %w", err) + } + if isValid { + return nil + } + + // Initiate slashing for an invalid receipt, and optionally submit a fraud report. + //nolint:nestif + if receipt.Destination() == g.summitDomainID { + _, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(receipt.Destination())), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { + tx, err = g.domains[receipt.Destination()].Inbox().VerifyReceipt( + transactor, + fraudReceipt.RcptPayload, + fraudReceipt.RcptSignature, + ) + if err != nil { + return nil, fmt.Errorf("could not verify receipt: %w", err) + } + + return + }) + if err != nil { + return fmt.Errorf("could not submit VerifyReceipt tx: %w", err) + } + } else { + _, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(receipt.Destination())), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { + tx, err = g.domains[receipt.Destination()].LightInbox().VerifyReceipt( + transactor, + fraudReceipt.RcptPayload, + fraudReceipt.RcptSignature, + ) + if err != nil { + return nil, fmt.Errorf("could not verify receipt: %w", err) + } + + return + }) + if err != nil { + return fmt.Errorf("could not submit VerifyReceipt tx: %w", err) + } + + rrReceipt, _, _, err := receipt.SignReceipt(ctx, g.bondedSigner, false) + if err != nil { + return fmt.Errorf("could not sign receipt: %w", err) + } + rrReceiptBytes, err := types.EncodeSignature(rrReceipt) + if err != nil { + return fmt.Errorf("could not encode receipt: %w", err) + } + _, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(g.summitDomainID)), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { + tx, err = g.domains[g.summitDomainID].Inbox().SubmitReceiptReport( + transactor, + fraudReceipt.RcptPayload, + fraudReceipt.RcptSignature, + rrReceiptBytes, + ) + if err != nil { + return nil, fmt.Errorf("could not submit receipt report: %w", err) + } + + return + }) + if err != nil { + return fmt.Errorf("could not submit SubmitReceiptReport tx: %w", err) + } + } + + return nil +} + +// handleStatusUpdated stores models related to a StatusUpdated event. +// +//nolint:cyclop,gocognit +func (g Guard) handleStatusUpdated(ctx context.Context, log ethTypes.Log, chainID uint32) error { + statusUpdated, err := g.bondingManagerParser.ParseStatusUpdated(log) + if err != nil { + return fmt.Errorf("could not parse status updated: %w", err) + } + + //nolint:exhaustive + switch types.AgentFlagType(statusUpdated.Flag) { + case types.AgentFlagFraudulent: + var agentProof [][32]byte + contractCall := func(ctx context.Context) error { + agentProof, err = g.domains[g.summitDomainID].BondingManager().GetProof(ctx, statusUpdated.Agent) + if err != nil { + return fmt.Errorf("could not get proof: %w", err) + } + + return nil + } + err = retry.WithBackoff(ctx, contractCall, g.retryConfig...) + if err != nil { + return fmt.Errorf("could not get proof: %w", err) + } + + _, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(g.summitDomainID)), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { + tx, err = g.domains[g.summitDomainID].BondingManager().CompleteSlashing( + transactor, + statusUpdated.Domain, + statusUpdated.Agent, + agentProof, + ) + if err != nil { + return nil, fmt.Errorf("could not complete slashing: %w", err) + } + + return + }) + if err != nil { + return fmt.Errorf("could not submit CompleteSlashing tx: %w", err) + } + case types.AgentFlagSlashed: + var agentRoot [32]byte + contractCall := func(ctx context.Context) error { + agentRoot, err = g.domains[g.summitDomainID].BondingManager().GetAgentRoot(ctx) + if err != nil { + return fmt.Errorf("could not get agent root: %w", err) + } + + return nil + } + err = retry.WithBackoff(ctx, contractCall, g.retryConfig...) + if err != nil { + return fmt.Errorf("could not get agent root: %w", err) + } + + var agentProof [][32]byte + contractCall = func(ctx context.Context) error { + agentProof, err = g.domains[g.summitDomainID].BondingManager().GetProof(ctx, statusUpdated.Agent) + if err != nil { + return fmt.Errorf("could not get proof: %w", err) + } + + return nil + } + err = retry.WithBackoff(ctx, contractCall, g.retryConfig...) + if err != nil { + return fmt.Errorf("could not get proof: %w", err) + } + + var remoteStatus types.AgentStatus + if chainID == g.summitDomainID { + err = g.guardDB.StoreAgentTree( + ctx, + agentRoot, + statusUpdated.Agent, + log.BlockNumber, + agentProof, + ) + if err != nil { + return fmt.Errorf("could not store agent tree: %w", err) + } + + err = g.guardDB.StoreAgentRoot( + ctx, + agentRoot, + log.BlockNumber, + ) + if err != nil { + return fmt.Errorf("could not store agent root: %w", err) + } + } + + if statusUpdated.Domain != 0 { + // Fetch the current remote status and check whether the status is synced. + contractCall := func(ctx context.Context) error { + remoteStatus, err = g.domains[statusUpdated.Domain].LightManager().GetAgentStatus(ctx, statusUpdated.Agent) + if err != nil { + return fmt.Errorf("could not get agent status: %w", err) + } + + return nil + } + err = retry.WithBackoff(ctx, contractCall, g.retryConfig...) + if err != nil { + return fmt.Errorf("could not get agent status: %w", err) + } + + if remoteStatus.Flag() == types.AgentFlagType(statusUpdated.Flag) { + return nil + } + + // If not synced, store a relayable agent status. + err = g.guardDB.StoreRelayableAgentStatus( + ctx, + statusUpdated.Agent, + remoteStatus.Flag(), + types.AgentFlagType(statusUpdated.Flag), + statusUpdated.Domain, + ) + if err != nil { + return fmt.Errorf("could not store relayable agent status: %w", err) + } + } + default: + logger.Errorf("Witnessed agent status updated, but not handling [status=%d, agent=%s]", statusUpdated.Flag, statusUpdated.Agent) + } + + return nil +} + +// handleRootUpdated stores models related to a RootUpdated event. +func (g Guard) handleRootUpdated(ctx context.Context, log ethTypes.Log, chainID uint32) error { + if chainID == g.summitDomainID { + newRoot, err := g.bondingManagerParser.ParseRootUpdated(log) + if err != nil || newRoot == nil { + return fmt.Errorf("could not parse root updated: %w", err) + } + err = g.guardDB.StoreAgentRoot( + ctx, + *newRoot, + log.BlockNumber, + ) + if err != nil { + return fmt.Errorf("could not store agent root: %w", err) + } + } + + return nil +} + +// updateAgentStatuses updates the status of all agents on all chains, except for summit. +func (g Guard) updateAgentStatuses(ctx context.Context) error { + for _, domain := range g.domains { + chainID := domain.Config().DomainID + if chainID == g.summitDomainID { + continue + } + + err := g.updateAgentStatus(ctx, chainID) + if err != nil { + return err + } + } + return nil +} + +// updateAgentStatus updates the status for each agent with a pending agent tree model, +// and open dispute on remote chain. +// +//nolint:cyclop,gocognit +func (g Guard) updateAgentStatus(ctx context.Context, chainID uint32) error { + eligibleAgentTrees, err := g.guardDB.GetRelayableAgentStatuses(ctx, chainID) + if err != nil { + return fmt.Errorf("could not get update agent status parameters: %w", err) + } + + if len(eligibleAgentTrees) == 0 { + return nil + } + + var localRoot [32]byte + contractCall := func(ctx context.Context) error { + localRoot, err = g.domains[chainID].LightManager().GetAgentRoot(ctx) + if err != nil { + return fmt.Errorf("could not get agent root: %w", err) + } + + return nil + } + err = retry.WithBackoff(ctx, contractCall, g.retryConfig...) + if err != nil { + return fmt.Errorf("could not get agent root: %w", err) + } + + localRootBlockNumber, err := g.guardDB.GetSummitBlockNumberForRoot(ctx, common.BytesToHash(localRoot[:]).String()) + if err != nil { + return fmt.Errorf("could not get block number for local root: %w", err) + } + + // Filter the eligible agent roots by the given block number and call updateAgentStatus(). + for _, t := range eligibleAgentTrees { + tree := t + // Get the first recorded summit block number for the tree agent root. + treeBlockNumber, err := g.guardDB.GetSummitBlockNumberForRoot(ctx, tree.AgentRoot) + if err != nil { + return fmt.Errorf("could not get block number for local root: %w", err) + } + //nolint:nestif + if localRootBlockNumber >= treeBlockNumber { + logger.Infof("Relaying agent status for agent %s on chain %d", tree.AgentAddress.String(), chainID) + // Fetch the agent status to be relayed from Summit. + var agentStatus types.AgentStatus + contractCall := func(ctx context.Context) error { + agentStatus, err = g.domains[g.summitDomainID].BondingManager().GetAgentStatus(ctx, tree.AgentAddress) + if err != nil { + return fmt.Errorf("could not get agent status: %w", err) + } + + return nil + } + err = retry.WithBackoff(ctx, contractCall, g.retryConfig...) + if err != nil { + return fmt.Errorf("could not get agent status: %w", err) + } + + if agentStatus.Domain() != chainID { + continue + } + + // Update agent status on remote. + _, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(chainID)), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { + tx, err = g.domains[chainID].LightManager().UpdateAgentStatus( + transactor, + tree.AgentAddress, + agentStatus, + tree.Proof, + ) + if err != nil { + return nil, fmt.Errorf("could not submit UpdateAgentStatus tx: %w", err) + } + logger.Infof("Updated agent status on chain %d for agent %s: %s [hash: %s]", chainID, tree.AgentAddress.String(), agentStatus.Flag().String(), tx.Hash()) + return + }) + if err != nil { + return fmt.Errorf("could not submit UpdateAgentStatus tx: %w", err) + } + + // Mark the relayable status as Relayed. + err = g.guardDB.UpdateAgentStatusRelayedState( + ctx, + tree.AgentAddress, + types.Relayed, + ) + if err != nil { + return fmt.Errorf("could not update agent status relayed state: %w", err) + } + } + } + + return nil +} diff --git a/agents/agents/guard/fraud_test.go b/agents/agents/guard/fraud_test.go new file mode 100644 index 0000000000..2e5881ee49 --- /dev/null +++ b/agents/agents/guard/fraud_test.go @@ -0,0 +1,1140 @@ +package guard_test + +import ( + "fmt" + "math/big" + "time" + + "github.com/brianvoe/gofakeit/v6" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + + "github.com/Flaque/filet" + "github.com/ethereum/go-ethereum/common" + . "github.com/stretchr/testify/assert" + "github.com/synapsecns/sanguine/agents/agents/executor" + "github.com/synapsecns/sanguine/agents/agents/executor/db" + "github.com/synapsecns/sanguine/agents/agents/guard" + "github.com/synapsecns/sanguine/agents/config" + execConfig "github.com/synapsecns/sanguine/agents/config/executor" + "github.com/synapsecns/sanguine/agents/domains" + "github.com/synapsecns/sanguine/agents/testutil/agentstestcontract" + "github.com/synapsecns/sanguine/agents/types" + "github.com/synapsecns/sanguine/ethergo/backends" + "github.com/synapsecns/sanguine/ethergo/backends/anvil" + signerConfig "github.com/synapsecns/sanguine/ethergo/signer/config" + "github.com/synapsecns/sanguine/ethergo/signer/signer" + submitterConfig "github.com/synapsecns/sanguine/ethergo/submitter/config" + omniClient "github.com/synapsecns/sanguine/services/omnirpc/client" + "github.com/synapsecns/sanguine/services/scribe/backend" + "github.com/synapsecns/sanguine/services/scribe/client" + scribeConfig "github.com/synapsecns/sanguine/services/scribe/config" + "github.com/synapsecns/sanguine/services/scribe/service" +) + +func (g GuardSuite) getTestGuard(scribeConfig scribeConfig.Config) (testGuard *guard.Guard, sclient client.ScribeClient, err error) { + testConfig := config.AgentConfig{ + Domains: map[string]config.DomainConfig{ + "origin_client": g.OriginDomainClient.Config(), + "destination_client": g.DestinationDomainClient.Config(), + "summit_client": g.SummitDomainClient.Config(), + }, + DomainID: uint32(0), + SummitDomainID: g.SummitDomainClient.Config().DomainID, + BondedSigner: signerConfig.SignerConfig{ + Type: signerConfig.FileType.String(), + File: filet.TmpFile(g.T(), "", g.GuardBondedWallet.PrivateKeyHex()).Name(), + }, + UnbondedSigner: signerConfig.SignerConfig{ + Type: signerConfig.FileType.String(), + File: filet.TmpFile(g.T(), "", g.GuardUnbondedWallet.PrivateKeyHex()).Name(), + }, + RefreshIntervalSeconds: 5, + MaxRetrySeconds: 60, + } + + // Scribe setup. + omniRPCClient := omniClient.NewOmnirpcClient(g.TestOmniRPC, g.GuardMetrics, omniClient.WithCaptureReqRes()) + originClient, err := backend.DialBackend(g.GetTestContext(), g.TestBackendOrigin.RPCAddress(), g.ScribeMetrics) + Nil(g.T(), err) + destinationClient, err := backend.DialBackend(g.GetTestContext(), g.TestBackendDestination.RPCAddress(), g.ScribeMetrics) + Nil(g.T(), err) + summitClient, err := backend.DialBackend(g.GetTestContext(), g.TestBackendSummit.RPCAddress(), g.ScribeMetrics) + Nil(g.T(), err) + + clients := map[uint32][]backend.ScribeBackend{ + uint32(g.TestBackendOrigin.GetChainID()): {originClient, originClient}, + uint32(g.TestBackendDestination.GetChainID()): {destinationClient, destinationClient}, + uint32(g.TestBackendSummit.GetChainID()): {summitClient, summitClient}, + } + scribe, err := service.NewScribe(g.ScribeTestDB, clients, scribeConfig, g.ScribeMetrics) + Nil(g.T(), err) + scribeClient := client.NewEmbeddedScribe("sqlite", g.DBPath, g.ScribeMetrics) + + //nolint:errcheck + go scribeClient.Start(g.GetTestContext()) + //nolint:errcheck + go scribe.Start(g.GetTestContext()) + //nolint:wrapcheck + testGuard, err = guard.NewGuard(g.GetTestContext(), testConfig, omniRPCClient, scribeClient.ScribeClient, g.GuardTestDB, g.GuardMetrics) + sclient = scribeClient.ScribeClient + if err != nil { + return nil, sclient, fmt.Errorf("could not create guard: %w", err) + } + if testGuard == nil { + return nil, sclient, fmt.Errorf("guard is nil") + } + + return testGuard, sclient, nil +} + +func (g GuardSuite) bumpBackends() { + txContextSummit := g.TestBackendSummit.GetTxContext(g.GetTestContext(), g.SummitMetadata.OwnerPtr()) + txContextOrigin := g.TestBackendOrigin.GetTxContext(g.GetTestContext(), g.OriginContractMetadata.OwnerPtr()) + txContextDestination := g.TestBackendDestination.GetTxContext(g.GetTestContext(), g.DestinationContractMetadata.OwnerPtr()) + g.bumpBackend(g.TestBackendSummit, g.TestContractOnSummit, txContextSummit.TransactOpts) + g.bumpBackend(g.TestBackendOrigin, g.TestContractOnOrigin, txContextOrigin.TransactOpts) + g.bumpBackend(g.TestBackendDestination, g.TestContractOnDestination, txContextDestination.TransactOpts) +} + +// Helper to get the test backend to emit expected events. +func (g GuardSuite) bumpBackend(backend backends.SimulatedTestBackend, contract *agentstestcontract.AgentsTestContractRef, txOpts *bind.TransactOpts) { + bumpTx, err := contract.EmitAgentsEventA(txOpts, big.NewInt(gofakeit.Int64()), big.NewInt(gofakeit.Int64()), big.NewInt(gofakeit.Int64())) + Nil(g.T(), err) + backend.WaitForConfirmation(g.GetTestContext(), bumpTx) +} + +func (g GuardSuite) updateAgentStatus(lightManager domains.LightManagerContract, bondedSigner, unbondedSigner signer.Signer, chainID uint32) { + agentStatus, err := g.SummitDomainClient.BondingManager().GetAgentStatus(g.GetTestContext(), bondedSigner.Address()) + Nil(g.T(), err) + agentProof, err := g.SummitDomainClient.BondingManager().GetProof(g.GetTestContext(), bondedSigner.Address()) + Nil(g.T(), err) + transactor, err := unbondedSigner.GetTransactor(g.GetTestContext(), big.NewInt(int64(chainID))) + Nil(g.T(), err) + _, err = lightManager.UpdateAgentStatus( + transactor, + bondedSigner.Address(), + agentStatus, + agentProof, + ) + Nil(g.T(), err) + g.bumpBackends() +} + +// TODO: Add a test for exiting the report logic early when the snapshot submitter is a guard. +func (g GuardSuite) TestFraudulentStateInSnapshot() { + testDone := false + defer func() { + testDone = true + }() + + originConfig := scribeConfig.ContractConfig{ + Address: g.OriginContract.Address().String(), + StartBlock: 0, + } + originChainConfig := scribeConfig.ChainConfig{ + ChainID: uint32(g.TestBackendOrigin.GetChainID()), + Confirmations: 1, + Contracts: []scribeConfig.ContractConfig{originConfig}, + } + destinationConfig := scribeConfig.ContractConfig{ + Address: g.LightInboxOnDestination.Address().String(), + StartBlock: 0, + } + destinationChainConfig := scribeConfig.ChainConfig{ + ChainID: uint32(g.TestBackendDestination.GetChainID()), + Confirmations: 1, + Contracts: []scribeConfig.ContractConfig{destinationConfig}, + } + summitConfig := scribeConfig.ContractConfig{ + Address: g.InboxOnSummit.Address().String(), + StartBlock: 0, + } + summitChainConfig := scribeConfig.ChainConfig{ + ChainID: uint32(g.TestBackendSummit.GetChainID()), + Confirmations: 1, + Contracts: []scribeConfig.ContractConfig{summitConfig}, + } + scribeConfig := scribeConfig.Config{ + Chains: []scribeConfig.ChainConfig{originChainConfig, destinationChainConfig, summitChainConfig}, + } + + // Start a new Guard. + guard, _, err := g.getTestGuard(scribeConfig) + Nil(g.T(), err) + go func() { + guardErr := guard.Start(g.GetTestContext()) + if !testDone { + Nil(g.T(), guardErr) + } + }() + + // Update the agent status on Origin. + g.updateAgentStatus(g.OriginDomainClient.LightManager(), g.NotaryBondedSigner, g.NotaryUnbondedSigner, uint32(g.TestBackendOrigin.GetChainID())) + + // Verify that the agent is marked as Active + status, err := g.OriginDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address()) + Equal(g.T(), status.Flag(), types.AgentFlagActive) + Nil(g.T(), err) + + // Store agent trees and roots so that the agent status can be updated by the guard. + agentRoot, err := g.SummitDomainClient.BondingManager().GetAgentRoot(g.GetTestContext()) + Nil(g.T(), err) + blockNumber, err := g.SummitDomainClient.BlockNumber(g.GetTestContext()) + Nil(g.T(), err) + notaryProof, err := g.SummitDomainClient.BondingManager().GetProof(g.GetTestContext(), g.NotaryBondedSigner.Address()) + Nil(g.T(), err) + err = g.GuardTestDB.StoreAgentTree( + g.GetTestContext(), + agentRoot, + g.NotaryBondedSigner.Address(), + uint64(blockNumber), + notaryProof, + ) + Nil(g.T(), err) + err = g.GuardTestDB.StoreAgentRoot(g.GetTestContext(), agentRoot, uint64(blockNumber)) + Nil(g.T(), err) + + // Before submitting the attestation, ensure that there are no disputes opened. + err = g.DestinationDomainClient.LightManager().GetDispute(g.GetTestContext(), big.NewInt(0)) + NotNil(g.T(), err) + + // Create a fraudulent snapshot + getState := func(nonce uint32) types.State { + gasData := types.NewGasData(gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16()) + state := types.NewState( + common.BigToHash(big.NewInt(gofakeit.Int64())), + g.OriginDomainClient.Config().DomainID, + nonce, + big.NewInt(int64(gofakeit.Int32())), + big.NewInt(int64(gofakeit.Int32())), + gasData, + ) + + return state + } + fraudulentSnapshot := types.NewSnapshot([]types.State{getState(1), getState(2)}) + + // Submit the snapshot with a guard then notary + guardSnapshotSignature, encodedSnapshot, _, err := fraudulentSnapshot.SignSnapshot(g.GetTestContext(), g.GuardBondedSigner) + Nil(g.T(), err) + txContextSummit := g.TestBackendSummit.GetTxContext(g.GetTestContext(), g.InboxMetadataOnSummit.OwnerPtr()) + tx, err := g.SummitDomainClient.Inbox().SubmitSnapshot(txContextSummit.TransactOpts, encodedSnapshot, guardSnapshotSignature) + Nil(g.T(), err) + NotNil(g.T(), tx) + g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx) + + notarySnapshotSignature, encodedSnapshot, _, err := fraudulentSnapshot.SignSnapshot(g.GetTestContext(), g.NotaryBondedSigner) + Nil(g.T(), err) + tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot(txContextSummit.TransactOpts, encodedSnapshot, notarySnapshotSignature) + Nil(g.T(), err) + NotNil(g.T(), tx) + g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx) + + // Verify that the guard eventually marks the accused agent as Fraudulent on Origin + g.Eventually(func() bool { + status, err := g.OriginDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address()) + Nil(g.T(), err) + + if status.Flag() == types.AgentFlagFraudulent { + return true + } + + g.bumpBackends() + return false + }) + + // Verify that a report has been submitted by the Guard by checking that a Dispute is now open. + g.Eventually(func() bool { + err = g.SummitDomainClient.BondingManager().GetDispute(g.GetTestContext(), big.NewInt(0)) + return err == nil + }) + + // Verify that a state report was submitted on summit. + fraudulentState := fraudulentSnapshot.States()[0] + g.verifyStateReport(g.InboxOnSummit, 1, fraudulentState) + + // Verify that a state report was submitted on destination. + g.verifyStateReport(g.LightInboxOnDestination, 1, fraudulentState) +} + +func (g GuardSuite) TestFraudulentAttestationOnDestination() { + testDone := false + defer func() { + testDone = true + }() + + originConfig := scribeConfig.ContractConfig{ + Address: g.OriginContract.Address().String(), + StartBlock: 0, + } + originChainConfig := scribeConfig.ChainConfig{ + ChainID: uint32(g.TestBackendOrigin.GetChainID()), + Confirmations: 1, + Contracts: []scribeConfig.ContractConfig{originConfig}, + } + destinationConfig := scribeConfig.ContractConfig{ + Address: g.LightInboxOnDestination.Address().String(), + StartBlock: 0, + } + destinationChainConfig := scribeConfig.ChainConfig{ + ChainID: uint32(g.TestBackendDestination.GetChainID()), + Confirmations: 1, + Contracts: []scribeConfig.ContractConfig{destinationConfig}, + } + summitConfig := scribeConfig.ContractConfig{ + Address: g.InboxOnSummit.Address().String(), + StartBlock: 0, + } + bondingManagerConfig := scribeConfig.ContractConfig{ + Address: g.BondingManagerOnSummit.Address().String(), + StartBlock: 0, + } + summitChainConfig := scribeConfig.ChainConfig{ + ChainID: uint32(g.TestBackendSummit.GetChainID()), + Confirmations: 1, + Contracts: []scribeConfig.ContractConfig{summitConfig, bondingManagerConfig}, + } + scribeConfig := scribeConfig.Config{ + Chains: []scribeConfig.ChainConfig{originChainConfig, destinationChainConfig, summitChainConfig}, + } + + // Start a new Guard. + guard, _, err := g.getTestGuard(scribeConfig) + Nil(g.T(), err) + go func() { + guardErr := guard.Start(g.GetTestContext()) + if !testDone { + Nil(g.T(), guardErr) + } + }() + + _, gasDataContract := g.TestDeployManager.GetGasDataHarness(g.GetTestContext(), g.TestBackendDestination) + _, attestationContract := g.TestDeployManager.GetAttestationHarness(g.GetTestContext(), g.TestBackendDestination) + + // Verify that the agent is marked as Active + txContextDest := g.TestBackendDestination.GetTxContext(g.GetTestContext(), g.DestinationContractMetadata.OwnerPtr()) + status, err := g.OriginDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.GuardBondedSigner.Address()) + Equal(g.T(), status.Flag(), types.AgentFlagActive) + Nil(g.T(), err) + + agentRoot := common.BigToHash(big.NewInt(gofakeit.Int64())) + gasData := types.NewGasData(gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16()) + chainGas := types.NewChainGas(gasData, uint32(g.TestBackendOrigin.GetChainID())) + chainGasBytes, err := types.EncodeChainGas(chainGas) + Nil(g.T(), err) + + // Build and sign a fraudulent attestation + // TODO: Change from using a harness to using the Go code. + snapGas := []*big.Int{new(big.Int).SetBytes(chainGasBytes)} + snapGasHash, err := gasDataContract.SnapGasHash(&bind.CallOpts{Context: g.GetTestContext()}, snapGas) + Nil(g.T(), err) + dataHash, err := attestationContract.DataHash(&bind.CallOpts{Context: g.GetTestContext()}, agentRoot, snapGasHash) + Nil(g.T(), err) + fraudAttestation := types.NewAttestation( + common.BigToHash(big.NewInt(int64(gofakeit.Int32()))), + dataHash, + 1, + big.NewInt(int64(gofakeit.Int32())), + big.NewInt(int64(gofakeit.Int32())), + ) + attSignature, attEncoded, _, err := fraudAttestation.SignAttestation(g.GetTestContext(), g.NotaryBondedSigner, true) + Nil(g.T(), err) + + // Before submitting the attestation, ensure that there are no disputes opened. + err = g.DestinationDomainClient.LightManager().GetDispute(g.GetTestContext(), big.NewInt(0)) + NotNil(g.T(), err) + + // Update the agent status of the Guard and Notary. + g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.GuardBondedSigner, g.GuardUnbondedSigner, uint32(g.TestBackendDestination.GetChainID())) + g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.NotaryBondedSigner, g.NotaryUnbondedSigner, uint32(g.TestBackendDestination.GetChainID())) + + // Submit the attestation + tx, err := g.DestinationDomainClient.LightInbox().SubmitAttestation( + txContextDest.TransactOpts, + attEncoded, + attSignature, + agentRoot, + snapGas, + ) + Nil(g.T(), err) + NotNil(g.T(), tx) + g.TestBackendDestination.WaitForConfirmation(g.GetTestContext(), tx) + + // Verify that the guard eventually marks the accused agent as Slashed + g.Eventually(func() bool { + status, err := g.SummitDomainClient.BondingManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address()) + Nil(g.T(), err) + if status.Flag() == types.AgentFlagSlashed { + return true + } + + // Make sure that scribe keeps producing new blocks + g.bumpBackends() + return false + }) + + // Verify that a report has been submitted by the Guard by checking that a Dispute is now open. + g.Eventually(func() bool { + err := g.DestinationDomainClient.LightManager().GetDispute(g.GetTestContext(), big.NewInt(0)) + return err == nil + }) +} + +func (g GuardSuite) TestReportFraudulentStateInAttestation() { + testDone := false + defer func() { + testDone = true + }() + + // This scribe config omits the Summit and Origin chains, since we do not want to pick up the fraud coming from the + // fraudulent snapshots, only from the Attestation submitted on the Destination that is associated with a fraudulent + // snapshot. + destinationConfig := scribeConfig.ContractConfig{ + Address: g.LightInboxOnDestination.Address().String(), + StartBlock: 0, + } + destinationChainConfig := scribeConfig.ChainConfig{ + ChainID: uint32(g.TestBackendDestination.GetChainID()), + Confirmations: 1, + Contracts: []scribeConfig.ContractConfig{destinationConfig}, + } + inboxConfig := scribeConfig.ContractConfig{ + Address: g.InboxOnSummit.Address().String(), + StartBlock: 0, + } + bondingManagerConfig := scribeConfig.ContractConfig{ + Address: g.BondingManagerOnSummit.Address().String(), + StartBlock: 0, + } + summitChainConfig := scribeConfig.ChainConfig{ + ChainID: uint32(g.TestBackendSummit.GetChainID()), + Confirmations: 1, + Contracts: []scribeConfig.ContractConfig{inboxConfig, bondingManagerConfig}, + } + scribeConfig := scribeConfig.Config{ + Chains: []scribeConfig.ChainConfig{destinationChainConfig, summitChainConfig}, + } + + // Start a new Guard. + guard, _, err := g.getTestGuard(scribeConfig) + Nil(g.T(), err) + go func() { + guardErr := guard.Start(g.GetTestContext()) + if !testDone { + Nil(g.T(), guardErr) + } + }() + + // Verify that the agent is marked as Active + txContextDest := g.TestBackendDestination.GetTxContext(g.GetTestContext(), g.DestinationContractMetadata.OwnerPtr()) + status, err := g.OriginDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.GuardBondedSigner.Address()) + Equal(g.T(), status.Flag(), types.AgentFlagActive) + Nil(g.T(), err) + + // Create a fraudulent snapshot + gasData := types.NewGasData(gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16()) + fraudulentState := types.NewState( + common.BigToHash(big.NewInt(gofakeit.Int64())), + g.OriginDomainClient.Config().DomainID, + 1, + big.NewInt(int64(gofakeit.Int32())), + big.NewInt(int64(gofakeit.Int32())), + gasData, + ) + fraudulentSnapshot := types.NewSnapshot([]types.State{fraudulentState}) + + // Before submitting the attestation, ensure that there are no disputes opened. + err = g.DestinationDomainClient.LightManager().GetDispute(g.GetTestContext(), big.NewInt(0)) + NotNil(g.T(), err) + + // Update the agent status of the Guard and Notary. + g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.GuardBondedSigner, g.GuardUnbondedSigner, uint32(g.TestBackendDestination.GetChainID())) + g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.NotaryBondedSigner, g.NotaryUnbondedSigner, uint32(g.TestBackendDestination.GetChainID())) + g.updateAgentStatus(g.OriginDomainClient.LightManager(), g.NotaryBondedSigner, g.NotaryUnbondedSigner, uint32(g.TestBackendOrigin.GetChainID())) + + // Submit the snapshot with a guard + guardSnapshotSignature, encodedSnapshot, _, err := fraudulentSnapshot.SignSnapshot(g.GetTestContext(), g.GuardBondedSigner) + Nil(g.T(), err) + transactOptsSummit := g.TestBackendSummit.GetTxContext(g.GetTestContext(), g.InboxMetadataOnSummit.OwnerPtr()) + tx, err := g.SummitDomainClient.Inbox().SubmitSnapshot(transactOptsSummit.TransactOpts, encodedSnapshot, guardSnapshotSignature) + Nil(g.T(), err) + NotNil(g.T(), tx) + g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx) + + // Submit the snapshot with a notary + notarySnapshotSignature, encodedSnapshot, _, err := fraudulentSnapshot.SignSnapshot(g.GetTestContext(), g.NotaryBondedSigner) + Nil(g.T(), err) + tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot(transactOptsSummit.TransactOpts, encodedSnapshot, notarySnapshotSignature) + Nil(g.T(), err) + NotNil(g.T(), tx) + g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx) + + // Submit the attestation + notaryAttestation, err := g.SummitDomainClient.Summit().GetAttestation(g.GetTestContext(), 1) + Nil(g.T(), err) + attSignature, attEncoded, _, err := notaryAttestation.Attestation().SignAttestation(g.GetTestContext(), g.NotaryBondedSigner, true) + Nil(g.T(), err) + tx, err = g.DestinationDomainClient.LightInbox().SubmitAttestation( + txContextDest.TransactOpts, + attEncoded, + attSignature, + notaryAttestation.AgentRoot(), + notaryAttestation.SnapGas(), + ) + Nil(g.T(), err) + NotNil(g.T(), tx) + g.TestBackendDestination.WaitForConfirmation(g.GetTestContext(), tx) + + // Verify that the guard eventually marks the accused agent as Fraudulent + g.Eventually(func() bool { + status, err := g.OriginDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address()) + Nil(g.T(), err) + if status.Flag() == types.AgentFlagFraudulent { + return true + } + + g.bumpBackends() + return false + }) + + // Verify that a dispute is now open on summit. + g.Eventually(func() bool { + err := g.SummitDomainClient.BondingManager().GetDispute(g.GetTestContext(), big.NewInt(0)) + return err == nil + }) + + // Verify that a state report was submitted on summit. + g.verifyStateReport(g.InboxOnSummit, 1, fraudulentState) + + // Verify that a state report was submitted on destination. + g.verifyStateReport(g.LightInboxOnDestination, 1, fraudulentState) +} + +type statementInboxContract interface { + GetReportsAmount(opts *bind.CallOpts) (*big.Int, error) + GetGuardReport(opts *bind.CallOpts, index *big.Int) (struct { + StatementPayload []byte + ReportSignature []byte + }, error) +} + +// Verify that a state report was submitted on the given contract. +// +//nolint:unparam +func (g GuardSuite) verifyStateReport(contract statementInboxContract, expectedNumReports int64, expectedState types.State) { + g.Eventually(func() bool { + numReports, err := contract.GetReportsAmount(&bind.CallOpts{Context: g.GetTestContext()}) + Nil(g.T(), err) + + if numReports.Int64() < expectedNumReports { + return false + } + if numReports.Int64() != expectedNumReports { + g.T().Fatalf("too many reports; expected %d, got %v", expectedNumReports, numReports.Int64()) + } + + stateReportIdx := big.NewInt(numReports.Int64() - 1) + stateReport, err := contract.GetGuardReport(&bind.CallOpts{Context: g.GetTestContext()}, stateReportIdx) + Nil(g.T(), err) + + expected, err := expectedState.Encode() + Nil(g.T(), err) + return Equal(g.T(), stateReport.StatementPayload, expected) + }) +} + +func (g GuardSuite) TestInvalidReceipt() { + testDone := false + defer func() { + testDone = true + }() + + originConfig := scribeConfig.ContractConfig{ + Address: g.OriginContract.Address().String(), + StartBlock: 0, + } + originChainConfig := scribeConfig.ChainConfig{ + ChainID: uint32(g.TestBackendOrigin.GetChainID()), + Confirmations: 1, + Contracts: []scribeConfig.ContractConfig{originConfig}, + } + destinationConfig := scribeConfig.ContractConfig{ + Address: g.LightInboxOnDestination.Address().String(), + StartBlock: 0, + } + destinationChainConfig := scribeConfig.ChainConfig{ + ChainID: uint32(g.TestBackendDestination.GetChainID()), + Confirmations: 1, + Contracts: []scribeConfig.ContractConfig{destinationConfig}, + } + summitConfig := scribeConfig.ContractConfig{ + Address: g.InboxOnSummit.Address().String(), + StartBlock: 0, + } + bondingManagerConfig := scribeConfig.ContractConfig{ + Address: g.BondingManagerOnSummit.Address().String(), + StartBlock: 0, + } + summitChainConfig := scribeConfig.ChainConfig{ + ChainID: uint32(g.TestBackendSummit.GetChainID()), + Confirmations: 1, + Contracts: []scribeConfig.ContractConfig{summitConfig, bondingManagerConfig}, + } + scribeConfig := scribeConfig.Config{ + Chains: []scribeConfig.ChainConfig{originChainConfig, destinationChainConfig, summitChainConfig}, + } + + // Start a new Guard. + guard, _, err := g.getTestGuard(scribeConfig) + Nil(g.T(), err) + go func() { + guardErr := guard.Start(g.GetTestContext()) + if !testDone { + Nil(g.T(), guardErr) + } + }() + + // Send a message on Origin for it to be included in a valid state. + summitTip := big.NewInt(int64(gofakeit.Uint32())) + attestationTip := big.NewInt(int64(gofakeit.Uint32())) + executorTip := big.NewInt(int64(gofakeit.Uint32())) + deliveryTip := big.NewInt(int64(gofakeit.Uint32())) + tips := types.NewTips(summitTip, attestationTip, executorTip, deliveryTip) + optimisticSeconds := uint32(1) + recipientDestination := g.TestClientMetadataOnDestination.Address().Hash() + nonce := uint32(1) + body := []byte{byte(gofakeit.Uint32())} + txContextOrigin := g.TestBackendOrigin.GetTxContext(g.GetTestContext(), g.OriginContractMetadata.OwnerPtr()) + txContextOrigin.Value = types.TotalTips(tips) + paddedRequest := big.NewInt(0) + + msgSender := common.BytesToHash(txContextOrigin.TransactOpts.From.Bytes()) + header := types.NewHeader(types.MessageFlagBase, uint32(g.TestBackendOrigin.GetChainID()), nonce, uint32(g.TestBackendDestination.GetChainID()), optimisticSeconds) + msgRequest := types.NewRequest(uint32(0), uint64(0), big.NewInt(0)) + baseMessage := types.NewBaseMessage(msgSender, recipientDestination, tips, msgRequest, body) + message, err := types.NewMessageFromBaseMessage(header, baseMessage) + Nil(g.T(), err) + + tx, err := g.OriginContract.SendBaseMessage( + txContextOrigin.TransactOpts, + uint32(g.TestBackendDestination.GetChainID()), + recipientDestination, + optimisticSeconds, + paddedRequest, + body, + ) + Nil(g.T(), err) + g.TestBackendOrigin.WaitForConfirmation(g.GetTestContext(), tx) + + // Submit the snapshot with a guard + latestOriginState, err := g.OriginDomainClient.Origin().SuggestLatestState(g.GetTestContext()) + Nil(g.T(), err) + snapshot := types.NewSnapshot([]types.State{latestOriginState}) + guardSnapshotSignature, encodedSnapshot, _, err := snapshot.SignSnapshot(g.GetTestContext(), g.GuardBondedSigner) + Nil(g.T(), err) + txContextSummit := g.TestBackendSummit.GetTxContext(g.GetTestContext(), g.InboxMetadataOnSummit.OwnerPtr()) + tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot(txContextSummit.TransactOpts, encodedSnapshot, guardSnapshotSignature) + Nil(g.T(), err) + NotNil(g.T(), tx) + g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx) + + // Submit the snapshot with a notary + notarySnapshotSignature, encodedSnapshot, _, err := snapshot.SignSnapshot(g.GetTestContext(), g.NotaryBondedSigner) + Nil(g.T(), err) + tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot(txContextSummit.TransactOpts, encodedSnapshot, notarySnapshotSignature) + Nil(g.T(), err) + NotNil(g.T(), tx) + g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx) + + // Build and sign a receipt + snapshotRoot, _, err := snapshot.SnapshotRootAndProofs() + Nil(g.T(), err) + g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.NotaryBondedSigner, g.NotaryUnbondedSigner, uint32(g.TestBackendDestination.GetChainID())) + messageHash, err := message.ToLeaf() + Nil(g.T(), err) + receipt := types.NewReceipt( + g.OriginDomainClient.Config().DomainID, + g.DestinationDomainClient.Config().DomainID, + messageHash, + snapshotRoot, + 0, + g.NotaryBondedWallet.Address(), + common.BigToAddress(big.NewInt(gofakeit.Int64())), + common.BigToAddress(big.NewInt(gofakeit.Int64())), + ) + rcptSignature, rcptPayload, _, err := receipt.SignReceipt(g.GetTestContext(), g.NotaryBondedSigner, true) + Nil(g.T(), err) + + // Submit the receipt + bodyHash, err := baseMessage.BodyLeaf() + Nil(g.T(), err) + var bodyHashB32 [32]byte + copy(bodyHashB32[:], bodyHash) + headerHash, err := header.Leaf() + Nil(g.T(), err) + paddedTips, err := types.EncodeTipsBigInt(tips) + Nil(g.T(), err) + + txContextSummit = g.TestBackendSummit.GetTxContext(g.GetTestContext(), g.SummitMetadata.OwnerPtr()) + tx, err = g.SummitDomainClient.Inbox().SubmitReceipt( + txContextSummit.TransactOpts, + rcptPayload, + rcptSignature, + paddedTips, + headerHash, + bodyHashB32, + ) + Nil(g.T(), err) + NotNil(g.T(), tx) + g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx) + + // Verify that the guard eventually marks the accused agent as Fraudulent + g.Eventually(func() bool { + status, err := g.DestinationDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address()) + Nil(g.T(), err) + if status.Flag() == types.AgentFlagFraudulent { + return true + } + + g.bumpBackends() + return false + }) + + // TODO: Uncomment once updating agent status is implemented. + // g.Eventually(func() bool { + // status, err := g.SummitDomainClient.BondingManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address()) + // Nil(g.T(), err) + // if status.Flag() == types.AgentFlagSlashed { + // return true + // } + + // g.bumpBackends() + // return false + // }) + + // Verify that a report has been submitted by the Guard by checking that a Dispute is now open. + g.Eventually(func() bool { + err := g.SummitDomainClient.BondingManager().GetDispute(g.GetTestContext(), big.NewInt(0)) + return err == nil + }) +} + +//nolint:maintidx,cyclop +func (g GuardSuite) TestUpdateAgentStatusOnRemote() { + testDone := false + defer func() { + testDone = true + }() + + // This scribe config omits the Summit and Origin chains, since we do not want to pick up the fraud coming from the + // fraudulent snapshots, only from the Attestation submitted on the Destination that is associated with a fraudulent + // snapshot. + originConfig := scribeConfig.ContractConfig{ + Address: g.OriginContract.Address().String(), + StartBlock: 0, + } + lightManagerConfig := scribeConfig.ContractConfig{ + Address: g.LightManagerOnOrigin.Address().String(), + StartBlock: 0, + } + originChainConfig := scribeConfig.ChainConfig{ + ChainID: uint32(g.TestBackendOrigin.GetChainID()), + GetLogsBatchAmount: 1, + StoreConcurrency: 1, + GetLogsRange: 1, + Confirmations: 1, + Contracts: []scribeConfig.ContractConfig{originConfig, lightManagerConfig}, + } + destinationConfig := scribeConfig.ContractConfig{ + Address: g.DestinationContract.Address().String(), + StartBlock: 0, + } + lightInboxDestinationConfig := scribeConfig.ContractConfig{ + Address: g.LightInboxOnDestination.Address().String(), + StartBlock: 0, + } + lightManagerDestinationConfig := scribeConfig.ContractConfig{ + Address: g.LightManagerOnDestination.Address().String(), + StartBlock: 0, + } + destinationChainConfig := scribeConfig.ChainConfig{ + ChainID: uint32(g.TestBackendDestination.GetChainID()), + Confirmations: 1, + Contracts: []scribeConfig.ContractConfig{destinationConfig, lightInboxDestinationConfig, lightManagerDestinationConfig}, + } + summitConfig := scribeConfig.ContractConfig{ + Address: g.SummitContract.Address().String(), + StartBlock: 0, + } + inboxConfig := scribeConfig.ContractConfig{ + Address: g.InboxOnSummit.Address().String(), + StartBlock: 0, + } + bondingManagerConfig := scribeConfig.ContractConfig{ + Address: g.BondingManagerOnSummit.Address().String(), + StartBlock: 0, + } + summitChainConfig := scribeConfig.ChainConfig{ + ChainID: uint32(g.TestBackendSummit.GetChainID()), + Confirmations: 1, + Contracts: []scribeConfig.ContractConfig{summitConfig, inboxConfig, bondingManagerConfig}, + } + scribeConfig := scribeConfig.Config{ + Chains: []scribeConfig.ChainConfig{originChainConfig, destinationChainConfig, summitChainConfig}, + } + + // Start a new Guard. + guard, scribeClient, err := g.getTestGuard(scribeConfig) + Nil(g.T(), err) + go func() { + guardErr := guard.Start(g.GetTestContext()) + if !testDone { + Nil(g.T(), guardErr) + } + }() + + // Scribe setup. + omniRPCClient := omniClient.NewOmnirpcClient(g.TestOmniRPC, g.GuardMetrics, omniClient.WithCaptureReqRes()) + chainID := uint32(g.TestBackendOrigin.GetChainID()) + destination := uint32(g.TestBackendDestination.GetChainID()) + summit := uint32(g.TestBackendSummit.GetChainID()) + + excCfg := execConfig.Config{ + SummitChainID: summit, + SummitAddress: g.SummitContract.Address().String(), + InboxAddress: g.InboxOnSummit.Address().String(), + Chains: []execConfig.ChainConfig{ + { + ChainID: chainID, + OriginAddress: g.OriginContract.Address().String(), + }, + { + ChainID: destination, + LightInboxAddress: g.LightInboxOnDestination.Address().String(), + DestinationAddress: g.DestinationContract.Address().String(), + }, + { + ChainID: summit, + DestinationAddress: g.DestinationContractOnSummit.Address().String(), + }, + }, + BaseOmnirpcURL: g.TestBackendOrigin.RPCAddress(), + UnbondedSigner: signerConfig.SignerConfig{ + Type: signerConfig.FileType.String(), + File: filet.TmpFile(g.T(), "", g.ExecutorUnbondedWallet.PrivateKeyHex()).Name(), + }, + SubmitterConfig: submitterConfig.Config{ + ChainConfig: submitterConfig.ChainConfig{ + GasEstimate: uint64(5000000), + }, + }, + } + + // This function will allow us to override the current time perceived by Executor. + var currentTime *time.Time + nowFunc := func() time.Time { + if currentTime == nil { + return time.Now() + } + return *currentTime + } + + // Start a new Executor. + exec, err := executor.NewExecutor(g.GetTestContext(), excCfg, g.ExecutorTestDB, scribeClient, omniRPCClient, g.ExecutorMetrics) + Nil(g.T(), err) + exec.NowFunc = nowFunc + + go func() { + execErr := exec.Run(g.GetTestContext()) + if !testDone { + Nil(g.T(), execErr) + } + }() + + // Verify that the agent is marked as Active + txContextDest := g.TestBackendDestination.GetTxContext(g.GetTestContext(), g.DestinationContractMetadata.OwnerPtr()) + status, err := g.OriginDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.GuardBondedSigner.Address()) + Equal(g.T(), status.Flag(), types.AgentFlagActive) + Nil(g.T(), err) + + // Create a fraudulent snapshot + gasData := types.NewGasData(gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16()) + fraudulentState := types.NewState( + common.BigToHash(big.NewInt(gofakeit.Int64())), + g.OriginDomainClient.Config().DomainID, + 1, + big.NewInt(int64(gofakeit.Int32())), + big.NewInt(int64(gofakeit.Int32())), + gasData, + ) + fraudulentSnapshot := types.NewSnapshot([]types.State{fraudulentState}) + + // Before submitting the attestation, ensure that there are no disputes opened. + err = g.DestinationDomainClient.LightManager().GetDispute(g.GetTestContext(), big.NewInt(0)) + NotNil(g.T(), err) + + // Update the agent status of the Guard and Notaries. + g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.GuardBondedSigner, g.GuardUnbondedSigner, destination) + g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.NotaryBondedSigner, g.NotaryUnbondedSigner, destination) + g.updateAgentStatus(g.OriginDomainClient.LightManager(), g.NotaryBondedSigner, g.NotaryUnbondedSigner, uint32(g.TestBackendOrigin.GetChainID())) + g.updateAgentStatus(g.DestinationDomainClient.LightManager(), g.NotaryOnDestinationBondedSigner, g.NotaryOnDestinationUnbondedSigner, destination) + g.updateAgentStatus(g.OriginDomainClient.LightManager(), g.NotaryOnDestinationBondedSigner, g.NotaryOnDestinationUnbondedSigner, uint32(g.TestBackendOrigin.GetChainID())) + + // Submit the snapshot with a guard + guardSnapshotSignature, encodedSnapshot, _, err := fraudulentSnapshot.SignSnapshot(g.GetTestContext(), g.GuardBondedSigner) + Nil(g.T(), err) + txContextSummit := g.TestBackendSummit.GetTxContext(g.GetTestContext(), g.InboxMetadataOnSummit.OwnerPtr()) + tx, err := g.SummitDomainClient.Inbox().SubmitSnapshot(txContextSummit.TransactOpts, encodedSnapshot, guardSnapshotSignature) + Nil(g.T(), err) + NotNil(g.T(), tx) + g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx) + g.bumpBackends() + + // Submit the snapshot with a notary + notarySnapshotSignature, encodedSnapshot, _, err := fraudulentSnapshot.SignSnapshot(g.GetTestContext(), g.NotaryBondedSigner) + Nil(g.T(), err) + tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot(txContextSummit.TransactOpts, encodedSnapshot, notarySnapshotSignature) + Nil(g.T(), err) + NotNil(g.T(), tx) + g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx) + g.bumpBackends() + + // Submit the attestation + notaryAttestation, err := g.SummitDomainClient.Summit().GetAttestation(g.GetTestContext(), 1) + Nil(g.T(), err) + attSignature, attEncoded, _, err := notaryAttestation.Attestation().SignAttestation(g.GetTestContext(), g.NotaryBondedSigner, true) + Nil(g.T(), err) + tx, err = g.DestinationDomainClient.LightInbox().SubmitAttestation( + txContextDest.TransactOpts, + attEncoded, + attSignature, + notaryAttestation.AgentRoot(), + notaryAttestation.SnapGas(), + ) + Nil(g.T(), err) + NotNil(g.T(), tx) + g.TestBackendDestination.WaitForConfirmation(g.GetTestContext(), tx) + g.bumpBackends() + + // Verify that the guard eventually marks the accused agent as Fraudulent + g.Eventually(func() bool { + status, err := g.OriginDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address()) + Nil(g.T(), err) + if status.Flag() == types.AgentFlagFraudulent { + return true + } + + g.bumpBackends() + return false + }) + + // Verify that a report has been submitted by the Guard by checking that a Dispute is now open. + g.Eventually(func() bool { + err := g.SummitDomainClient.BondingManager().GetDispute(g.GetTestContext(), big.NewInt(0)) + return err == nil + }) + + // Get the origin state so we can submit it on the Summit. + originStateRaw, err := g.OriginContract.SuggestLatestState(&bind.CallOpts{Context: g.GetTestContext()}) + g.Nil(err) + originState, err := types.DecodeState(originStateRaw) + g.Nil(err) + snapshot := types.NewSnapshot([]types.State{originState}) + + //nolint:wrapcheck + submitAndVerifySnapshot := func(originState types.State, agentSigner signer.Signer) error { + agentSnapshotSignature, encodedSnapshot, _, err := snapshot.SignSnapshot(g.GetTestContext(), agentSigner) + if err != nil { + return err + } + txContextSummit = g.TestBackendSummit.GetTxContext(g.GetTestContext(), g.SummitMetadata.OwnerPtr()) + tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot( + txContextSummit.TransactOpts, + encodedSnapshot, + agentSnapshotSignature, + ) + if err != nil { + return err + } + g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx) + g.bumpBackends() + + latestStateRaw, err := g.SummitContract.GetLatestAgentState(&bind.CallOpts{Context: g.GetTestContext()}, uint32(g.TestBackendOrigin.GetChainID()), agentSigner.Address()) + if err != nil { + return err + } + latestState, err := types.DecodeState(latestStateRaw) + if err != nil { + return err + } + latestStateHash, err := latestState.Hash() + if err != nil { + return err + } + originStateHash, err := originState.Hash() + if err != nil { + return err + } + latestStateHashHex := common.BytesToHash(latestStateHash[:]) + originStateHashHex := common.BytesToHash(originStateHash[:]) + if latestStateHash != originStateHash { + return fmt.Errorf("latest state hash mismatch; expected %v, got %v", originStateHashHex, latestStateHashHex) + } + return nil + } + + // Submit snapshot with Guard. + g.Eventually(func() bool { + err := submitAndVerifySnapshot(originState, g.GuardBondedSigner) + if err != nil { + fmt.Println(err) + return false + } + return true + }) + time.Sleep(5 * time.Second) + + // Submit snapshot with Notary. + g.Eventually(func() bool { + err := submitAndVerifySnapshot(originState, g.NotaryOnOriginBondedSigner) + if err != nil { + fmt.Println(err) + return false + } + return true + }) + + // Wait for the executor to have attestations before increasing time. + summitChainID := uint32(g.TestBackendSummit.GetChainID()) + attestationNonce := uint32(2) + g.Eventually(func() bool { + attest, err := g.ExecutorTestDB.GetAttestation(g.GetTestContext(), db.DBAttestation{ + Destination: &summitChainID, + AttestationNonce: &attestationNonce, + }) + Nil(g.T(), err) + return attest != nil + }) + + // Increase EVM time to allow agent status to be updated to Slashed on summit. + optimisticPeriodSeconds := int64(86400) + offset := optimisticPeriodSeconds / 2 + increaseEvmTime := func(backend backends.SimulatedTestBackend, seconds int64) { + anvilClient, err := anvil.Dial(g.GetTestContext(), backend.RPCAddress()) + Nil(g.T(), err) + err = anvilClient.IncreaseTime(g.GetTestContext(), seconds) + Nil(g.T(), err) + } + increaseEvmTime(g.TestBackendSummit, optimisticPeriodSeconds+offset) + g.bumpBackends() + + // Increase executor time so that the manager message may be executed. + updatedTime := time.Now().Add(time.Duration(optimisticPeriodSeconds+offset) * time.Second) + currentTime = &updatedTime + + // Verify that the accused agent is eventually Slashed on Summit. + g.Eventually(func() bool { + status, err := g.SummitDomainClient.BondingManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address()) + Nil(g.T(), err) + if status.Flag() == types.AgentFlagSlashed { + return true + } + time.Sleep(5 * time.Second) + g.bumpBackends() + return false + }) + + // Get the origin state so we can submit it on the Summit. + originStateRaw, err = g.OriginContract.SuggestLatestState(&bind.CallOpts{Context: g.GetTestContext()}) + g.Nil(err) + originState, err = types.DecodeState(originStateRaw) + g.Nil(err) + snapshot = types.NewSnapshot([]types.State{originState}) + + // Submit snapshot with Guard. + guardSnapshotSignature, encodedSnapshot, _, err = snapshot.SignSnapshot(g.GetTestContext(), g.GuardBondedSigner) + g.Nil(err) + tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot( + txContextSummit.TransactOpts, + encodedSnapshot, + guardSnapshotSignature, + ) + g.Nil(err) + g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx) + g.bumpBackends() + + // Submit snapshot with Notary. + notarySnapshotSignature, encodedSnapshot, _, err = snapshot.SignSnapshot(g.GetTestContext(), g.NotaryOnOriginBondedSigner) + g.Nil(err) + tx, err = g.SummitDomainClient.Inbox().SubmitSnapshot( + txContextSummit.TransactOpts, + encodedSnapshot, + notarySnapshotSignature, + ) + g.Nil(err) + g.TestBackendSummit.WaitForConfirmation(g.GetTestContext(), tx) + g.bumpBackends() + + // Create a new attestation with the agent root corresponding to newly Slashed status. + latestAgentRoot, err := g.SummitDomainClient.BondingManager().GetAgentRoot(g.GetTestContext()) + Nil(g.T(), err) + _, gasDataContract := g.TestDeployManager.GetGasDataHarness(g.GetTestContext(), g.TestBackendDestination) + _, attestationContract := g.TestDeployManager.GetAttestationHarness(g.GetTestContext(), g.TestBackendDestination) + chainGas := types.NewChainGas(originState.GasData(), uint32(g.TestBackendOrigin.GetChainID())) + chainGasBytes, err := types.EncodeChainGas(chainGas) + Nil(g.T(), err) + // TODO: Change from using a harness to using the Go code. + snapGas := []*big.Int{new(big.Int).SetBytes(chainGasBytes)} + snapGasHash, err := gasDataContract.SnapGasHash(&bind.CallOpts{Context: g.GetTestContext()}, snapGas) + Nil(g.T(), err) + dataHash, err := attestationContract.DataHash(&bind.CallOpts{Context: g.GetTestContext()}, latestAgentRoot, snapGasHash) + Nil(g.T(), err) + notaryAttestation, err = g.SummitDomainClient.Summit().GetAttestation(g.GetTestContext(), 2) + Nil(g.T(), err) + attestation := types.NewAttestation( + notaryAttestation.Attestation().SnapshotRoot(), + dataHash, + 2, + notaryAttestation.Attestation().BlockNumber(), + notaryAttestation.Attestation().Timestamp(), + ) + attEncoded, err = attestation.Encode() + Nil(g.T(), err) + notaryAttestation, err = types.NewNotaryAttestation(attEncoded, latestAgentRoot, snapGas) + Nil(g.T(), err) + + // Submit the attestation. + attSignature, attEncoded, _, err = attestation.SignAttestation(g.GetTestContext(), g.NotaryOnDestinationBondedSigner, true) + Nil(g.T(), err) + tx, err = g.DestinationDomainClient.LightInbox().SubmitAttestation( + txContextDest.TransactOpts, + attEncoded, + attSignature, + latestAgentRoot, + notaryAttestation.SnapGas(), + ) + Nil(g.T(), err) + NotNil(g.T(), tx) + g.TestBackendDestination.WaitForConfirmation(g.GetTestContext(), tx) + + // Advance time on destination and call passAgentRoot() so that the latest agent root is accepted. + increaseEvmTime(g.TestBackendDestination, optimisticPeriodSeconds+offset) + g.bumpBackends() + txContextDestination := g.TestBackendDestination.GetTxContext(g.GetTestContext(), g.DestinationContractMetadata.OwnerPtr()) + tx, err = g.DestinationDomainClient.Destination().PassAgentRoot(txContextDestination.TransactOpts) + g.Nil(err) + g.TestBackendDestination.WaitForConfirmation(g.GetTestContext(), tx) + g.bumpBackends() + + // Verify that the guard eventually marks the accused agent as Slashed. + g.Eventually(func() bool { + status, err := g.DestinationDomainClient.LightManager().GetAgentStatus(g.GetTestContext(), g.NotaryBondedSigner.Address()) + Nil(g.T(), err) + if status.Flag() == types.AgentFlagSlashed { + return true + } + + g.bumpBackends() + return false + }) +} diff --git a/agents/agents/guard/guard.go b/agents/agents/guard/guard.go index 2a76dc20f1..1c7f3e7e70 100644 --- a/agents/agents/guard/guard.go +++ b/agents/agents/guard/guard.go @@ -3,18 +3,33 @@ package guard import ( "context" "fmt" + "math/big" + "strconv" + "time" + + "github.com/synapsecns/sanguine/agents/contracts/bondingmanager" + "github.com/synapsecns/sanguine/agents/contracts/lightmanager" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/synapsecns/sanguine/agents/agents/guard/db" + "github.com/synapsecns/sanguine/agents/contracts/inbox" + "github.com/synapsecns/sanguine/agents/contracts/lightinbox" + "github.com/synapsecns/sanguine/agents/contracts/origin" "github.com/synapsecns/sanguine/core/metrics" + "github.com/synapsecns/sanguine/core/retry" signerConfig "github.com/synapsecns/sanguine/ethergo/signer/config" "github.com/synapsecns/sanguine/ethergo/submitter" omnirpcClient "github.com/synapsecns/sanguine/services/omnirpc/client" + "github.com/synapsecns/sanguine/services/scribe/client" + pbscribe "github.com/synapsecns/sanguine/services/scribe/grpc/types/types/v1" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" - "math/big" - "time" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" "github.com/synapsecns/sanguine/agents/config" "github.com/synapsecns/sanguine/agents/domains" @@ -27,59 +42,232 @@ import ( type Guard struct { bondedSigner signer.Signer unbondedSigner signer.Signer - domains []domains.DomainClient - summitDomain domains.DomainClient + domains map[uint32]domains.DomainClient + summitDomainID uint32 refreshInterval time.Duration summitLatestStates map[uint32]types.State // TODO: change to metrics type - originLatestStates map[uint32]types.State - handler metrics.Handler - txSubmitter submitter.TransactionSubmitter + originLatestStates map[uint32]types.State + handler metrics.Handler + grpcClient pbscribe.ScribeServiceClient + grpcConn *grpc.ClientConn + logChans map[uint32]chan *ethTypes.Log + inboxParser inbox.Parser + lightInboxParser lightinbox.Parser + bondingManagerParser bondingmanager.Parser + lightManagerParser lightmanager.Parser + boundOrigins map[uint32]*origin.Origin + txSubmitter submitter.TransactionSubmitter + retryConfig []retry.WithBackoffConfigurator + guardDB db.GuardDB +} + +const ( + logChanSize = 1000 + scribeConnectTimeout = 30 * time.Second +) + +func makeScribeClient(parentCtx context.Context, handler metrics.Handler, url string) (*grpc.ClientConn, pbscribe.ScribeServiceClient, error) { + ctx, cancel := context.WithTimeout(parentCtx, scribeConnectTimeout) + defer cancel() + + conn, err := grpc.DialContext(ctx, url, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor(otelgrpc.WithTracerProvider(handler.GetTracerProvider()))), + grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor(otelgrpc.WithTracerProvider(handler.GetTracerProvider()))), + ) + if err != nil { + return nil, nil, fmt.Errorf("could not dial grpc: %w", err) + } + + scribeClient := pbscribe.NewScribeServiceClient(conn) + + // Ensure that gRPC is up and running. + healthCheck, err := scribeClient.Check(ctx, &pbscribe.HealthCheckRequest{}, grpc.WaitForReady(true)) + if err != nil { + return nil, nil, fmt.Errorf("could not check: %w", err) + } + if healthCheck.Status != pbscribe.HealthCheckResponse_SERVING { + return nil, nil, fmt.Errorf("not serving: %s", healthCheck.Status) + } + + return conn, scribeClient, nil } // NewGuard creates a new guard. // //nolint:cyclop -func NewGuard(ctx context.Context, cfg config.AgentConfig, omniRPCClient omnirpcClient.RPCClient, txDB db.GuardDB, handler metrics.Handler) (_ Guard, err error) { - guard := Guard{ +func NewGuard(ctx context.Context, cfg config.AgentConfig, omniRPCClient omnirpcClient.RPCClient, scribeClient client.ScribeClient, guardDB db.GuardDB, handler metrics.Handler) (guard *Guard, err error) { + guard = &Guard{ refreshInterval: time.Second * time.Duration(cfg.RefreshIntervalSeconds), + domains: make(map[uint32]domains.DomainClient), + logChans: make(map[uint32]chan *ethTypes.Log), + boundOrigins: make(map[uint32]*origin.Origin), + } + + guard.grpcConn, guard.grpcClient, err = makeScribeClient(ctx, handler, fmt.Sprintf("%s:%d", scribeClient.URL, scribeClient.Port)) + if err != nil { + return nil, fmt.Errorf("could not create scribe client: %w", err) } - guard.domains = []domains.DomainClient{} guard.bondedSigner, err = signerConfig.SignerFromConfig(ctx, cfg.BondedSigner) if err != nil { - return Guard{}, fmt.Errorf("error with bondedSigner, could not create guard: %w", err) + return nil, fmt.Errorf("error with bondedSigner, could not create guard: %w", err) } guard.unbondedSigner, err = signerConfig.SignerFromConfig(ctx, cfg.UnbondedSigner) if err != nil { - return Guard{}, fmt.Errorf("error with unbondedSigner, could not create guard: %w", err) + return nil, fmt.Errorf("error with unbondedSigner, could not create guard: %w", err) } + // Set up evm utilities for each domain for domainName, domain := range cfg.Domains { - var domainClient domains.DomainClient + omnirpcClient, err := omniRPCClient.GetConfirmationsClient(ctx, int(domain.DomainID), 1) + if err != nil { + return nil, fmt.Errorf("error with omniRPCClient, could not create guard: %w", err) + } - chainRPCURL := omniRPCClient.GetEndpoint(int(domain.DomainID), 1) - domainClient, err = evm.NewEVM(ctx, domainName, domain, chainRPCURL) + chainRPCURL := omniRPCClient.GetDefaultEndpoint(int(domain.DomainID)) + domainClient, err := evm.NewEVM(ctx, domainName, domain, chainRPCURL) if err != nil { - return Guard{}, fmt.Errorf("failing to create evm for domain, could not create guard for: %w", err) + return nil, fmt.Errorf("failing to create evm for domain, could not create guard for: %w", err) } - guard.domains = append(guard.domains, domainClient) + guard.domains[domain.DomainID] = domainClient + + guard.logChans[domain.DomainID] = make(chan *ethTypes.Log, logChanSize) + guard.boundOrigins[domain.DomainID], err = origin.NewOrigin( + common.HexToAddress(domain.OriginAddress), + omnirpcClient, + ) + if err != nil { + return nil, fmt.Errorf("could not create origin: %w", err) + } + + // Initialize contract parsers for the summit domain. if domain.DomainID == cfg.SummitDomainID { - guard.summitDomain = domainClient + guard.summitDomainID = domain.DomainID + + guard.inboxParser, err = inbox.NewParser(common.HexToAddress(domain.InboxAddress)) + if err != nil { + return nil, fmt.Errorf("could not create inbox parser: %w", err) + } + + guard.lightInboxParser, err = lightinbox.NewParser(common.HexToAddress(domain.LightInboxAddress)) + if err != nil { + return nil, fmt.Errorf("could not create inbox parser: %w", err) + } + + guard.bondingManagerParser, err = bondingmanager.NewParser(common.HexToAddress(domain.BondingManagerAddress)) + if err != nil { + return nil, fmt.Errorf("could not create bonding manager parser: %w", err) + } + + guard.lightManagerParser, err = lightmanager.NewParser(common.HexToAddress(domain.LightManagerAddress)) + if err != nil { + return nil, fmt.Errorf("could not create light manager parser: %w", err) + } } } + _, ok := guard.domains[guard.summitDomainID] + if !ok { + return nil, fmt.Errorf("summit domain not set: %d", guard.summitDomainID) + } + guard.summitLatestStates = make(map[uint32]types.State, len(guard.domains)) guard.originLatestStates = make(map[uint32]types.State, len(guard.domains)) - guard.handler = handler - - guard.txSubmitter = submitter.NewTransactionSubmitter(handler, guard.unbondedSigner, omniRPCClient, txDB.SubmitterDB(), &cfg.SubmitterConfig) + guard.txSubmitter = submitter.NewTransactionSubmitter(handler, guard.unbondedSigner, omniRPCClient, guardDB.SubmitterDB(), &cfg.SubmitterConfig) + guard.retryConfig = []retry.WithBackoffConfigurator{ + retry.WithMaxAttemptTime(time.Second * time.Duration(cfg.MaxRetrySeconds)), + } + guard.guardDB = guardDB return guard, nil } +// streamLogs uses the grpcConnection to Scribe, with a chainID and address to get all logs from that address. +func (g Guard) streamLogs(ctx context.Context, chainID uint32, address string) error { + // TODO: Get last block number to define starting point for streamLogs. + fromBlock := strconv.FormatUint(0, 16) + toBlock := "latest" + stream, err := g.grpcClient.StreamLogs(ctx, &pbscribe.StreamLogsRequest{ + Filter: &pbscribe.LogFilter{ + ContractAddress: &pbscribe.NullableString{Kind: &pbscribe.NullableString_Data{Data: address}}, + ChainId: chainID, + }, + FromBlock: fromBlock, + ToBlock: toBlock, + }) + if err != nil { + return fmt.Errorf("could not stream logs: %w", err) + } + + for { + response, err := stream.Recv() + if err != nil { + return fmt.Errorf("could not receive: %w", err) + } + + log := response.Log.ToLog() + if log == nil { + return fmt.Errorf("could not convert log") + } + + select { + case <-ctx.Done(): + err := stream.CloseSend() + if err != nil { + return fmt.Errorf("could not close stream: %w", err) + } + + err = g.grpcConn.Close() + if err != nil { + return fmt.Errorf("could not close connection: %w", err) + } + + return fmt.Errorf("context done: %w", ctx.Err()) + case g.logChans[chainID] <- log: + logger.Info("Received log with topic: %s", log.Topics[0].String()) + } + } +} + +// receiveLogs continuously receives logs from the log channel and processes them. +func (g Guard) receiveLogs(ctx context.Context, chainID uint32) error { + for { + select { + case <-ctx.Done(): + return fmt.Errorf("context canceled: %w", ctx.Err()) + case log := <-g.logChans[chainID]: + if log == nil { + return fmt.Errorf("log is nil") + } + + err := g.handleLog(ctx, *log, chainID) + if err != nil { + return fmt.Errorf("could not process log: %w", err) + } + } + } +} + +func (g Guard) handleLog(ctx context.Context, log ethTypes.Log, chainID uint32) error { + switch { + case isSnapshotAcceptedEvent(g.inboxParser, log): + return g.handleSnapshot(ctx, log) + case isAttestationAcceptedEvent(g.lightInboxParser, log): + return g.handleAttestation(ctx, log) + case isReceiptAcceptedEvent(g.inboxParser, log): + return g.handleReceipt(ctx, log) + case isStatusUpdatedEvent(g.bondingManagerParser, log): + return g.handleStatusUpdated(ctx, log, chainID) + case isRootUpdatedEvent(g.bondingManagerParser, log): + return g.handleRootUpdated(ctx, log, chainID) + } + return nil +} + //nolint:cyclop func (g Guard) loadSummitLatestStates(parentCtx context.Context) { for _, domain := range g.domains { @@ -88,7 +276,7 @@ func (g Guard) loadSummitLatestStates(parentCtx context.Context) { )) originID := domain.Config().DomainID - latestState, err := g.summitDomain.Summit().GetLatestAgentState(ctx, originID, g.bondedSigner) + latestState, err := g.domains[g.summitDomainID].Summit().GetLatestAgentState(ctx, originID, g.bondedSigner) if err != nil { latestState = nil logger.Errorf("Failed calling GetLatestAgentState for originID %d on the Summit contract: err = %v", originID, err) @@ -172,8 +360,10 @@ func (g Guard) getLatestSnapshot() (types.Snapshot, map[uint32]types.State) { //nolint:cyclop func (g Guard) submitLatestSnapshot(parentCtx context.Context) { + summitDomain := g.domains[g.summitDomainID] + ctx, span := g.handler.Tracer().Start(parentCtx, "submitLatestSnapshot", trace.WithAttributes( - attribute.Int("domain", int(g.summitDomain.Config().DomainID)), + attribute.Int("domain", int(g.summitDomainID)), )) defer func() { @@ -194,8 +384,8 @@ func (g Guard) submitLatestSnapshot(parentCtx context.Context) { attribute.String("err", err.Error()), )) } else { - _, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(g.summitDomain.Config().DomainID)), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { - tx, err = g.summitDomain.Inbox().SubmitSnapshot(transactor, g.unbondedSigner, encodedSnapshot, snapshotSignature) + _, err = g.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(g.summitDomainID)), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { + tx, err = summitDomain.Inbox().SubmitSnapshot(transactor, encodedSnapshot, snapshotSignature) if err != nil { return nil, fmt.Errorf("failed to submit snapshot: %w", err) } @@ -232,6 +422,37 @@ func (g Guard) Start(parentCtx context.Context) error { return err }) + group.Go(func() error { + return g.streamLogs(ctx, g.summitDomainID, g.domains[g.summitDomainID].Config().InboxAddress) + }) + + group.Go(func() error { + return g.streamLogs(ctx, g.summitDomainID, g.domains[g.summitDomainID].Config().BondingManagerAddress) + }) + + group.Go(func() error { + return g.receiveLogs(ctx, g.summitDomainID) + }) + + for _, domain := range g.domains { + domainCfg := domain.Config() + if domainCfg.DomainID == g.summitDomainID { + continue + } + + group.Go(func() error { + return g.streamLogs(ctx, domainCfg.DomainID, domainCfg.LightInboxAddress) + }) + + group.Go(func() error { + return g.streamLogs(ctx, domainCfg.DomainID, domainCfg.LightManagerAddress) + }) + + group.Go(func() error { + return g.receiveLogs(ctx, domainCfg.DomainID) + }) + } + group.Go(func() error { for { select { @@ -242,6 +463,10 @@ func (g Guard) Start(parentCtx context.Context) error { case <-time.After(g.refreshInterval): g.loadOriginLatestStates(ctx) g.submitLatestSnapshot(ctx) + err := g.updateAgentStatuses(ctx) + if err != nil { + return err + } } } }) diff --git a/agents/agents/guard/guard_test.go b/agents/agents/guard/guard_test.go index 246a11dd3a..5f200057e8 100644 --- a/agents/agents/guard/guard_test.go +++ b/agents/agents/guard/guard_test.go @@ -1,13 +1,15 @@ package guard_test import ( - signerConfig "github.com/synapsecns/sanguine/ethergo/signer/config" - omniClient "github.com/synapsecns/sanguine/services/omnirpc/client" "math/big" "os" "testing" "time" + signerConfig "github.com/synapsecns/sanguine/ethergo/signer/config" + omniClient "github.com/synapsecns/sanguine/services/omnirpc/client" + "github.com/synapsecns/sanguine/services/scribe/client" + "github.com/Flaque/filet" awsTime "github.com/aws/smithy-go/time" "github.com/brianvoe/gofakeit/v6" @@ -24,47 +26,53 @@ func RemoveGuardTempFile(t *testing.T, fileName string) { Nil(t, err) } -func (u GuardSuite) TestGuardE2E() { +func (g GuardSuite) TestGuardE2E() { testConfig := config.AgentConfig{ Domains: map[string]config.DomainConfig{ - "origin_client": u.OriginDomainClient.Config(), - "destination_client": u.DestinationDomainClient.Config(), - "summit_client": u.SummitDomainClient.Config(), + "origin_client": g.OriginDomainClient.Config(), + "destination_client": g.DestinationDomainClient.Config(), + "summit_client": g.SummitDomainClient.Config(), }, DomainID: uint32(0), - SummitDomainID: u.SummitDomainClient.Config().DomainID, + SummitDomainID: g.SummitDomainClient.Config().DomainID, BondedSigner: signerConfig.SignerConfig{ Type: signerConfig.FileType.String(), - File: filet.TmpFile(u.T(), "", u.GuardBondedWallet.PrivateKeyHex()).Name(), + File: filet.TmpFile(g.T(), "", g.GuardBondedWallet.PrivateKeyHex()).Name(), }, UnbondedSigner: signerConfig.SignerConfig{ Type: signerConfig.FileType.String(), - File: filet.TmpFile(u.T(), "", u.GuardUnbondedWallet.PrivateKeyHex()).Name(), + File: filet.TmpFile(g.T(), "", g.GuardUnbondedWallet.PrivateKeyHex()).Name(), }, RefreshIntervalSeconds: 5, } encodedTestConfig, err := testConfig.Encode() - Nil(u.T(), err) + Nil(g.T(), err) tempConfigFile, err := os.CreateTemp("", "guard_temp_config.yaml") - Nil(u.T(), err) - defer RemoveGuardTempFile(u.T(), tempConfigFile.Name()) + Nil(g.T(), err) + defer RemoveGuardTempFile(g.T(), tempConfigFile.Name()) numBytesWritten, err := tempConfigFile.Write(encodedTestConfig) - Nil(u.T(), err) - Positive(u.T(), numBytesWritten) + Nil(g.T(), err) + Positive(g.T(), numBytesWritten) decodedAgentConfig, err := config.DecodeAgentConfig(tempConfigFile.Name()) - Nil(u.T(), err) + Nil(g.T(), err) decodedAgentConfigBackToEncodedBytes, err := decodedAgentConfig.Encode() - Nil(u.T(), err) + Nil(g.T(), err) + + Equal(g.T(), encodedTestConfig, decodedAgentConfigBackToEncodedBytes) - Equal(u.T(), encodedTestConfig, decodedAgentConfigBackToEncodedBytes) + omniRPCClient := omniClient.NewOmnirpcClient(g.TestOmniRPC, g.GuardMetrics, omniClient.WithCaptureReqRes()) + scribeClient := client.NewEmbeddedScribe("sqlite", g.DBPath, g.ScribeMetrics) + go func() { + scribeErr := scribeClient.Start(g.GetTestContext()) + Nil(g.T(), scribeErr) + }() - omniRPCClient := omniClient.NewOmnirpcClient(u.TestOmniRPC, u.GuardMetrics, omniClient.WithCaptureReqRes()) - guard, err := guard.NewGuard(u.GetTestContext(), testConfig, omniRPCClient, u.GuardTestDB, u.GuardMetrics) - Nil(u.T(), err) + guard, err := guard.NewGuard(g.GetTestContext(), testConfig, omniRPCClient, scribeClient.ScribeClient, g.GuardTestDB, g.GuardMetrics) + Nil(g.T(), err) tips := types.NewTips(big.NewInt(int64(0)), big.NewInt(int64(0)), big.NewInt(int64(0)), big.NewInt(int64(0))) @@ -72,64 +80,64 @@ func (u GuardSuite) TestGuardE2E() { body := []byte{byte(gofakeit.Uint32())} - txContextOrigin := u.TestBackendOrigin.GetTxContext(u.GetTestContext(), u.OriginContractMetadata.OwnerPtr()) + txContextOrigin := g.TestBackendOrigin.GetTxContext(g.GetTestContext(), g.OriginContractMetadata.OwnerPtr()) txContextOrigin.Value = types.TotalTips(tips) - txContextTestClientOrigin := u.TestBackendOrigin.GetTxContext(u.GetTestContext(), u.TestClientMetadataOnOrigin.OwnerPtr()) + txContextTestClientOrigin := g.TestBackendOrigin.GetTxContext(g.GetTestContext(), g.TestClientMetadataOnOrigin.OwnerPtr()) gasLimit := uint64(10000000) version := uint32(1) - testClientOnOriginTx, err := u.TestClientOnOrigin.SendMessage( + testClientOnOriginTx, err := g.TestClientOnOrigin.SendMessage( txContextTestClientOrigin.TransactOpts, - uint32(u.TestBackendDestination.GetChainID()), - u.TestClientMetadataOnDestination.Address(), + uint32(g.TestBackendDestination.GetChainID()), + g.TestClientMetadataOnDestination.Address(), optimisticSeconds, gasLimit, version, body) - u.Nil(err) - u.TestBackendOrigin.WaitForConfirmation(u.GetTestContext(), testClientOnOriginTx) + g.Nil(err) + g.TestBackendOrigin.WaitForConfirmation(g.GetTestContext(), testClientOnOriginTx) go func() { // we don't check errors here since this will error on cancellation at the end of the test - _ = guard.Start(u.GetTestContext()) + _ = guard.Start(g.GetTestContext()) }() - u.Eventually(func() bool { - _ = awsTime.SleepWithContext(u.GetTestContext(), time.Second*5) + g.Eventually(func() bool { + _ = awsTime.SleepWithContext(g.GetTestContext(), time.Second*5) - rawState, err := u.SummitContract.GetLatestAgentState( - &bind.CallOpts{Context: u.GetTestContext()}, - u.OriginDomainClient.Config().DomainID, - u.GuardBondedSigner.Address()) + rawState, err := g.SummitContract.GetLatestAgentState( + &bind.CallOpts{Context: g.GetTestContext()}, + g.OriginDomainClient.Config().DomainID, + g.GuardBondedSigner.Address()) - Nil(u.T(), err) + Nil(g.T(), err) if len(rawState) == 0 { return false } state, err := types.DecodeState(rawState) - Nil(u.T(), err) + Nil(g.T(), err) return state.Nonce() >= uint32(1) }) // Now make sure GetLatestState works as well - u.Eventually(func() bool { - _ = awsTime.SleepWithContext(u.GetTestContext(), time.Second*5) + g.Eventually(func() bool { + _ = awsTime.SleepWithContext(g.GetTestContext(), time.Second*5) - rawState, err := u.SummitContract.GetLatestState( - &bind.CallOpts{Context: u.GetTestContext()}, - u.OriginDomainClient.Config().DomainID) - Nil(u.T(), err) + rawState, err := g.SummitContract.GetLatestState( + &bind.CallOpts{Context: g.GetTestContext()}, + g.OriginDomainClient.Config().DomainID) + Nil(g.T(), err) if len(rawState) == 0 { return false } state, err := types.DecodeState(rawState) - Nil(u.T(), err) + Nil(g.T(), err) return state.Nonce() >= uint32(1) }) } diff --git a/agents/agents/guard/suite_test.go b/agents/agents/guard/suite_test.go index 4e0fe88b84..a2b13b7233 100644 --- a/agents/agents/guard/suite_test.go +++ b/agents/agents/guard/suite_test.go @@ -23,10 +23,10 @@ func NewGuardSuite(tb testing.TB) *GuardSuite { } } -func (u *GuardSuite) SetupTest() { +func (g *GuardSuite) SetupTest() { chainwatcher.PollInterval = time.Second - u.SimulatedBackendsTestSuite.SetupTest() + g.SimulatedBackendsTestSuite.SetupTest() } func TestGuardSuite(t *testing.T) { diff --git a/agents/agents/guard/utils.go b/agents/agents/guard/utils.go new file mode 100644 index 0000000000..2a4a6ba0d6 --- /dev/null +++ b/agents/agents/guard/utils.go @@ -0,0 +1,36 @@ +package guard + +import ( + ethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/synapsecns/sanguine/agents/contracts/bondingmanager" + "github.com/synapsecns/sanguine/agents/contracts/inbox" + "github.com/synapsecns/sanguine/agents/contracts/lightinbox" +) + +func isSnapshotAcceptedEvent(parser inbox.Parser, log ethTypes.Log) bool { + inboxEvent, ok := parser.EventType(log) + return ok && inboxEvent == inbox.SnapshotAcceptedEvent +} + +func isAttestationAcceptedEvent(parser lightinbox.Parser, log ethTypes.Log) bool { + lightManagerEvent, ok := parser.EventType(log) + return ok && lightManagerEvent == lightinbox.AttestationAcceptedEvent +} + +func isReceiptAcceptedEvent(parser inbox.Parser, log ethTypes.Log) bool { + inboxEvent, ok := parser.EventType(log) + return ok && inboxEvent == inbox.ReceiptAcceptedEvent +} + +func isStatusUpdatedEvent(parser bondingmanager.Parser, log ethTypes.Log) bool { + bondingManagerEvent, ok := parser.EventType(log) + return ok && bondingManagerEvent == bondingmanager.StatusUpdatedEvent +} + +func isRootUpdatedEvent(bondingParser bondingmanager.Parser, log ethTypes.Log) bool { + bondingManagerEvent, ok := bondingParser.EventType(log) + if ok && bondingManagerEvent == bondingmanager.RootUpdatedEvent { + return true + } + return false +} diff --git a/agents/agents/notary/notary.go b/agents/agents/notary/notary.go index a26cc0fea1..974c34c3bc 100644 --- a/agents/agents/notary/notary.go +++ b/agents/agents/notary/notary.go @@ -195,17 +195,17 @@ func (n *Notary) shouldNotaryRegisteredOnDestination(parentCtx context.Context) return false, false } - agentStatus, err := n.destinationDomain.LightManager().GetAgentStatus(ctx, n.bondedSigner) + agentStatus, err := n.destinationDomain.LightManager().GetAgentStatus(ctx, n.bondedSigner.Address()) if err != nil { span.AddEvent("GetAgentStatus failed", trace.WithAttributes( attribute.String("err", err.Error()), )) return false, false } - if types.AgentFlagType(agentStatus.Flag()) == types.AgentFlagUnknown { + if agentStatus.Flag() == types.AgentFlagUnknown { // Here we want to add the Notary and proceed with sending to destination return true, true - } else if types.AgentFlagType(agentStatus.Flag()) == types.AgentFlagActive { + } else if agentStatus.Flag() == types.AgentFlagActive { // Here we already added the Notary and can proceed with sending to destination return false, true } @@ -393,7 +393,7 @@ func (n *Notary) submitLatestSnapshot(parentCtx context.Context) { } else { logger.Infof("Notary submitting snapshot to summit") _, err := n.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(n.summitDomain.Config().DomainID)), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { - tx, err = n.summitDomain.Inbox().SubmitSnapshot(transactor, n.unbondedSigner, encodedSnapshot, snapshotSignature) + tx, err = n.summitDomain.Inbox().SubmitSnapshot(transactor, encodedSnapshot, snapshotSignature) if err != nil { return nil, fmt.Errorf("could not submit snapshot: %w", err) } @@ -419,7 +419,7 @@ func (n *Notary) registerNotaryOnDestination(parentCtx context.Context) bool { ctx, span := n.handler.Tracer().Start(parentCtx, "registerNotaryOnDestination") defer span.End() - agentProof, err := n.summitDomain.BondingManager().GetProof(ctx, n.bondedSigner) + agentProof, err := n.summitDomain.BondingManager().GetProof(ctx, n.bondedSigner.Address()) if err != nil { logger.Errorf("Error getting agent proof: %v", err) span.AddEvent("Error getting agent proof", trace.WithAttributes( @@ -427,19 +427,26 @@ func (n *Notary) registerNotaryOnDestination(parentCtx context.Context) bool { )) return false } - agentStatus, err := n.summitDomain.BondingManager().GetAgentStatus(ctx, n.bondedSigner) + agentStatus, err := n.summitDomain.BondingManager().GetAgentStatus(ctx, n.bondedSigner.Address()) if err != nil { span.AddEvent("GetAgentStatus on bonding manager failed", trace.WithAttributes( attribute.String("err", err.Error()), )) return false } - err = n.destinationDomain.LightManager().UpdateAgentStatus( - ctx, - n.unbondedSigner, - n.bondedSigner, - agentStatus, - agentProof) + _, err = n.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(n.destinationDomain.Config().DomainID)), func(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) { + tx, err = n.destinationDomain.LightManager().UpdateAgentStatus( + transactor, + n.bondedSigner.Address(), + agentStatus, + agentProof, + ) + if err != nil { + return nil, fmt.Errorf("could not update agent status: %w", err) + } + + return + }) if err != nil { span.AddEvent("Error updating agent status", trace.WithAttributes( attribute.String("err", err.Error()), @@ -462,7 +469,7 @@ func (n *Notary) submitMyLatestAttestation(parentCtx context.Context) { return } - attestationSignature, _, _, err := n.myLatestNotaryAttestation.Attestation().SignAttestation(ctx, n.bondedSigner) + attestationSignature, _, _, err := n.myLatestNotaryAttestation.Attestation().SignAttestation(ctx, n.bondedSigner, true) if err != nil { logger.Errorf("Error signing attestation: %v", err) span.AddEvent("Error signing attestation", trace.WithAttributes( diff --git a/agents/agents/notary/notary_test.go b/agents/agents/notary/notary_test.go index 1ca24872dd..99cb6802fc 100644 --- a/agents/agents/notary/notary_test.go +++ b/agents/agents/notary/notary_test.go @@ -1,14 +1,16 @@ package notary_test import ( - "github.com/synapsecns/sanguine/agents/agents/notary" - signerConfig "github.com/synapsecns/sanguine/ethergo/signer/config" - omniClient "github.com/synapsecns/sanguine/services/omnirpc/client" "math/big" "os" "testing" "time" + "github.com/synapsecns/sanguine/agents/agents/notary" + signerConfig "github.com/synapsecns/sanguine/ethergo/signer/config" + omniClient "github.com/synapsecns/sanguine/services/omnirpc/client" + "github.com/synapsecns/sanguine/services/scribe/client" + "github.com/Flaque/filet" awsTime "github.com/aws/smithy-go/time" "github.com/brianvoe/gofakeit/v6" @@ -84,7 +86,14 @@ func (u *NotarySuite) TestNotaryE2E() { Equal(u.T(), encodedNotaryTestConfig, decodedAgentConfigBackToEncodedBytes) omniRPCClient := omniClient.NewOmnirpcClient(u.TestOmniRPC, u.NotaryMetrics, omniClient.WithCaptureReqRes()) - guard, err := guard.NewGuard(u.GetTestContext(), guardTestConfig, omniRPCClient, u.GuardTestDB, u.GuardMetrics) + + scribeClient := client.NewEmbeddedScribe("sqlite", u.DBPath, u.ScribeMetrics) + go func() { + scribeErr := scribeClient.Start(u.GetTestContext()) + Nil(u.T(), scribeErr) + }() + + guard, err := guard.NewGuard(u.GetTestContext(), guardTestConfig, omniRPCClient, scribeClient.ScribeClient, u.GuardTestDB, u.GuardMetrics) Nil(u.T(), err) tips := types.NewTips(big.NewInt(int64(0)), big.NewInt(int64(0)), big.NewInt(int64(0)), big.NewInt(int64(0))) @@ -114,8 +123,7 @@ func (u *NotarySuite) TestNotaryE2E() { go func() { // we don't check errors here since this will error on cancellation at the end of the test - err = guard.Start(u.GetTestContext()) - u.Nil(err) + _ = guard.Start(u.GetTestContext()) }() u.Eventually(func() bool { diff --git a/agents/config/agent_config.go b/agents/config/agent_config.go index 408d91819a..bb0239ac8f 100644 --- a/agents/config/agent_config.go +++ b/agents/config/agent_config.go @@ -3,11 +3,12 @@ package config import ( "context" "fmt" - "github.com/synapsecns/sanguine/ethergo/signer/config" - submitterConfig "github.com/synapsecns/sanguine/ethergo/submitter/config" "os" "path/filepath" + "github.com/synapsecns/sanguine/ethergo/signer/config" + submitterConfig "github.com/synapsecns/sanguine/ethergo/submitter/config" + "github.com/davecgh/go-spew/spew" "github.com/jftuga/ellipsis" "gopkg.in/yaml.v2" @@ -17,6 +18,8 @@ import ( type AgentConfig struct { // DBConfig is the database configuration. DBConfig DBConfig `yaml:"db_config"` + // ScribeConfig is the scribe configuration. + ScribeConfig ScribeConfig `yaml:"scribe_config"` // Domains stores all the domains Domains DomainConfigs `yaml:"domains"` // DomainID is the domain of the chain that this agent is assigned to. @@ -40,6 +43,8 @@ type AgentConfig struct { DBPrefix string `yaml:"db_prefix"` // SubmitterConfig is the config for the submitter. SubmitterConfig submitterConfig.Config `yaml:"submitter_config"` + // MaxRetrySeconds is the maximum number of seconds to retry an RPC call (not a transaction). + MaxRetrySeconds uint32 `yaml:"max_retry_seconds"` } // IsValid makes sure the config is valid. This is done by calling IsValid() on each diff --git a/agents/config/executor/config.go b/agents/config/executor/config.go index 49038fe517..5d0b178063 100644 --- a/agents/config/executor/config.go +++ b/agents/config/executor/config.go @@ -18,7 +18,7 @@ type Config struct { // DBConfig is the database configuration. DBConfig config.DBConfig `yaml:"db_config"` // ScribeConfig is the scribe configuration. - ScribeConfig ScribeConfig `yaml:"scribe_config"` + ScribeConfig config.ScribeConfig `yaml:"scribe_config"` // Chains stores all chain information Chains ChainConfigs `yaml:"chains"` // SummitChainID is the chain ID of the chain that the summit contract is deployed on. diff --git a/agents/config/executor/config_test.go b/agents/config/executor/config_test.go index 43c1e71f20..5286d208d2 100644 --- a/agents/config/executor/config_test.go +++ b/agents/config/executor/config_test.go @@ -21,11 +21,10 @@ func configFixture(c ConfigSuite) executor.Config { Type: "sqlite", Source: gofakeit.Word(), }, - ScribeConfig: executor.ScribeConfig{ + ScribeConfig: config.ScribeConfig{ Type: "embedded", - EmbeddedDBConfig: config.DBConfig{ - Type: "mysql", - Source: gofakeit.Word(), + EmbeddedDBConfig: scribeConfig.DBConfig{ + Type: "mysql", }, EmbeddedScribeConfig: scribeConfig.Config{ RPCURL: gofakeit.URL(), diff --git a/agents/config/executor/scribe.go b/agents/config/scribe.go similarity index 89% rename from agents/config/executor/scribe.go rename to agents/config/scribe.go index 1841e7f7ce..e42fc997a0 100644 --- a/agents/config/executor/scribe.go +++ b/agents/config/scribe.go @@ -1,9 +1,9 @@ -package executor +package config import ( "context" "fmt" - "github.com/synapsecns/sanguine/agents/config" + scribeConfig "github.com/synapsecns/sanguine/services/scribe/config" ) @@ -13,7 +13,7 @@ type ScribeConfig struct { Type string `yaml:"type"` // EmbeddedDBConfig is the database configuration for an embedded scribe. - EmbeddedDBConfig config.DBConfig `yaml:"embedded_db_config,omitempty"` + EmbeddedDBConfig scribeConfig.DBConfig `yaml:"embedded_db_config,omitempty"` // EmbeddedScribeConfig is the config for the embedded scribe. EmbeddedScribeConfig scribeConfig.Config `yaml:"embedded_scribe_config,omitempty"` diff --git a/agents/contracts/bondingmanager/eventtype_string.go b/agents/contracts/bondingmanager/eventtype_string.go new file mode 100644 index 0000000000..0c13e80d92 --- /dev/null +++ b/agents/contracts/bondingmanager/eventtype_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type=EventType"; DO NOT EDIT. + +package bondingmanager + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[StatusUpdatedEvent-0] + _ = x[DisputeOpenedEvent-1] + _ = x[RootUpdatedEvent-2] +} + +const _EventType_name = "StatusUpdatedEventDisputeOpenedEventRootUpdatedEvent" + +var _EventType_index = [...]uint8{0, 18, 36, 52} + +func (i EventType) String() string { + if i >= EventType(len(_EventType_index)-1) { + return "EventType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _EventType_name[_EventType_index[i]:_EventType_index[i+1]] +} diff --git a/agents/contracts/bondingmanager/parser.go b/agents/contracts/bondingmanager/parser.go new file mode 100644 index 0000000000..a7fcf0f849 --- /dev/null +++ b/agents/contracts/bondingmanager/parser.go @@ -0,0 +1,93 @@ +package bondingmanager + +import ( + "fmt" + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" +) + +// Parser parses events from the light inbox contract. +type Parser interface { + // EventType determines if an event was initiated by the bridge or the user. + EventType(log ethTypes.Log) (_ EventType, ok bool) + // ParseStatusUpdated parses a StatusUpdated event + ParseStatusUpdated(log ethTypes.Log) (_ *BondingManagerStatusUpdated, err error) + // ParseDisputeOpened parses a DisputeOpened event + ParseDisputeOpened(log ethTypes.Log) (_ *BondingManagerDisputeOpened, err error) + // ParseRootUpdated parses a RootUpdated event + ParseRootUpdated(log ethTypes.Log) (_ *[32]byte, err error) +} + +type parserImpl struct { + // filterer is the parser filterer we use to parse events + filterer *BondingManagerFilterer +} + +// NewParser creates a new parser for the bonding manager contract. +func NewParser(bondingManagerAddress common.Address) (Parser, error) { + parser, err := NewBondingManagerFilterer(bondingManagerAddress, nil) + if err != nil { + return nil, fmt.Errorf("could not create %T: %w", BondingManagerFilterer{}, err) + } + + return &parserImpl{filterer: parser}, nil +} + +func (p parserImpl) ParseStatusUpdated(log ethTypes.Log) (_ *BondingManagerStatusUpdated, err error) { + bondingManagerStatusUpdated, err := p.filterer.ParseStatusUpdated(log) + if err != nil { + return nil, fmt.Errorf("could not parse status updated: %w", err) + } + + return bondingManagerStatusUpdated, nil +} + +func (p parserImpl) ParseDisputeOpened(log ethTypes.Log) (_ *BondingManagerDisputeOpened, err error) { + bondingManagerDisputeOpened, err := p.filterer.ParseDisputeOpened(log) + if err != nil { + return nil, fmt.Errorf("could not parse status updated: %w", err) + } + + return bondingManagerDisputeOpened, nil +} + +func (p parserImpl) ParseRootUpdated(log ethTypes.Log) (_ *[32]byte, err error) { + bondingManagerRootUpdated, err := p.filterer.ParseRootUpdated(log) + if err != nil { + return nil, fmt.Errorf("could not parse status updated: %w", err) + } + + return &bondingManagerRootUpdated.NewRoot, nil +} + +func (p parserImpl) EventType(log ethTypes.Log) (_ EventType, ok bool) { + for _, logTopic := range log.Topics { + eventType := eventTypeFromTopic(logTopic) + if eventType == nil { + continue + } + + return *eventType, true + } + // return an unknown event to avoid cases where user failed to check the event type + return EventType(len(topicMap()) + 2), false +} + +// EventType is the type of the bonding manager event +// +//go:generate go run golang.org/x/tools/cmd/stringer -type=EventType +type EventType uint + +const ( + // StatusUpdatedEvent is an StatusUpdated event. + StatusUpdatedEvent EventType = iota + // DisputeOpenedEvent is an DisputeOpened event. + DisputeOpenedEvent + // RootUpdatedEvent is an RootUpdated event. + RootUpdatedEvent +) + +// Int gets the int for an event type. +func (i EventType) Int() uint8 { + return uint8(i) +} diff --git a/agents/contracts/bondingmanager/topics.go b/agents/contracts/bondingmanager/topics.go new file mode 100644 index 0000000000..43264b004d --- /dev/null +++ b/agents/contracts/bondingmanager/topics.go @@ -0,0 +1,57 @@ +package bondingmanager + +import ( + "bytes" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" +) + +func init() { + // set topics + var err error + + parsedBondingManager, err := abi.JSON(strings.NewReader(BondingManagerMetaData.ABI)) + if err != nil { + panic(err) + } + + StatusUpdatedTopic = parsedBondingManager.Events["StatusUpdated"].ID + DisputeOpenedTopic = parsedBondingManager.Events["DisputeOpened"].ID + RootUpdatedTopic = parsedBondingManager.Events["RootUpdated"].ID + + if StatusUpdatedTopic == (common.Hash{}) { + panic("StatusUpdatedTopic is nil") + } +} + +// StatusUpdatedTopic is the topic that gets emitted when the StatusUpdated event is called. +var StatusUpdatedTopic common.Hash + +// DisputeOpenedTopic is the topic that gets emitted when the DisputeOpened event is called. +var DisputeOpenedTopic common.Hash + +// RootUpdatedTopic is the topic that gets emitted when the RootUpdated event is called. +var RootUpdatedTopic common.Hash + +// topicMap maps events to topics. +// this is returned as a function to assert immutability. +func topicMap() map[EventType]common.Hash { + return map[EventType]common.Hash{ + StatusUpdatedEvent: StatusUpdatedTopic, + DisputeOpenedEvent: DisputeOpenedTopic, + RootUpdatedEvent: RootUpdatedTopic, + } +} + +// eventTypeFromTopic gets the event type from the topic +// returns nil if the topic is not found. +func eventTypeFromTopic(ogTopic common.Hash) *EventType { + for eventType, topic := range topicMap() { + if bytes.Equal(ogTopic.Bytes(), topic.Bytes()) { + return &eventType + } + } + return nil +} diff --git a/agents/contracts/inbox/eventtype_string.go b/agents/contracts/inbox/eventtype_string.go index baaae07505..9b55b8d0f8 100644 --- a/agents/contracts/inbox/eventtype_string.go +++ b/agents/contracts/inbox/eventtype_string.go @@ -9,11 +9,12 @@ func _() { // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[SnapshotAcceptedEvent-0] + _ = x[ReceiptAcceptedEvent-1] } -const _EventType_name = "SnapshotAcceptedEvent" +const _EventType_name = "SnapshotAcceptedEventReceiptAcceptedEvent" -var _EventType_index = [...]uint8{0, 21} +var _EventType_index = [...]uint8{0, 21, 41} func (i EventType) String() string { if i >= EventType(len(_EventType_index)-1) { diff --git a/agents/contracts/inbox/parser.go b/agents/contracts/inbox/parser.go index c6bdd22fc7..bb6d78b14e 100644 --- a/agents/contracts/inbox/parser.go +++ b/agents/contracts/inbox/parser.go @@ -13,7 +13,9 @@ type Parser interface { // EventType is the event type. EventType(log ethTypes.Log) (_ EventType, ok bool) // ParseSnapshotAccepted parses a SnapshotAccepted event. - ParseSnapshotAccepted(log ethTypes.Log) (_ types.Snapshot, domain uint32, ok bool) + ParseSnapshotAccepted(log ethTypes.Log) (_ *types.FraudSnapshot, err error) + // ParseReceiptAccepted parses a ReceiptAccepted event. + ParseReceiptAccepted(log ethTypes.Log) (_ *InboxReceiptAccepted, err error) } type parserImpl struct { @@ -45,18 +47,28 @@ func (p parserImpl) EventType(log ethTypes.Log) (_ EventType, ok bool) { } // ParseSnapshotAccepted parses a SnapshotAccepted event. -func (p parserImpl) ParseSnapshotAccepted(log ethTypes.Log) (_ types.Snapshot, domain uint32, ok bool) { +func (p parserImpl) ParseSnapshotAccepted(log ethTypes.Log) (_ *types.FraudSnapshot, err error) { inboxSnapshot, err := p.filterer.ParseSnapshotAccepted(log) if err != nil { - return nil, 0, false + return nil, fmt.Errorf("could not parse snapshot accepted event: %w", err) } - snapshot, err := types.DecodeSnapshot(inboxSnapshot.SnapPayload) + fraudSnapshot, err := types.NewFraudSnapshotFromPayload(inboxSnapshot.SnapPayload, inboxSnapshot.Domain, inboxSnapshot.Agent, inboxSnapshot.SnapSignature) if err != nil { - return nil, 0, false + return nil, fmt.Errorf("could not create fraud snapshot from payload: %w", err) } - return snapshot, inboxSnapshot.Domain, true + return fraudSnapshot, nil +} + +// ParseReceiptAccepted parses a ReceiptAccepted event. +func (p parserImpl) ParseReceiptAccepted(log ethTypes.Log) (_ *InboxReceiptAccepted, err error) { + inboxReceipt, err := p.filterer.ParseReceiptAccepted(log) + if err != nil { + return nil, fmt.Errorf("could not parse snapshot accepted event: %w", err) + } + + return inboxReceipt, nil } // EventType is the type of the summit events @@ -67,6 +79,8 @@ type EventType uint const ( // SnapshotAcceptedEvent is a SnapshotAccepted event. SnapshotAcceptedEvent EventType = iota + // ReceiptAcceptedEvent is a ReceiptAccepted event. + ReceiptAcceptedEvent ) // Int gets the int for an event type. @@ -75,4 +89,4 @@ func (i EventType) Int() uint8 { } // AllEventTypes contains all event types. -var AllEventTypes = []EventType{SnapshotAcceptedEvent} +var AllEventTypes = []EventType{SnapshotAcceptedEvent, ReceiptAcceptedEvent} diff --git a/agents/contracts/inbox/topics.go b/agents/contracts/inbox/topics.go index d50b3772a6..317609e293 100644 --- a/agents/contracts/inbox/topics.go +++ b/agents/contracts/inbox/topics.go @@ -19,20 +19,30 @@ func init() { SnapshotAcceptedTopic = parsedInbox.Events["SnapshotAccepted"].ID + ReceiptAcceptedTopic = parsedInbox.Events["ReceiptAccepted"].ID + if SnapshotAcceptedTopic == (common.Hash{}) { panic("SnapshotAcceptedTopic is nil") } + if ReceiptAcceptedTopic == (common.Hash{}) { + panic("ReceiptAcceptedTopic is nil") + } } // SnapshotAcceptedTopic is the topic that gets emitted // when the SnapshotAccepted event is called. var SnapshotAcceptedTopic common.Hash +// ReceiptAcceptedTopic is the topic that gets emitted +// when the ReceiptAccepted event is called. +var ReceiptAcceptedTopic common.Hash + // topicMap maps events to topics. // this is returned as a function to assert immutability. func topicMap() map[EventType]common.Hash { return map[EventType]common.Hash{ SnapshotAcceptedEvent: SnapshotAcceptedTopic, + ReceiptAcceptedEvent: ReceiptAcceptedTopic, } } diff --git a/agents/contracts/lightinbox/parser.go b/agents/contracts/lightinbox/parser.go index d4b80597c1..5e29072630 100644 --- a/agents/contracts/lightinbox/parser.go +++ b/agents/contracts/lightinbox/parser.go @@ -13,7 +13,7 @@ type Parser interface { // EventType determines if an event was initiated by the bridge or the user. EventType(log ethTypes.Log) (_ EventType, ok bool) // ParseAttestationAccepted parses an AttestationAccepted event - ParseAttestationAccepted(log ethTypes.Log) (_ types.Attestation, ok bool) + ParseAttestationAccepted(log ethTypes.Log) (_ *types.FraudAttestation, err error) } type parserImpl struct { @@ -45,18 +45,23 @@ func (p parserImpl) EventType(log ethTypes.Log) (_ EventType, ok bool) { } // ParseAttestationAccepted parses an AttestationAccepted event. -func (p parserImpl) ParseAttestationAccepted(log ethTypes.Log) (_ types.Attestation, ok bool) { +func (p parserImpl) ParseAttestationAccepted(log ethTypes.Log) (_ *types.FraudAttestation, err error) { lightInboxAttestationAccepted, err := p.filterer.ParseAttestationAccepted(log) if err != nil { - return nil, false + return nil, fmt.Errorf("could not parse attestation accepted: %w", err) } - attestation, err := types.DecodeAttestation(lightInboxAttestationAccepted.AttPayload) + fraudAttestation, err := types.NewFraudAttestationFromPayload( + lightInboxAttestationAccepted.AttPayload, + lightInboxAttestationAccepted.Domain, + lightInboxAttestationAccepted.Notary, + lightInboxAttestationAccepted.AttSignature, + ) if err != nil { - return nil, false + return nil, fmt.Errorf("could not create fraud attestation from payload: %w", err) } - return attestation, true + return fraudAttestation, nil } // EventType is the type of the light inbox event diff --git a/agents/contracts/lightmanager/eventtype_string.go b/agents/contracts/lightmanager/eventtype_string.go new file mode 100644 index 0000000000..89ec10816c --- /dev/null +++ b/agents/contracts/lightmanager/eventtype_string.go @@ -0,0 +1,24 @@ +// Code generated by "stringer -type=EventType"; DO NOT EDIT. + +package lightmanager + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[DisputeOpenedEvent-0] + _ = x[RootUpdatedEvent-1] +} + +const _EventType_name = "DisputeOpenedEventRootUpdatedEvent" + +var _EventType_index = [...]uint8{0, 18, 34} + +func (i EventType) String() string { + if i >= EventType(len(_EventType_index)-1) { + return "EventType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _EventType_name[_EventType_index[i]:_EventType_index[i+1]] +} diff --git a/agents/contracts/lightmanager/parser.go b/agents/contracts/lightmanager/parser.go new file mode 100644 index 0000000000..33d6ad5047 --- /dev/null +++ b/agents/contracts/lightmanager/parser.go @@ -0,0 +1,80 @@ +package lightmanager + +import ( + "fmt" + "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" +) + +// Parser parses events from the light inbox contract. +type Parser interface { + // EventType determines if an event was initiated by the bridge or the user. + EventType(log ethTypes.Log) (_ EventType, ok bool) + // ParseDisputeOpened parses a DisputeOpened event + ParseDisputeOpened(log ethTypes.Log) (_ *LightManagerDisputeOpened, err error) + // ParseRootUpdated parses a RootUpdated event + ParseRootUpdated(log ethTypes.Log) (_ *[32]byte, err error) +} + +type parserImpl struct { + // filterer is the parser filterer we use to parse events + filterer *LightManagerFilterer +} + +// NewParser creates a new parser for the light manager contract. +func NewParser(lightManagerAddress common.Address) (Parser, error) { + parser, err := NewLightManagerFilterer(lightManagerAddress, nil) + if err != nil { + return nil, fmt.Errorf("could not create %T: %w", LightManagerFilterer{}, err) + } + + return &parserImpl{filterer: parser}, nil +} + +func (p parserImpl) ParseDisputeOpened(log ethTypes.Log) (_ *LightManagerDisputeOpened, err error) { + lightManagerDisputeOpened, err := p.filterer.ParseDisputeOpened(log) + if err != nil { + return nil, fmt.Errorf("could not parse status updated: %w", err) + } + + return lightManagerDisputeOpened, nil +} + +func (p parserImpl) ParseRootUpdated(log ethTypes.Log) (_ *[32]byte, err error) { + lightManagerRootUpdated, err := p.filterer.ParseRootUpdated(log) + if err != nil { + return nil, fmt.Errorf("could not parse status updated: %w", err) + } + + return &lightManagerRootUpdated.NewRoot, nil +} + +func (p parserImpl) EventType(log ethTypes.Log) (_ EventType, ok bool) { + for _, logTopic := range log.Topics { + eventType := eventTypeFromTopic(logTopic) + if eventType == nil { + continue + } + + return *eventType, true + } + // return an unknown event to avoid cases where user failed to check the event type + return EventType(len(topicMap()) + 2), false +} + +// EventType is the type of the light manager event +// +//go:generate go run golang.org/x/tools/cmd/stringer -type=EventType +type EventType uint + +const ( + // DisputeOpenedEvent is an DisputeOpened event. + DisputeOpenedEvent EventType = iota + // RootUpdatedEvent is an RootUpdated event. + RootUpdatedEvent +) + +// Int gets the int for an event type. +func (i EventType) Int() uint8 { + return uint8(i) +} diff --git a/agents/contracts/lightmanager/topics.go b/agents/contracts/lightmanager/topics.go new file mode 100644 index 0000000000..fe5478b421 --- /dev/null +++ b/agents/contracts/lightmanager/topics.go @@ -0,0 +1,52 @@ +package lightmanager + +import ( + "bytes" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" +) + +func init() { + // set topics + var err error + + parsedLightManager, err := abi.JSON(strings.NewReader(LightManagerMetaData.ABI)) + if err != nil { + panic(err) + } + + DisputeOpenedTopic = parsedLightManager.Events["DisputeOpened"].ID + RootUpdatedTopic = parsedLightManager.Events["RootUpdated"].ID + + if DisputeOpenedTopic == (common.Hash{}) { + panic("DisputeOpenedTopic is nil") + } +} + +// DisputeOpenedTopic is the topic that gets emitted when the DisputeOpened event is called. +var DisputeOpenedTopic common.Hash + +// RootUpdatedTopic is the topic that gets emitted when the RootUpdates event is called. +var RootUpdatedTopic common.Hash + +// topicMap maps events to topics. +// this is returned as a function to assert immutability. +func topicMap() map[EventType]common.Hash { + return map[EventType]common.Hash{ + DisputeOpenedEvent: DisputeOpenedTopic, + RootUpdatedEvent: RootUpdatedTopic, + } +} + +// eventTypeFromTopic gets the event type from the topic +// returns nil if the topic is not found. +func eventTypeFromTopic(ogTopic common.Hash) *EventType { + for eventType, topic := range topicMap() { + if bytes.Equal(ogTopic.Bytes(), topic.Bytes()) { + return &eventType + } + } + return nil +} diff --git a/agents/contracts/test/receiptharness/generate.go b/agents/contracts/test/receiptharness/generate.go new file mode 100644 index 0000000000..833a68ea4e --- /dev/null +++ b/agents/contracts/test/receiptharness/generate.go @@ -0,0 +1,5 @@ +// Package receiptharness generates abi data for contract ReceiptHarness.t.sol +package receiptharness + +//go:generate go run github.com/synapsecns/sanguine/tools/abigen generate --sol ../../../../packages/contracts-core/flattened/ReceiptHarness.t.sol --pkg receiptharness --sol-version 0.8.17 --filename receiptharness +// line after go:generate cannot be left blank diff --git a/agents/contracts/test/receiptharness/helpers.go b/agents/contracts/test/receiptharness/helpers.go new file mode 100644 index 0000000000..fdb8d319d5 --- /dev/null +++ b/agents/contracts/test/receiptharness/helpers.go @@ -0,0 +1,37 @@ +package receiptharness + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" +) + +// ReceiptHarnessRef is a receipt harness reference +// +//nolint:golint +type ReceiptHarnessRef struct { + *ReceiptHarness + address common.Address +} + +// Address gets the address of the contract. +func (h ReceiptHarnessRef) Address() common.Address { + return h.address +} + +// NewReceiptHarnessRef creates a new receipt harness. +func NewReceiptHarnessRef(address common.Address, backend bind.ContractBackend) (*ReceiptHarnessRef, error) { + contract, err := NewReceiptHarness(address, backend) + if err != nil { + return nil, fmt.Errorf("could not create receipt harness: %w", err) + } + + return &ReceiptHarnessRef{ + ReceiptHarness: contract, + address: address, + }, nil +} + +var _ vm.ContractRef = &ReceiptHarnessRef{} diff --git a/agents/contracts/test/receiptharness/receiptharness.abigen.go b/agents/contracts/test/receiptharness/receiptharness.abigen.go new file mode 100644 index 0000000000..d0e54d9604 --- /dev/null +++ b/agents/contracts/test/receiptharness/receiptharness.abigen.go @@ -0,0 +1,1002 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package receiptharness + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// MemViewLibMetaData contains all meta data concerning the MemViewLib contract. +var MemViewLibMetaData = &bind.MetaData{ + ABI: "[]", + Bin: "0x60566037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220beab3f2401d4449e2e42960fbb1f7c101929749fa5a3041e7c849125dd282aed64736f6c63430008110033", +} + +// MemViewLibABI is the input ABI used to generate the binding from. +// Deprecated: Use MemViewLibMetaData.ABI instead. +var MemViewLibABI = MemViewLibMetaData.ABI + +// MemViewLibBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use MemViewLibMetaData.Bin instead. +var MemViewLibBin = MemViewLibMetaData.Bin + +// DeployMemViewLib deploys a new Ethereum contract, binding an instance of MemViewLib to it. +func DeployMemViewLib(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *MemViewLib, error) { + parsed, err := MemViewLibMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(MemViewLibBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &MemViewLib{MemViewLibCaller: MemViewLibCaller{contract: contract}, MemViewLibTransactor: MemViewLibTransactor{contract: contract}, MemViewLibFilterer: MemViewLibFilterer{contract: contract}}, nil +} + +// MemViewLib is an auto generated Go binding around an Ethereum contract. +type MemViewLib struct { + MemViewLibCaller // Read-only binding to the contract + MemViewLibTransactor // Write-only binding to the contract + MemViewLibFilterer // Log filterer for contract events +} + +// MemViewLibCaller is an auto generated read-only Go binding around an Ethereum contract. +type MemViewLibCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MemViewLibTransactor is an auto generated write-only Go binding around an Ethereum contract. +type MemViewLibTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MemViewLibFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type MemViewLibFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MemViewLibSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type MemViewLibSession struct { + Contract *MemViewLib // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// MemViewLibCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type MemViewLibCallerSession struct { + Contract *MemViewLibCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// MemViewLibTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type MemViewLibTransactorSession struct { + Contract *MemViewLibTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// MemViewLibRaw is an auto generated low-level Go binding around an Ethereum contract. +type MemViewLibRaw struct { + Contract *MemViewLib // Generic contract binding to access the raw methods on +} + +// MemViewLibCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type MemViewLibCallerRaw struct { + Contract *MemViewLibCaller // Generic read-only contract binding to access the raw methods on +} + +// MemViewLibTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type MemViewLibTransactorRaw struct { + Contract *MemViewLibTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewMemViewLib creates a new instance of MemViewLib, bound to a specific deployed contract. +func NewMemViewLib(address common.Address, backend bind.ContractBackend) (*MemViewLib, error) { + contract, err := bindMemViewLib(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &MemViewLib{MemViewLibCaller: MemViewLibCaller{contract: contract}, MemViewLibTransactor: MemViewLibTransactor{contract: contract}, MemViewLibFilterer: MemViewLibFilterer{contract: contract}}, nil +} + +// NewMemViewLibCaller creates a new read-only instance of MemViewLib, bound to a specific deployed contract. +func NewMemViewLibCaller(address common.Address, caller bind.ContractCaller) (*MemViewLibCaller, error) { + contract, err := bindMemViewLib(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &MemViewLibCaller{contract: contract}, nil +} + +// NewMemViewLibTransactor creates a new write-only instance of MemViewLib, bound to a specific deployed contract. +func NewMemViewLibTransactor(address common.Address, transactor bind.ContractTransactor) (*MemViewLibTransactor, error) { + contract, err := bindMemViewLib(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &MemViewLibTransactor{contract: contract}, nil +} + +// NewMemViewLibFilterer creates a new log filterer instance of MemViewLib, bound to a specific deployed contract. +func NewMemViewLibFilterer(address common.Address, filterer bind.ContractFilterer) (*MemViewLibFilterer, error) { + contract, err := bindMemViewLib(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &MemViewLibFilterer{contract: contract}, nil +} + +// bindMemViewLib binds a generic wrapper to an already deployed contract. +func bindMemViewLib(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(MemViewLibABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_MemViewLib *MemViewLibRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _MemViewLib.Contract.MemViewLibCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_MemViewLib *MemViewLibRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _MemViewLib.Contract.MemViewLibTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_MemViewLib *MemViewLibRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _MemViewLib.Contract.MemViewLibTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_MemViewLib *MemViewLibCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _MemViewLib.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_MemViewLib *MemViewLibTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _MemViewLib.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_MemViewLib *MemViewLibTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _MemViewLib.Contract.contract.Transact(opts, method, params...) +} + +// ReceiptHarnessMetaData contains all meta data concerning the ReceiptHarness contract. +var ReceiptHarnessMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"name\":\"IndexedTooMuch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OccupiedMemory\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PrecompileOutOfGas\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnallocatedMemory\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnformattedReceipt\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ViewOverrun\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"attNotary\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"castToReceipt\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"destination\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"a\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"b\",\"type\":\"bytes\"}],\"name\":\"equals\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"finalExecutor\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"firstExecutor\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"origin_\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"destination_\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"messageHash_\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"snapshotRoot_\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"stateIndex_\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"attNotary_\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"firstExecutor_\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"finalExecutor_\",\"type\":\"address\"}],\"name\":\"formatReceipt\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"hashInvalid\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"hashValid\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"isReceipt\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"messageHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"origin\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"snapshotRoot\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"stateIndex\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}]", + Sigs: map[string]string{ + "e152a8cb": "attNotary(bytes)", + "5cab5d3b": "castToReceipt(bytes)", + "c81aa9c8": "destination(bytes)", + "137e618a": "equals(bytes,bytes)", + "f7e6a05b": "finalExecutor(bytes)", + "27f2ee36": "firstExecutor(bytes)", + "c3bfda6c": "formatReceipt(uint32,uint32,bytes32,bytes32,uint8,address,address,address)", + "60cf3bf0": "hashInvalid(bytes)", + "730dbf63": "hashValid(bytes)", + "0bb3b580": "isReceipt(bytes)", + "ed54c3b6": "messageHash(bytes)", + "cb3eb0e1": "origin(bytes)", + "854dfcd7": "snapshotRoot(bytes)", + "595271d1": "stateIndex(bytes)", + }, + Bin: "0x608060405234801561001057600080fd5b50610c09806100206000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c8063854dfcd71161008c578063cb3eb0e111610066578063cb3eb0e1146102f5578063e152a8cb14610308578063ed54c3b61461031b578063f7e6a05b1461032e57600080fd5b8063854dfcd7146101db578063c3bfda6c146101ee578063c81aa9c8146102cd57600080fd5b8063595271d1116100c8578063595271d1146101625780635cab5d3b1461018757806360cf3bf0146101a7578063730dbf63146101c857600080fd5b80630bb3b580146100ef578063137e618a1461011757806327f2ee361461012a575b600080fd5b6101026100fd3660046109c6565b610341565b60405190151581526020015b60405180910390f35b6101026101253660046109fb565b61036d565b61013d6101383660046109c6565b61039c565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161010e565b6101756101703660046109c6565b6103af565b60405160ff909116815260200161010e565b61019a6101953660046109c6565b6103c2565b60405161010e9190610a5f565b6101ba6101b53660046109c6565b6103da565b60405190815260200161010e565b6101ba6101d63660046109c6565b6103f0565b6101ba6101e93660046109c6565b610406565b61019a6101fc366004610b08565b6040805160e0998a1b7fffffffff0000000000000000000000000000000000000000000000000000000090811660208301529890991b90971660248901526028880195909552604887019390935260f89190911b7fff00000000000000000000000000000000000000000000000000000000000000166068860152606090811b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000908116606987015291811b8216607d8601529190911b1660918301528051608581840301815260a5909201905290565b6102e06102db3660046109c6565b610419565b60405163ffffffff909116815260200161010e565b6102e06103033660046109c6565b61042c565b61013d6103163660046109c6565b61043f565b6101ba6103293660046109c6565b610452565b61013d61033c3660046109c6565b610465565b600061036761034f83610478565b6fffffffffffffffffffffffffffffffff1660851490565b92915050565b600061039561038361037e84610478565b610493565b61038f61037e86610478565b906104e5565b9392505050565b60006103676103aa83610501565b61050f565b60006103676103bd83610501565b61051e565b606060006103cf83610501565b905061039581610530565b60006103676103eb61037e84610478565b61058d565b600061036761040161037e84610478565b6105bb565b600061036761041483610501565b6105e7565b600061036761042783610501565b6105f9565b600061036761043a83610501565b610607565b600061036761044d83610501565b610615565b600061036761046083610501565b610622565b600061036761047383610501565b610631565b80516000906020830161048b818361063e565b949350505050565b600060856fffffffffffffffffffffffffffffffff8316146104e1576040517f76b4e13c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5090565b60006104f0826106a1565b6104f9846106a1565b149392505050565b600061036761037e83610478565b6000610367605d835b906106cc565b600061036760486001845b91906106d6565b6040518061054183602083016106f7565b506fffffffffffffffffffffffffffffffff83166000601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168301602001604052509052919050565b60006103677fdf42b2c0137811ba604f5c79e20c4d6b94770aa819cc524eca444056544f8ab7835b906107a6565b60006103677fb38669e8ca41a27fcd85729b868e8ab047d0f142073a017213e58f0a91e88ef3836105b5565b600061036760286020845b91906107e2565b600061036760048084610529565b600061036781600484610529565b6000610367604983610518565b600061036760086020846105f2565b6000610367607183610518565b60008061064b8385610b99565b905060405181111561065b575060005b80600003610695576040517f10bef38600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b608084901b831761048b565b6000806106ae8360801c90565b6fffffffffffffffffffffffffffffffff9390931690922092915050565b6000610395838360145b6000806106e48585856107e2565b602084900360031b1c9150509392505050565b6040516000906fffffffffffffffffffffffffffffffff841690608085901c9080851015610751576040517f4b2a158c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008386858560045afa905080610794576040517f7c7d772f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b608086901b8417979650505050505050565b6000816107b2846106a1565b60408051602081019390935282015260600160405160208183030381529060405280519060200120905092915050565b6000816000036107f457506000610395565b602082111561082f576040517f31d784a800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6fffffffffffffffffffffffffffffffff841661084c8385610b99565b1115610884576040517fa3b99ded00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600382901b60006108958660801c90565b909401517f80000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff929092019190911d16949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f83011261092c57600080fd5b813567ffffffffffffffff80821115610947576109476108ec565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171561098d5761098d6108ec565b816040528381528660208588010111156109a657600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000602082840312156109d857600080fd5b813567ffffffffffffffff8111156109ef57600080fd5b61048b8482850161091b565b60008060408385031215610a0e57600080fd5b823567ffffffffffffffff80821115610a2657600080fd5b610a328683870161091b565b93506020850135915080821115610a4857600080fd5b50610a558582860161091b565b9150509250929050565b600060208083528351808285015260005b81811015610a8c57858101830151858201604001528201610a70565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b803563ffffffff81168114610adf57600080fd5b919050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610adf57600080fd5b600080600080600080600080610100898b031215610b2557600080fd5b610b2e89610acb565b9750610b3c60208a01610acb565b96506040890135955060608901359450608089013560ff81168114610b6057600080fd5b9350610b6e60a08a01610ae4565b9250610b7c60c08a01610ae4565b9150610b8a60e08a01610ae4565b90509295985092959890939650565b80820180821115610367577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea264697066735822122039924413aea489827d6ab16c8f3b9ba78c921ec5fa110ef66f52da8359b9ee1064736f6c63430008110033", +} + +// ReceiptHarnessABI is the input ABI used to generate the binding from. +// Deprecated: Use ReceiptHarnessMetaData.ABI instead. +var ReceiptHarnessABI = ReceiptHarnessMetaData.ABI + +// Deprecated: Use ReceiptHarnessMetaData.Sigs instead. +// ReceiptHarnessFuncSigs maps the 4-byte function signature to its string representation. +var ReceiptHarnessFuncSigs = ReceiptHarnessMetaData.Sigs + +// ReceiptHarnessBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use ReceiptHarnessMetaData.Bin instead. +var ReceiptHarnessBin = ReceiptHarnessMetaData.Bin + +// DeployReceiptHarness deploys a new Ethereum contract, binding an instance of ReceiptHarness to it. +func DeployReceiptHarness(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *ReceiptHarness, error) { + parsed, err := ReceiptHarnessMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ReceiptHarnessBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ReceiptHarness{ReceiptHarnessCaller: ReceiptHarnessCaller{contract: contract}, ReceiptHarnessTransactor: ReceiptHarnessTransactor{contract: contract}, ReceiptHarnessFilterer: ReceiptHarnessFilterer{contract: contract}}, nil +} + +// ReceiptHarness is an auto generated Go binding around an Ethereum contract. +type ReceiptHarness struct { + ReceiptHarnessCaller // Read-only binding to the contract + ReceiptHarnessTransactor // Write-only binding to the contract + ReceiptHarnessFilterer // Log filterer for contract events +} + +// ReceiptHarnessCaller is an auto generated read-only Go binding around an Ethereum contract. +type ReceiptHarnessCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ReceiptHarnessTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ReceiptHarnessTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ReceiptHarnessFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ReceiptHarnessFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ReceiptHarnessSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ReceiptHarnessSession struct { + Contract *ReceiptHarness // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ReceiptHarnessCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ReceiptHarnessCallerSession struct { + Contract *ReceiptHarnessCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ReceiptHarnessTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ReceiptHarnessTransactorSession struct { + Contract *ReceiptHarnessTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ReceiptHarnessRaw is an auto generated low-level Go binding around an Ethereum contract. +type ReceiptHarnessRaw struct { + Contract *ReceiptHarness // Generic contract binding to access the raw methods on +} + +// ReceiptHarnessCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ReceiptHarnessCallerRaw struct { + Contract *ReceiptHarnessCaller // Generic read-only contract binding to access the raw methods on +} + +// ReceiptHarnessTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ReceiptHarnessTransactorRaw struct { + Contract *ReceiptHarnessTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewReceiptHarness creates a new instance of ReceiptHarness, bound to a specific deployed contract. +func NewReceiptHarness(address common.Address, backend bind.ContractBackend) (*ReceiptHarness, error) { + contract, err := bindReceiptHarness(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &ReceiptHarness{ReceiptHarnessCaller: ReceiptHarnessCaller{contract: contract}, ReceiptHarnessTransactor: ReceiptHarnessTransactor{contract: contract}, ReceiptHarnessFilterer: ReceiptHarnessFilterer{contract: contract}}, nil +} + +// NewReceiptHarnessCaller creates a new read-only instance of ReceiptHarness, bound to a specific deployed contract. +func NewReceiptHarnessCaller(address common.Address, caller bind.ContractCaller) (*ReceiptHarnessCaller, error) { + contract, err := bindReceiptHarness(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ReceiptHarnessCaller{contract: contract}, nil +} + +// NewReceiptHarnessTransactor creates a new write-only instance of ReceiptHarness, bound to a specific deployed contract. +func NewReceiptHarnessTransactor(address common.Address, transactor bind.ContractTransactor) (*ReceiptHarnessTransactor, error) { + contract, err := bindReceiptHarness(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ReceiptHarnessTransactor{contract: contract}, nil +} + +// NewReceiptHarnessFilterer creates a new log filterer instance of ReceiptHarness, bound to a specific deployed contract. +func NewReceiptHarnessFilterer(address common.Address, filterer bind.ContractFilterer) (*ReceiptHarnessFilterer, error) { + contract, err := bindReceiptHarness(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ReceiptHarnessFilterer{contract: contract}, nil +} + +// bindReceiptHarness binds a generic wrapper to an already deployed contract. +func bindReceiptHarness(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ReceiptHarnessABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ReceiptHarness *ReceiptHarnessRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ReceiptHarness.Contract.ReceiptHarnessCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ReceiptHarness *ReceiptHarnessRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ReceiptHarness.Contract.ReceiptHarnessTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ReceiptHarness *ReceiptHarnessRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ReceiptHarness.Contract.ReceiptHarnessTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ReceiptHarness *ReceiptHarnessCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ReceiptHarness.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ReceiptHarness *ReceiptHarnessTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ReceiptHarness.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ReceiptHarness *ReceiptHarnessTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ReceiptHarness.Contract.contract.Transact(opts, method, params...) +} + +// AttNotary is a free data retrieval call binding the contract method 0xe152a8cb. +// +// Solidity: function attNotary(bytes payload) pure returns(address) +func (_ReceiptHarness *ReceiptHarnessCaller) AttNotary(opts *bind.CallOpts, payload []byte) (common.Address, error) { + var out []interface{} + err := _ReceiptHarness.contract.Call(opts, &out, "attNotary", payload) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// AttNotary is a free data retrieval call binding the contract method 0xe152a8cb. +// +// Solidity: function attNotary(bytes payload) pure returns(address) +func (_ReceiptHarness *ReceiptHarnessSession) AttNotary(payload []byte) (common.Address, error) { + return _ReceiptHarness.Contract.AttNotary(&_ReceiptHarness.CallOpts, payload) +} + +// AttNotary is a free data retrieval call binding the contract method 0xe152a8cb. +// +// Solidity: function attNotary(bytes payload) pure returns(address) +func (_ReceiptHarness *ReceiptHarnessCallerSession) AttNotary(payload []byte) (common.Address, error) { + return _ReceiptHarness.Contract.AttNotary(&_ReceiptHarness.CallOpts, payload) +} + +// CastToReceipt is a free data retrieval call binding the contract method 0x5cab5d3b. +// +// Solidity: function castToReceipt(bytes payload) view returns(bytes) +func (_ReceiptHarness *ReceiptHarnessCaller) CastToReceipt(opts *bind.CallOpts, payload []byte) ([]byte, error) { + var out []interface{} + err := _ReceiptHarness.contract.Call(opts, &out, "castToReceipt", payload) + + if err != nil { + return *new([]byte), err + } + + out0 := *abi.ConvertType(out[0], new([]byte)).(*[]byte) + + return out0, err + +} + +// CastToReceipt is a free data retrieval call binding the contract method 0x5cab5d3b. +// +// Solidity: function castToReceipt(bytes payload) view returns(bytes) +func (_ReceiptHarness *ReceiptHarnessSession) CastToReceipt(payload []byte) ([]byte, error) { + return _ReceiptHarness.Contract.CastToReceipt(&_ReceiptHarness.CallOpts, payload) +} + +// CastToReceipt is a free data retrieval call binding the contract method 0x5cab5d3b. +// +// Solidity: function castToReceipt(bytes payload) view returns(bytes) +func (_ReceiptHarness *ReceiptHarnessCallerSession) CastToReceipt(payload []byte) ([]byte, error) { + return _ReceiptHarness.Contract.CastToReceipt(&_ReceiptHarness.CallOpts, payload) +} + +// Destination is a free data retrieval call binding the contract method 0xc81aa9c8. +// +// Solidity: function destination(bytes payload) pure returns(uint32) +func (_ReceiptHarness *ReceiptHarnessCaller) Destination(opts *bind.CallOpts, payload []byte) (uint32, error) { + var out []interface{} + err := _ReceiptHarness.contract.Call(opts, &out, "destination", payload) + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// Destination is a free data retrieval call binding the contract method 0xc81aa9c8. +// +// Solidity: function destination(bytes payload) pure returns(uint32) +func (_ReceiptHarness *ReceiptHarnessSession) Destination(payload []byte) (uint32, error) { + return _ReceiptHarness.Contract.Destination(&_ReceiptHarness.CallOpts, payload) +} + +// Destination is a free data retrieval call binding the contract method 0xc81aa9c8. +// +// Solidity: function destination(bytes payload) pure returns(uint32) +func (_ReceiptHarness *ReceiptHarnessCallerSession) Destination(payload []byte) (uint32, error) { + return _ReceiptHarness.Contract.Destination(&_ReceiptHarness.CallOpts, payload) +} + +// Equals is a free data retrieval call binding the contract method 0x137e618a. +// +// Solidity: function equals(bytes a, bytes b) pure returns(bool) +func (_ReceiptHarness *ReceiptHarnessCaller) Equals(opts *bind.CallOpts, a []byte, b []byte) (bool, error) { + var out []interface{} + err := _ReceiptHarness.contract.Call(opts, &out, "equals", a, b) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// Equals is a free data retrieval call binding the contract method 0x137e618a. +// +// Solidity: function equals(bytes a, bytes b) pure returns(bool) +func (_ReceiptHarness *ReceiptHarnessSession) Equals(a []byte, b []byte) (bool, error) { + return _ReceiptHarness.Contract.Equals(&_ReceiptHarness.CallOpts, a, b) +} + +// Equals is a free data retrieval call binding the contract method 0x137e618a. +// +// Solidity: function equals(bytes a, bytes b) pure returns(bool) +func (_ReceiptHarness *ReceiptHarnessCallerSession) Equals(a []byte, b []byte) (bool, error) { + return _ReceiptHarness.Contract.Equals(&_ReceiptHarness.CallOpts, a, b) +} + +// FinalExecutor is a free data retrieval call binding the contract method 0xf7e6a05b. +// +// Solidity: function finalExecutor(bytes payload) pure returns(address) +func (_ReceiptHarness *ReceiptHarnessCaller) FinalExecutor(opts *bind.CallOpts, payload []byte) (common.Address, error) { + var out []interface{} + err := _ReceiptHarness.contract.Call(opts, &out, "finalExecutor", payload) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// FinalExecutor is a free data retrieval call binding the contract method 0xf7e6a05b. +// +// Solidity: function finalExecutor(bytes payload) pure returns(address) +func (_ReceiptHarness *ReceiptHarnessSession) FinalExecutor(payload []byte) (common.Address, error) { + return _ReceiptHarness.Contract.FinalExecutor(&_ReceiptHarness.CallOpts, payload) +} + +// FinalExecutor is a free data retrieval call binding the contract method 0xf7e6a05b. +// +// Solidity: function finalExecutor(bytes payload) pure returns(address) +func (_ReceiptHarness *ReceiptHarnessCallerSession) FinalExecutor(payload []byte) (common.Address, error) { + return _ReceiptHarness.Contract.FinalExecutor(&_ReceiptHarness.CallOpts, payload) +} + +// FirstExecutor is a free data retrieval call binding the contract method 0x27f2ee36. +// +// Solidity: function firstExecutor(bytes payload) pure returns(address) +func (_ReceiptHarness *ReceiptHarnessCaller) FirstExecutor(opts *bind.CallOpts, payload []byte) (common.Address, error) { + var out []interface{} + err := _ReceiptHarness.contract.Call(opts, &out, "firstExecutor", payload) + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// FirstExecutor is a free data retrieval call binding the contract method 0x27f2ee36. +// +// Solidity: function firstExecutor(bytes payload) pure returns(address) +func (_ReceiptHarness *ReceiptHarnessSession) FirstExecutor(payload []byte) (common.Address, error) { + return _ReceiptHarness.Contract.FirstExecutor(&_ReceiptHarness.CallOpts, payload) +} + +// FirstExecutor is a free data retrieval call binding the contract method 0x27f2ee36. +// +// Solidity: function firstExecutor(bytes payload) pure returns(address) +func (_ReceiptHarness *ReceiptHarnessCallerSession) FirstExecutor(payload []byte) (common.Address, error) { + return _ReceiptHarness.Contract.FirstExecutor(&_ReceiptHarness.CallOpts, payload) +} + +// FormatReceipt is a free data retrieval call binding the contract method 0xc3bfda6c. +// +// Solidity: function formatReceipt(uint32 origin_, uint32 destination_, bytes32 messageHash_, bytes32 snapshotRoot_, uint8 stateIndex_, address attNotary_, address firstExecutor_, address finalExecutor_) pure returns(bytes) +func (_ReceiptHarness *ReceiptHarnessCaller) FormatReceipt(opts *bind.CallOpts, origin_ uint32, destination_ uint32, messageHash_ [32]byte, snapshotRoot_ [32]byte, stateIndex_ uint8, attNotary_ common.Address, firstExecutor_ common.Address, finalExecutor_ common.Address) ([]byte, error) { + var out []interface{} + err := _ReceiptHarness.contract.Call(opts, &out, "formatReceipt", origin_, destination_, messageHash_, snapshotRoot_, stateIndex_, attNotary_, firstExecutor_, finalExecutor_) + + if err != nil { + return *new([]byte), err + } + + out0 := *abi.ConvertType(out[0], new([]byte)).(*[]byte) + + return out0, err + +} + +// FormatReceipt is a free data retrieval call binding the contract method 0xc3bfda6c. +// +// Solidity: function formatReceipt(uint32 origin_, uint32 destination_, bytes32 messageHash_, bytes32 snapshotRoot_, uint8 stateIndex_, address attNotary_, address firstExecutor_, address finalExecutor_) pure returns(bytes) +func (_ReceiptHarness *ReceiptHarnessSession) FormatReceipt(origin_ uint32, destination_ uint32, messageHash_ [32]byte, snapshotRoot_ [32]byte, stateIndex_ uint8, attNotary_ common.Address, firstExecutor_ common.Address, finalExecutor_ common.Address) ([]byte, error) { + return _ReceiptHarness.Contract.FormatReceipt(&_ReceiptHarness.CallOpts, origin_, destination_, messageHash_, snapshotRoot_, stateIndex_, attNotary_, firstExecutor_, finalExecutor_) +} + +// FormatReceipt is a free data retrieval call binding the contract method 0xc3bfda6c. +// +// Solidity: function formatReceipt(uint32 origin_, uint32 destination_, bytes32 messageHash_, bytes32 snapshotRoot_, uint8 stateIndex_, address attNotary_, address firstExecutor_, address finalExecutor_) pure returns(bytes) +func (_ReceiptHarness *ReceiptHarnessCallerSession) FormatReceipt(origin_ uint32, destination_ uint32, messageHash_ [32]byte, snapshotRoot_ [32]byte, stateIndex_ uint8, attNotary_ common.Address, firstExecutor_ common.Address, finalExecutor_ common.Address) ([]byte, error) { + return _ReceiptHarness.Contract.FormatReceipt(&_ReceiptHarness.CallOpts, origin_, destination_, messageHash_, snapshotRoot_, stateIndex_, attNotary_, firstExecutor_, finalExecutor_) +} + +// HashInvalid is a free data retrieval call binding the contract method 0x60cf3bf0. +// +// Solidity: function hashInvalid(bytes payload) pure returns(bytes32) +func (_ReceiptHarness *ReceiptHarnessCaller) HashInvalid(opts *bind.CallOpts, payload []byte) ([32]byte, error) { + var out []interface{} + err := _ReceiptHarness.contract.Call(opts, &out, "hashInvalid", payload) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// HashInvalid is a free data retrieval call binding the contract method 0x60cf3bf0. +// +// Solidity: function hashInvalid(bytes payload) pure returns(bytes32) +func (_ReceiptHarness *ReceiptHarnessSession) HashInvalid(payload []byte) ([32]byte, error) { + return _ReceiptHarness.Contract.HashInvalid(&_ReceiptHarness.CallOpts, payload) +} + +// HashInvalid is a free data retrieval call binding the contract method 0x60cf3bf0. +// +// Solidity: function hashInvalid(bytes payload) pure returns(bytes32) +func (_ReceiptHarness *ReceiptHarnessCallerSession) HashInvalid(payload []byte) ([32]byte, error) { + return _ReceiptHarness.Contract.HashInvalid(&_ReceiptHarness.CallOpts, payload) +} + +// HashValid is a free data retrieval call binding the contract method 0x730dbf63. +// +// Solidity: function hashValid(bytes payload) pure returns(bytes32) +func (_ReceiptHarness *ReceiptHarnessCaller) HashValid(opts *bind.CallOpts, payload []byte) ([32]byte, error) { + var out []interface{} + err := _ReceiptHarness.contract.Call(opts, &out, "hashValid", payload) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// HashValid is a free data retrieval call binding the contract method 0x730dbf63. +// +// Solidity: function hashValid(bytes payload) pure returns(bytes32) +func (_ReceiptHarness *ReceiptHarnessSession) HashValid(payload []byte) ([32]byte, error) { + return _ReceiptHarness.Contract.HashValid(&_ReceiptHarness.CallOpts, payload) +} + +// HashValid is a free data retrieval call binding the contract method 0x730dbf63. +// +// Solidity: function hashValid(bytes payload) pure returns(bytes32) +func (_ReceiptHarness *ReceiptHarnessCallerSession) HashValid(payload []byte) ([32]byte, error) { + return _ReceiptHarness.Contract.HashValid(&_ReceiptHarness.CallOpts, payload) +} + +// IsReceipt is a free data retrieval call binding the contract method 0x0bb3b580. +// +// Solidity: function isReceipt(bytes payload) pure returns(bool) +func (_ReceiptHarness *ReceiptHarnessCaller) IsReceipt(opts *bind.CallOpts, payload []byte) (bool, error) { + var out []interface{} + err := _ReceiptHarness.contract.Call(opts, &out, "isReceipt", payload) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsReceipt is a free data retrieval call binding the contract method 0x0bb3b580. +// +// Solidity: function isReceipt(bytes payload) pure returns(bool) +func (_ReceiptHarness *ReceiptHarnessSession) IsReceipt(payload []byte) (bool, error) { + return _ReceiptHarness.Contract.IsReceipt(&_ReceiptHarness.CallOpts, payload) +} + +// IsReceipt is a free data retrieval call binding the contract method 0x0bb3b580. +// +// Solidity: function isReceipt(bytes payload) pure returns(bool) +func (_ReceiptHarness *ReceiptHarnessCallerSession) IsReceipt(payload []byte) (bool, error) { + return _ReceiptHarness.Contract.IsReceipt(&_ReceiptHarness.CallOpts, payload) +} + +// MessageHash is a free data retrieval call binding the contract method 0xed54c3b6. +// +// Solidity: function messageHash(bytes payload) pure returns(bytes32) +func (_ReceiptHarness *ReceiptHarnessCaller) MessageHash(opts *bind.CallOpts, payload []byte) ([32]byte, error) { + var out []interface{} + err := _ReceiptHarness.contract.Call(opts, &out, "messageHash", payload) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// MessageHash is a free data retrieval call binding the contract method 0xed54c3b6. +// +// Solidity: function messageHash(bytes payload) pure returns(bytes32) +func (_ReceiptHarness *ReceiptHarnessSession) MessageHash(payload []byte) ([32]byte, error) { + return _ReceiptHarness.Contract.MessageHash(&_ReceiptHarness.CallOpts, payload) +} + +// MessageHash is a free data retrieval call binding the contract method 0xed54c3b6. +// +// Solidity: function messageHash(bytes payload) pure returns(bytes32) +func (_ReceiptHarness *ReceiptHarnessCallerSession) MessageHash(payload []byte) ([32]byte, error) { + return _ReceiptHarness.Contract.MessageHash(&_ReceiptHarness.CallOpts, payload) +} + +// Origin is a free data retrieval call binding the contract method 0xcb3eb0e1. +// +// Solidity: function origin(bytes payload) pure returns(uint32) +func (_ReceiptHarness *ReceiptHarnessCaller) Origin(opts *bind.CallOpts, payload []byte) (uint32, error) { + var out []interface{} + err := _ReceiptHarness.contract.Call(opts, &out, "origin", payload) + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// Origin is a free data retrieval call binding the contract method 0xcb3eb0e1. +// +// Solidity: function origin(bytes payload) pure returns(uint32) +func (_ReceiptHarness *ReceiptHarnessSession) Origin(payload []byte) (uint32, error) { + return _ReceiptHarness.Contract.Origin(&_ReceiptHarness.CallOpts, payload) +} + +// Origin is a free data retrieval call binding the contract method 0xcb3eb0e1. +// +// Solidity: function origin(bytes payload) pure returns(uint32) +func (_ReceiptHarness *ReceiptHarnessCallerSession) Origin(payload []byte) (uint32, error) { + return _ReceiptHarness.Contract.Origin(&_ReceiptHarness.CallOpts, payload) +} + +// SnapshotRoot is a free data retrieval call binding the contract method 0x854dfcd7. +// +// Solidity: function snapshotRoot(bytes payload) pure returns(bytes32) +func (_ReceiptHarness *ReceiptHarnessCaller) SnapshotRoot(opts *bind.CallOpts, payload []byte) ([32]byte, error) { + var out []interface{} + err := _ReceiptHarness.contract.Call(opts, &out, "snapshotRoot", payload) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// SnapshotRoot is a free data retrieval call binding the contract method 0x854dfcd7. +// +// Solidity: function snapshotRoot(bytes payload) pure returns(bytes32) +func (_ReceiptHarness *ReceiptHarnessSession) SnapshotRoot(payload []byte) ([32]byte, error) { + return _ReceiptHarness.Contract.SnapshotRoot(&_ReceiptHarness.CallOpts, payload) +} + +// SnapshotRoot is a free data retrieval call binding the contract method 0x854dfcd7. +// +// Solidity: function snapshotRoot(bytes payload) pure returns(bytes32) +func (_ReceiptHarness *ReceiptHarnessCallerSession) SnapshotRoot(payload []byte) ([32]byte, error) { + return _ReceiptHarness.Contract.SnapshotRoot(&_ReceiptHarness.CallOpts, payload) +} + +// StateIndex is a free data retrieval call binding the contract method 0x595271d1. +// +// Solidity: function stateIndex(bytes payload) pure returns(uint8) +func (_ReceiptHarness *ReceiptHarnessCaller) StateIndex(opts *bind.CallOpts, payload []byte) (uint8, error) { + var out []interface{} + err := _ReceiptHarness.contract.Call(opts, &out, "stateIndex", payload) + + if err != nil { + return *new(uint8), err + } + + out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) + + return out0, err + +} + +// StateIndex is a free data retrieval call binding the contract method 0x595271d1. +// +// Solidity: function stateIndex(bytes payload) pure returns(uint8) +func (_ReceiptHarness *ReceiptHarnessSession) StateIndex(payload []byte) (uint8, error) { + return _ReceiptHarness.Contract.StateIndex(&_ReceiptHarness.CallOpts, payload) +} + +// StateIndex is a free data retrieval call binding the contract method 0x595271d1. +// +// Solidity: function stateIndex(bytes payload) pure returns(uint8) +func (_ReceiptHarness *ReceiptHarnessCallerSession) StateIndex(payload []byte) (uint8, error) { + return _ReceiptHarness.Contract.StateIndex(&_ReceiptHarness.CallOpts, payload) +} + +// ReceiptLibMetaData contains all meta data concerning the ReceiptLib contract. +var ReceiptLibMetaData = &bind.MetaData{ + ABI: "[]", + Bin: "0x60566037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220f103584dc9fa399972815cba256acdf18451db63a1ed6da7fafad4bb4653dcf564736f6c63430008110033", +} + +// ReceiptLibABI is the input ABI used to generate the binding from. +// Deprecated: Use ReceiptLibMetaData.ABI instead. +var ReceiptLibABI = ReceiptLibMetaData.ABI + +// ReceiptLibBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use ReceiptLibMetaData.Bin instead. +var ReceiptLibBin = ReceiptLibMetaData.Bin + +// DeployReceiptLib deploys a new Ethereum contract, binding an instance of ReceiptLib to it. +func DeployReceiptLib(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *ReceiptLib, error) { + parsed, err := ReceiptLibMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ReceiptLibBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ReceiptLib{ReceiptLibCaller: ReceiptLibCaller{contract: contract}, ReceiptLibTransactor: ReceiptLibTransactor{contract: contract}, ReceiptLibFilterer: ReceiptLibFilterer{contract: contract}}, nil +} + +// ReceiptLib is an auto generated Go binding around an Ethereum contract. +type ReceiptLib struct { + ReceiptLibCaller // Read-only binding to the contract + ReceiptLibTransactor // Write-only binding to the contract + ReceiptLibFilterer // Log filterer for contract events +} + +// ReceiptLibCaller is an auto generated read-only Go binding around an Ethereum contract. +type ReceiptLibCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ReceiptLibTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ReceiptLibTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ReceiptLibFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ReceiptLibFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ReceiptLibSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ReceiptLibSession struct { + Contract *ReceiptLib // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ReceiptLibCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ReceiptLibCallerSession struct { + Contract *ReceiptLibCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ReceiptLibTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ReceiptLibTransactorSession struct { + Contract *ReceiptLibTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ReceiptLibRaw is an auto generated low-level Go binding around an Ethereum contract. +type ReceiptLibRaw struct { + Contract *ReceiptLib // Generic contract binding to access the raw methods on +} + +// ReceiptLibCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ReceiptLibCallerRaw struct { + Contract *ReceiptLibCaller // Generic read-only contract binding to access the raw methods on +} + +// ReceiptLibTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ReceiptLibTransactorRaw struct { + Contract *ReceiptLibTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewReceiptLib creates a new instance of ReceiptLib, bound to a specific deployed contract. +func NewReceiptLib(address common.Address, backend bind.ContractBackend) (*ReceiptLib, error) { + contract, err := bindReceiptLib(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &ReceiptLib{ReceiptLibCaller: ReceiptLibCaller{contract: contract}, ReceiptLibTransactor: ReceiptLibTransactor{contract: contract}, ReceiptLibFilterer: ReceiptLibFilterer{contract: contract}}, nil +} + +// NewReceiptLibCaller creates a new read-only instance of ReceiptLib, bound to a specific deployed contract. +func NewReceiptLibCaller(address common.Address, caller bind.ContractCaller) (*ReceiptLibCaller, error) { + contract, err := bindReceiptLib(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ReceiptLibCaller{contract: contract}, nil +} + +// NewReceiptLibTransactor creates a new write-only instance of ReceiptLib, bound to a specific deployed contract. +func NewReceiptLibTransactor(address common.Address, transactor bind.ContractTransactor) (*ReceiptLibTransactor, error) { + contract, err := bindReceiptLib(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ReceiptLibTransactor{contract: contract}, nil +} + +// NewReceiptLibFilterer creates a new log filterer instance of ReceiptLib, bound to a specific deployed contract. +func NewReceiptLibFilterer(address common.Address, filterer bind.ContractFilterer) (*ReceiptLibFilterer, error) { + contract, err := bindReceiptLib(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ReceiptLibFilterer{contract: contract}, nil +} + +// bindReceiptLib binds a generic wrapper to an already deployed contract. +func bindReceiptLib(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ReceiptLibABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ReceiptLib *ReceiptLibRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ReceiptLib.Contract.ReceiptLibCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ReceiptLib *ReceiptLibRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ReceiptLib.Contract.ReceiptLibTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ReceiptLib *ReceiptLibRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ReceiptLib.Contract.ReceiptLibTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ReceiptLib *ReceiptLibCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ReceiptLib.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ReceiptLib *ReceiptLibTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ReceiptLib.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ReceiptLib *ReceiptLibTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ReceiptLib.Contract.contract.Transact(opts, method, params...) +} diff --git a/agents/contracts/test/receiptharness/receiptharness.contractinfo.json b/agents/contracts/test/receiptharness/receiptharness.contractinfo.json new file mode 100644 index 0000000000..e2309edf6b --- /dev/null +++ b/agents/contracts/test/receiptharness/receiptharness.contractinfo.json @@ -0,0 +1 @@ +{"solidity/ReceiptHarness.t.sol:MemViewLib":{"code":"0x60566037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220beab3f2401d4449e2e42960fbb1f7c101929749fa5a3041e7c849125dd282aed64736f6c63430008110033","runtime-code":"0x73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220beab3f2401d4449e2e42960fbb1f7c101929749fa5a3041e7c849125dd282aed64736f6c63430008110033","info":{"source":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.17;\n\n// ══════════════════════════════ INVALID CALLER ═══════════════════════════════\n\nerror CallerNotAgentManager();\nerror CallerNotDestination();\nerror CallerNotInbox();\nerror CallerNotSummit();\n\n// ══════════════════════════════ INCORRECT DATA ═══════════════════════════════\n\nerror IncorrectAttestation();\nerror IncorrectAgentDomain();\nerror IncorrectAgentIndex();\nerror IncorrectAgentProof();\nerror IncorrectDataHash();\nerror IncorrectDestinationDomain();\nerror IncorrectOriginDomain();\nerror IncorrectSnapshotProof();\nerror IncorrectSnapshotRoot();\nerror IncorrectState();\nerror IncorrectStatesAmount();\nerror IncorrectTipsProof();\nerror IncorrectVersionLength();\n\nerror IncorrectNonce();\nerror IncorrectSender();\nerror IncorrectRecipient();\n\nerror FlagOutOfRange();\nerror IndexOutOfRange();\nerror NonceOutOfRange();\n\nerror OutdatedNonce();\n\nerror UnformattedAttestation();\nerror UnformattedAttestationReport();\nerror UnformattedBaseMessage();\nerror UnformattedCallData();\nerror UnformattedCallDataPrefix();\nerror UnformattedMessage();\nerror UnformattedReceipt();\nerror UnformattedReceiptReport();\nerror UnformattedSignature();\nerror UnformattedSnapshot();\nerror UnformattedState();\nerror UnformattedStateReport();\n\n// ═══════════════════════════════ MERKLE TREES ════════════════════════════════\n\nerror LeafNotProven();\nerror MerkleTreeFull();\nerror NotEnoughLeafs();\nerror TreeHeightTooLow();\n\n// ═════════════════════════════ OPTIMISTIC PERIOD ═════════════════════════════\n\nerror BaseClientOptimisticPeriod();\nerror MessageOptimisticPeriod();\nerror SlashAgentOptimisticPeriod();\nerror WithdrawTipsOptimisticPeriod();\nerror ZeroProofMaturity();\n\n// ═══════════════════════════════ AGENT MANAGER ═══════════════════════════════\n\nerror AgentNotGuard();\nerror AgentNotNotary();\n\nerror AgentCantBeAdded();\nerror AgentNotActive();\nerror AgentNotActiveNorUnstaking();\nerror AgentNotFraudulent();\nerror AgentNotUnstaking();\nerror AgentUnknown();\n\nerror DisputeAlreadyResolved();\nerror DisputeNotOpened();\nerror DisputeNotStuck();\nerror GuardInDispute();\nerror NotaryInDispute();\n\nerror MustBeSynapseDomain();\nerror SynapseDomainForbidden();\n\n// ════════════════════════════════ DESTINATION ════════════════════════════════\n\nerror AlreadyExecuted();\nerror AlreadyFailed();\nerror DuplicatedSnapshotRoot();\nerror IncorrectMagicValue();\nerror GasLimitTooLow();\nerror GasSuppliedTooLow();\n\n// ══════════════════════════════════ ORIGIN ═══════════════════════════════════\n\nerror ContentLengthTooBig();\nerror EthTransferFailed();\nerror InsufficientEthBalance();\n\n// ════════════════════════════════ GAS ORACLE ═════════════════════════════════\n\nerror LocalGasDataNotSet();\nerror RemoteGasDataNotSet();\n\n// ═══════════════════════════════════ TIPS ════════════════════════════════════\n\nerror TipsClaimMoreThanEarned();\nerror TipsClaimZero();\nerror TipsOverflow();\nerror TipsValueTooLow();\n\n// ════════════════════════════════ MEMORY VIEW ════════════════════════════════\n\nerror IndexedTooMuch();\nerror ViewOverrun();\nerror OccupiedMemory();\nerror UnallocatedMemory();\nerror PrecompileOutOfGas();\n\n// ═════════════════════════════════ MULTICALL ═════════════════════════════════\n\nerror MulticallFailed();\n\n/// @dev MemView is an untyped view over a portion of memory to be used instead of `bytes memory`\ntype MemView is uint256;\n\n/// @dev Attach library functions to MemView\nusing MemViewLib for MemView global;\n\n/// @notice Library for operations with the memory views.\n/// Forked from https://github.com/summa-tx/memview-sol with several breaking changes:\n/// - The codebase is ported to Solidity 0.8\n/// - Custom errors are added\n/// - The runtime type checking is replaced with compile-time check provided by User-Defined Value Types\n/// https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types\n/// - uint256 is used as the underlying type for the \"memory view\" instead of bytes29.\n/// It is wrapped into MemView custom type in order not to be confused with actual integers.\n/// - Therefore the \"type\" field is discarded, allowing to allocate 16 bytes for both view location and length\n/// - The documentation is expanded\n/// - Library functions unused by the rest of the codebase are removed\n// - Very pretty code separators are added :)\nlibrary MemViewLib {\n /// @notice Stack layout for uint256 (from highest bits to lowest)\n /// (32 .. 16] loc 16 bytes Memory address of underlying bytes\n /// (16 .. 00] len 16 bytes Length of underlying bytes\n\n // ═══════════════════════════════════════════ BUILDING MEMORY VIEW ════════════════════════════════════════════════\n\n /**\n * @notice Instantiate a new untyped memory view. This should generally not be called directly.\n * Prefer `ref` wherever possible.\n * @param loc_ The memory address\n * @param len_ The length\n * @return The new view with the specified location and length\n */\n function build(uint256 loc_, uint256 len_) internal pure returns (MemView) {\n uint256 end_ = loc_ + len_;\n // Make sure that a view is not constructed that points to unallocated memory\n // as this could be indicative of a buffer overflow attack\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n if gt(end_, mload(0x40)) { end_ := 0 }\n }\n if (end_ == 0) {\n revert UnallocatedMemory();\n }\n return _unsafeBuildUnchecked(loc_, len_);\n }\n\n /**\n * @notice Instantiate a memory view from a byte array.\n * @dev Note that due to Solidity memory representation, it is not possible to\n * implement a deref, as the `bytes` type stores its len in memory.\n * @param arr The byte array\n * @return The memory view over the provided byte array\n */\n function ref(bytes memory arr) internal pure returns (MemView) {\n uint256 len_ = arr.length;\n // `bytes arr` is stored in memory in the following way\n // 1. First, uint256 arr.length is stored. That requires 32 bytes (0x20).\n // 2. Then, the array data is stored.\n uint256 loc_;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // We add 0x20, so that the view starts exactly where the array data starts\n loc_ := add(arr, 0x20)\n }\n return build(loc_, len_);\n }\n\n // ════════════════════════════════════════════ CLONING MEMORY VIEW ════════════════════════════════════════════════\n\n /**\n * @notice Copies the referenced memory to a new loc in memory, returning a `bytes` pointing to the new memory.\n * @param memView The memory view\n * @return arr The cloned byte array\n */\n function clone(MemView memView) internal view returns (bytes memory arr) {\n uint256 ptr;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Load unused memory pointer\n ptr := mload(0x40)\n // This is where the byte array will be stored\n arr := ptr\n }\n unchecked {\n _unsafeCopyTo(memView, ptr + 0x20);\n }\n // `bytes arr` is stored in memory in the following way\n // 1. First, uint256 arr.length is stored. That requires 32 bytes (0x20).\n // 2. Then, the array data is stored.\n uint256 len_ = memView.len();\n uint256 footprint_ = memView.footprint();\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Write new unused pointer: the old value + array footprint + 32 bytes to store the length\n mstore(0x40, add(add(ptr, footprint_), 0x20))\n // Write len of new array (in bytes)\n mstore(ptr, len_)\n }\n }\n\n /**\n * @notice Copies all views, joins them into a new bytearray.\n * @param memViews The memory views\n * @return arr The new byte array with joined data behind the given views\n */\n function join(MemView[] memory memViews) internal view returns (bytes memory arr) {\n uint256 ptr;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Load unused memory pointer\n ptr := mload(0x40)\n // This is where the byte array will be stored\n arr := ptr\n }\n MemView newView;\n unchecked {\n newView = _unsafeJoin(memViews, ptr + 0x20);\n }\n uint256 len_ = newView.len();\n uint256 footprint_ = newView.footprint();\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Write new unused pointer: the old value + array footprint + 32 bytes to store the length\n mstore(0x40, add(add(ptr, footprint_), 0x20))\n // Write len of new array (in bytes)\n mstore(ptr, len_)\n }\n }\n\n // ══════════════════════════════════════════ INSPECTING MEMORY VIEW ═══════════════════════════════════════════════\n\n /**\n * @notice Returns the memory address of the underlying bytes.\n * @param memView The memory view\n * @return loc_ The memory address\n */\n function loc(MemView memView) internal pure returns (uint256 loc_) {\n // loc is stored in the highest 16 bytes of the underlying uint256\n return MemView.unwrap(memView) \u003e\u003e 128;\n }\n\n /**\n * @notice Returns the number of bytes of the view.\n * @param memView The memory view\n * @return len_ The length of the view\n */\n function len(MemView memView) internal pure returns (uint256 len_) {\n // len is stored in the lowest 16 bytes of the underlying uint256\n return MemView.unwrap(memView) \u0026 type(uint128).max;\n }\n\n /**\n * @notice Returns the endpoint of `memView`.\n * @param memView The memory view\n * @return end_ The endpoint of `memView`\n */\n function end(MemView memView) internal pure returns (uint256 end_) {\n // The endpoint never overflows uint128, let alone uint256, so we could use unchecked math here\n unchecked {\n return memView.loc() + memView.len();\n }\n }\n\n /**\n * @notice Returns the number of memory words this memory view occupies, rounded up.\n * @param memView The memory view\n * @return words_ The number of memory words\n */\n function words(MemView memView) internal pure returns (uint256 words_) {\n // returning ceil(length / 32.0)\n unchecked {\n return (memView.len() + 31) \u003e\u003e 5;\n }\n }\n\n /**\n * @notice Returns the in-memory footprint of a fresh copy of the view.\n * @param memView The memory view\n * @return footprint_ The in-memory footprint of a fresh copy of the view.\n */\n function footprint(MemView memView) internal pure returns (uint256 footprint_) {\n // words() * 32\n return memView.words() \u003c\u003c 5;\n }\n\n // ════════════════════════════════════════════ HASHING MEMORY VIEW ════════════════════════════════════════════════\n\n /**\n * @notice Returns the keccak256 hash of the underlying memory\n * @param memView The memory view\n * @return digest The keccak256 hash of the underlying memory\n */\n function keccak(MemView memView) internal pure returns (bytes32 digest) {\n uint256 loc_ = memView.loc();\n uint256 len_ = memView.len();\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n digest := keccak256(loc_, len_)\n }\n }\n\n /**\n * @notice Adds a salt to the keccak256 hash of the underlying data and returns the keccak256 hash of the\n * resulting data.\n * @param memView The memory view\n * @return digestSalted keccak256(salt, keccak256(memView))\n */\n function keccakSalted(MemView memView, bytes32 salt) internal pure returns (bytes32 digestSalted) {\n return keccak256(bytes.concat(salt, memView.keccak()));\n }\n\n // ════════════════════════════════════════════ SLICING MEMORY VIEW ════════════════════════════════════════════════\n\n /**\n * @notice Safe slicing without memory modification.\n * @param memView The memory view\n * @param index_ The start index\n * @param len_ The length\n * @return The new view for the slice of the given length starting from the given index\n */\n function slice(MemView memView, uint256 index_, uint256 len_) internal pure returns (MemView) {\n uint256 loc_ = memView.loc();\n // Ensure it doesn't overrun the view\n if (loc_ + index_ + len_ \u003e memView.end()) {\n revert ViewOverrun();\n }\n // Build a view starting from index with the given length\n unchecked {\n // loc_ + index_ \u003c= memView.end()\n return build({loc_: loc_ + index_, len_: len_});\n }\n }\n\n /**\n * @notice Shortcut to `slice`. Gets a view representing bytes from `index` to end(memView).\n * @param memView The memory view\n * @param index_ The start index\n * @return The new view for the slice starting from the given index until the initial view endpoint\n */\n function sliceFrom(MemView memView, uint256 index_) internal pure returns (MemView) {\n uint256 len_ = memView.len();\n // Ensure it doesn't overrun the view\n if (index_ \u003e len_) {\n revert ViewOverrun();\n }\n // Build a view starting from index with the given length\n unchecked {\n // index_ \u003c= len_ =\u003e memView.loc() + index_ \u003c= memView.loc() + memView.len() == memView.end()\n return build({loc_: memView.loc() + index_, len_: len_ - index_});\n }\n }\n\n /**\n * @notice Shortcut to `slice`. Gets a view representing the first `len` bytes.\n * @param memView The memory view\n * @param len_ The length\n * @return The new view for the slice of the given length starting from the initial view beginning\n */\n function prefix(MemView memView, uint256 len_) internal pure returns (MemView) {\n return memView.slice({index_: 0, len_: len_});\n }\n\n /**\n * @notice Shortcut to `slice`. Gets a view representing the last `len` byte.\n * @param memView The memory view\n * @param len_ The length\n * @return The new view for the slice of the given length until the initial view endpoint\n */\n function postfix(MemView memView, uint256 len_) internal pure returns (MemView) {\n uint256 viewLen = memView.len();\n // Ensure it doesn't overrun the view\n if (len_ \u003e viewLen) {\n revert ViewOverrun();\n }\n // Could do the unchecked math due to the check above\n uint256 index_;\n unchecked {\n index_ = viewLen - len_;\n }\n // Build a view starting from index with the given length\n unchecked {\n // len_ \u003c= memView.len() =\u003e memView.loc() \u003c= loc_ \u003c= memView.end()\n return build({loc_: memView.loc() + viewLen - len_, len_: len_});\n }\n }\n\n // ═══════════════════════════════════════════ INDEXING MEMORY VIEW ════════════════════════════════════════════════\n\n /**\n * @notice Load up to 32 bytes from the view onto the stack.\n * @dev Returns a bytes32 with only the `bytes_` HIGHEST bytes set.\n * This can be immediately cast to a smaller fixed-length byte array.\n * To automatically cast to an integer, use `indexUint`.\n * @param memView The memory view\n * @param index_ The index\n * @param bytes_ The amount of bytes to load onto the stack\n * @return result The 32 byte result having only `bytes_` highest bytes set\n */\n function index(MemView memView, uint256 index_, uint256 bytes_) internal pure returns (bytes32 result) {\n if (bytes_ == 0) {\n return bytes32(0);\n }\n // Can't load more than 32 bytes to the stack in one go\n if (bytes_ \u003e 32) {\n revert IndexedTooMuch();\n }\n // The last indexed byte should be within view boundaries\n if (index_ + bytes_ \u003e memView.len()) {\n revert ViewOverrun();\n }\n uint256 bitLength = bytes_ \u003c\u003c 3; // bytes_ * 8\n uint256 loc_ = memView.loc();\n // Get a mask with `bitLength` highest bits set\n uint256 mask;\n // 0x800...00 binary representation is 100...00\n // sar stands for \"signed arithmetic shift\": https://en.wikipedia.org/wiki/Arithmetic_shift\n // sar(N-1, 100...00) = 11...100..00, with exactly N highest bits set to 1\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n mask := sar(sub(bitLength, 1), 0x8000000000000000000000000000000000000000000000000000000000000000)\n }\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Load a full word using index offset, and apply mask to ignore non-relevant bytes\n result := and(mload(add(loc_, index_)), mask)\n }\n }\n\n /**\n * @notice Parse an unsigned integer from the view at `index`.\n * @dev Requires that the view have \u003e= `bytes_` bytes following that index.\n * @param memView The memory view\n * @param index_ The index\n * @param bytes_ The amount of bytes to load onto the stack\n * @return The unsigned integer\n */\n function indexUint(MemView memView, uint256 index_, uint256 bytes_) internal pure returns (uint256) {\n bytes32 indexedBytes = memView.index(index_, bytes_);\n // `index()` returns left-aligned `bytes_`, while integers are right-aligned\n // Shifting here to right-align with the full 32 bytes word: need to shift right `(32 - bytes_)` bytes\n unchecked {\n // memView.index() reverts when bytes_ \u003e 32, thus unchecked math\n return uint256(indexedBytes) \u003e\u003e ((32 - bytes_) \u003c\u003c 3);\n }\n }\n\n /**\n * @notice Parse an address from the view at `index`.\n * @dev Requires that the view have \u003e= 20 bytes following that index.\n * @param memView The memory view\n * @param index_ The index\n * @return The address\n */\n function indexAddress(MemView memView, uint256 index_) internal pure returns (address) {\n // index 20 bytes as `uint160`, and then cast to `address`\n return address(uint160(memView.indexUint(index_, 20)));\n }\n\n // ══════════════════════════════════════════════ PRIVATE HELPERS ══════════════════════════════════════════════════\n\n /// @dev Returns a memory view over the specified memory location\n /// without checking if it points to unallocated memory.\n function _unsafeBuildUnchecked(uint256 loc_, uint256 len_) private pure returns (MemView) {\n // There is no scenario where loc or len would overflow uint128, so we omit this check.\n // We use the highest 128 bits to encode the location and the lowest 128 bits to encode the length.\n return MemView.wrap((loc_ \u003c\u003c 128) | len_);\n }\n\n /**\n * @notice Copy the view to a location, return an unsafe memory reference\n * @dev Super Dangerous direct memory access.\n * This reference can be overwritten if anything else modifies memory (!!!).\n * As such it MUST be consumed IMMEDIATELY. Update the free memory pointer to ensure the copied data\n * is not overwritten. This function is private to prevent unsafe usage by callers.\n * @param memView The memory view\n * @param newLoc The new location to copy the underlying view data\n * @return The memory view over the unsafe memory with the copied underlying data\n */\n function _unsafeCopyTo(MemView memView, uint256 newLoc) private view returns (MemView) {\n uint256 len_ = memView.len();\n uint256 oldLoc = memView.loc();\n\n uint256 ptr;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Load unused memory pointer\n ptr := mload(0x40)\n }\n // Revert if we're writing in occupied memory\n if (newLoc \u003c ptr) {\n revert OccupiedMemory();\n }\n bool res;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // use the identity precompile (0x04) to copy\n res := staticcall(gas(), 0x04, oldLoc, len_, newLoc, len_)\n }\n if (!res) revert PrecompileOutOfGas();\n return _unsafeBuildUnchecked({loc_: newLoc, len_: len_});\n }\n\n /**\n * @notice Join the views in memory, return an unsafe reference to the memory.\n * @dev Super Dangerous direct memory access.\n * This reference can be overwritten if anything else modifies memory (!!!).\n * As such it MUST be consumed IMMEDIATELY. Update the free memory pointer to ensure the copied data\n * is not overwritten. This function is private to prevent unsafe usage by callers.\n * @param memViews The memory views\n * @return The conjoined view pointing to the new memory\n */\n function _unsafeJoin(MemView[] memory memViews, uint256 location) private view returns (MemView) {\n uint256 ptr;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Load unused memory pointer\n ptr := mload(0x40)\n }\n // Revert if we're writing in occupied memory\n if (location \u003c ptr) {\n revert OccupiedMemory();\n }\n // Copy the views to the specified location one by one, by tracking the amount of copied bytes so far\n uint256 offset = 0;\n for (uint256 i = 0; i \u003c memViews.length;) {\n MemView memView = memViews[i];\n // We can use the unchecked math here as location + sum(view.length) will never overflow uint256\n unchecked {\n _unsafeCopyTo(memView, location + offset);\n offset += memView.len();\n ++i;\n }\n }\n return _unsafeBuildUnchecked({loc_: location, len_: offset});\n }\n}\n\n// Here we define common constants to enable their easier reusing later.\n\n// ══════════════════════════════════ MERKLE ═══════════════════════════════════\n/// @dev Height of the Agent Merkle Tree\nuint256 constant AGENT_TREE_HEIGHT = 32;\n/// @dev Height of the Origin Merkle Tree\nuint256 constant ORIGIN_TREE_HEIGHT = 32;\n/// @dev Height of the Snapshot Merkle Tree. Allows up to 64 leafs, e.g. up to 32 states\nuint256 constant SNAPSHOT_TREE_HEIGHT = 6;\n// ══════════════════════════════════ STRUCTS ══════════════════════════════════\n/// @dev See Attestation.sol: (bytes32,bytes32,uint32,uint40,uint40): 32+32+4+5+5\nuint256 constant ATTESTATION_LENGTH = 78;\n/// @dev See GasData.sol: (uint16,uint16,uint16,uint16,uint16,uint16): 2+2+2+2+2+2\nuint256 constant GAS_DATA_LENGTH = 12;\n/// @dev See Receipt.sol: (uint32,uint32,bytes32,bytes32,uint8,address,address,address): 4+4+32+32+1+20+20+20\nuint256 constant RECEIPT_LENGTH = 133;\n/// @dev See State.sol: (bytes32,uint32,uint32,uint40,uint40,GasData): 32+4+4+5+5+len(GasData)\nuint256 constant STATE_LENGTH = 50 + GAS_DATA_LENGTH;\n/// @dev Maximum amount of states in a single snapshot. Each state produces two leafs in the tree\nuint256 constant SNAPSHOT_MAX_STATES = 1 \u003c\u003c (SNAPSHOT_TREE_HEIGHT - 1);\n// ══════════════════════════════════ MESSAGE ══════════════════════════════════\n/// @dev See Header.sol: (uint8,uint32,uint32,uint32,uint32): 1+4+4+4+4\nuint256 constant HEADER_LENGTH = 17;\n/// @dev See Request.sol: (uint96,uint64,uint32): 12+8+4\nuint256 constant REQUEST_LENGTH = 24;\n/// @dev See Tips.sol: (uint64,uint64,uint64,uint64): 8+8+8+8\nuint256 constant TIPS_LENGTH = 32;\n/// @dev The amount of discarded last bits when encoding tip values\nuint256 constant TIPS_GRANULARITY = 32;\n/// @dev Tip values could be only the multiples of TIPS_MULTIPLIER\nuint256 constant TIPS_MULTIPLIER = 1 \u003c\u003c TIPS_GRANULARITY;\n// ══════════════════════════════ STATEMENT SALTS ══════════════════════════════\n/// @dev Salts for signing various statements\nbytes32 constant ATTESTATION_VALID_SALT = keccak256(\"ATTESTATION_VALID_SALT\");\nbytes32 constant ATTESTATION_INVALID_SALT = keccak256(\"ATTESTATION_INVALID_SALT\");\nbytes32 constant RECEIPT_VALID_SALT = keccak256(\"RECEIPT_VALID_SALT\");\nbytes32 constant RECEIPT_INVALID_SALT = keccak256(\"RECEIPT_INVALID_SALT\");\nbytes32 constant SNAPSHOT_VALID_SALT = keccak256(\"SNAPSHOT_VALID_SALT\");\nbytes32 constant STATE_INVALID_SALT = keccak256(\"STATE_INVALID_SALT\");\n// ═════════════════════════════════ PROTOCOL ══════════════════════════════════\n/// @dev Optimistic period for new agent roots in LightManager\nuint32 constant AGENT_ROOT_OPTIMISTIC_PERIOD = 1 days;\nuint32 constant BONDING_OPTIMISTIC_PERIOD = 1 days;\n/// @dev Amount of time without fresh data from Notaries before contract owner can resolve stuck disputes manually\nuint256 constant FRESH_DATA_TIMEOUT = 4 hours;\n/// @dev Maximum bytes per message = 2 KiB (somewhat arbitrarily set to begin)\nuint256 constant MAX_CONTENT_BYTES = 2 * 2 ** 10;\n/// @dev Domain of the Synapse Chain\n// TODO: replace the placeholder with actual value (for MVP this is Optimism chainId)\nuint32 constant SYNAPSE_DOMAIN = 10;\n\n/// Receipt is a memory view over a formatted \"full receipt\" payload.\ntype Receipt is uint256;\n\nusing ReceiptLib for Receipt global;\n\n/// Receipt structure represents a Notary statement that a certain message has been executed in `ExecutionHub`.\n/// - It is possible to prove the correctness of the tips payload using the message hash, therefore tips are not\n/// included in the receipt.\n/// - Receipt is signed by a Notary and submitted to `Summit` in order to initiate the tips distribution for an\n/// executed message.\n/// - If a message execution fails the first time, the `finalExecutor` field will be set to zero address. In this\n/// case, when the message is finally executed successfully, the `finalExecutor` field will be updated. Both\n/// receipts will be considered valid.\n/// # Memory layout of Receipt fields\n///\n/// | Position | Field | Type | Bytes | Description |\n/// | ---------- | ------------- | ------- | ----- | ------------------------------------------------ |\n/// | [000..004) | origin | uint32 | 4 | Domain where message originated |\n/// | [004..008) | destination | uint32 | 4 | Domain where message was executed |\n/// | [008..040) | messageHash | bytes32 | 32 | Hash of the message |\n/// | [040..072) | snapshotRoot | bytes32 | 32 | Snapshot root used for proving the message |\n/// | [072..073) | stateIndex | uint8 | 1 | Index of state used for the snapshot proof |\n/// | [073..093) | attNotary | address | 20 | Notary who posted attestation with snapshot root |\n/// | [093..113) | firstExecutor | address | 20 | Executor who performed first valid execution |\n/// | [113..133) | finalExecutor | address | 20 | Executor who successfully executed the message |\nlibrary ReceiptLib {\n using MemViewLib for bytes;\n\n /// @dev The variables below are not supposed to be used outside of the library directly.\n uint256 private constant OFFSET_ORIGIN = 0;\n uint256 private constant OFFSET_DESTINATION = 4;\n uint256 private constant OFFSET_MESSAGE_HASH = 8;\n uint256 private constant OFFSET_SNAPSHOT_ROOT = 40;\n uint256 private constant OFFSET_STATE_INDEX = 72;\n uint256 private constant OFFSET_ATT_NOTARY = 73;\n uint256 private constant OFFSET_FIRST_EXECUTOR = 93;\n uint256 private constant OFFSET_FINAL_EXECUTOR = 113;\n\n // ═════════════════════════════════════════════════ RECEIPT ═════════════════════════════════════════════════════\n\n /**\n * @notice Returns a formatted Receipt payload with provided fields.\n * @param origin_ Domain where message originated\n * @param destination_ Domain where message was executed\n * @param messageHash_ Hash of the message\n * @param snapshotRoot_ Snapshot root used for proving the message\n * @param stateIndex_ Index of state used for the snapshot proof\n * @param attNotary_ Notary who posted attestation with snapshot root\n * @param firstExecutor_ Executor who performed first valid execution attempt\n * @param finalExecutor_ Executor who successfully executed the message\n * @return Formatted receipt\n */\n function formatReceipt(\n uint32 origin_,\n uint32 destination_,\n bytes32 messageHash_,\n bytes32 snapshotRoot_,\n uint8 stateIndex_,\n address attNotary_,\n address firstExecutor_,\n address finalExecutor_\n ) internal pure returns (bytes memory) {\n return abi.encodePacked(\n origin_, destination_, messageHash_, snapshotRoot_, stateIndex_, attNotary_, firstExecutor_, finalExecutor_\n );\n }\n\n /**\n * @notice Returns a Receipt view over the given payload.\n * @dev Will revert if the payload is not a receipt.\n */\n function castToReceipt(bytes memory payload) internal pure returns (Receipt) {\n return castToReceipt(payload.ref());\n }\n\n /**\n * @notice Casts a memory view to a Receipt view.\n * @dev Will revert if the memory view is not over a receipt.\n */\n function castToReceipt(MemView memView) internal pure returns (Receipt) {\n if (!isReceipt(memView)) revert UnformattedReceipt();\n return Receipt.wrap(MemView.unwrap(memView));\n }\n\n /// @notice Checks that a payload is a formatted Receipt.\n function isReceipt(MemView memView) internal pure returns (bool) {\n // Check payload length\n return memView.len() == RECEIPT_LENGTH;\n }\n\n /// @notice Returns the hash of an Receipt, that could be later signed by a Notary to signal\n /// that the receipt is valid.\n function hashValid(Receipt receipt) internal pure returns (bytes32) {\n // The final hash to sign is keccak(receiptSalt, keccak(receipt))\n return receipt.unwrap().keccakSalted(RECEIPT_VALID_SALT);\n }\n\n /// @notice Returns the hash of a Receipt, that could be later signed by a Guard to signal\n /// that the receipt is invalid.\n function hashInvalid(Receipt receipt) internal pure returns (bytes32) {\n // The final hash to sign is keccak(receiptBodyInvalidSalt, keccak(receipt))\n return receipt.unwrap().keccakSalted(RECEIPT_INVALID_SALT);\n }\n\n /// @notice Convenience shortcut for unwrapping a view.\n function unwrap(Receipt receipt) internal pure returns (MemView) {\n return MemView.wrap(Receipt.unwrap(receipt));\n }\n\n /// @notice Compares two Receipt structures.\n function equals(Receipt a, Receipt b) internal pure returns (bool) {\n // Length of a Receipt payload is fixed, so we just need to compare the hashes\n return a.unwrap().keccak() == b.unwrap().keccak();\n }\n\n // ═════════════════════════════════════════════ RECEIPT SLICING ═════════════════════════════════════════════════\n\n /// @notice Returns receipt's origin field\n function origin(Receipt receipt) internal pure returns (uint32) {\n return uint32(receipt.unwrap().indexUint({index_: OFFSET_ORIGIN, bytes_: 4}));\n }\n\n /// @notice Returns receipt's destination field\n function destination(Receipt receipt) internal pure returns (uint32) {\n return uint32(receipt.unwrap().indexUint({index_: OFFSET_DESTINATION, bytes_: 4}));\n }\n\n /// @notice Returns receipt's \"message hash\" field\n function messageHash(Receipt receipt) internal pure returns (bytes32) {\n return receipt.unwrap().index({index_: OFFSET_MESSAGE_HASH, bytes_: 32});\n }\n\n /// @notice Returns receipt's \"snapshot root\" field\n function snapshotRoot(Receipt receipt) internal pure returns (bytes32) {\n return receipt.unwrap().index({index_: OFFSET_SNAPSHOT_ROOT, bytes_: 32});\n }\n\n /// @notice Returns receipt's \"state index\" field\n function stateIndex(Receipt receipt) internal pure returns (uint8) {\n return uint8(receipt.unwrap().indexUint({index_: OFFSET_STATE_INDEX, bytes_: 1}));\n }\n\n /// @notice Returns receipt's \"attestation notary\" field\n function attNotary(Receipt receipt) internal pure returns (address) {\n return receipt.unwrap().indexAddress({index_: OFFSET_ATT_NOTARY});\n }\n\n /// @notice Returns receipt's \"first executor\" field\n function firstExecutor(Receipt receipt) internal pure returns (address) {\n return receipt.unwrap().indexAddress({index_: OFFSET_FIRST_EXECUTOR});\n }\n\n /// @notice Returns receipt's \"final executor\" field\n function finalExecutor(Receipt receipt) internal pure returns (address) {\n return receipt.unwrap().indexAddress({index_: OFFSET_FINAL_EXECUTOR});\n }\n}\n\n// solhint-disable ordering\ncontract ReceiptHarness {\n using ReceiptLib for bytes;\n using ReceiptLib for MemView;\n using MemViewLib for bytes;\n\n // Note: we don't add an empty test() function here, as it currently leads\n // to zero coverage on the corresponding library.\n\n // ══════════════════════════════════════════════════ GETTERS ══════════════════════════════════════════════════════\n\n function castToReceipt(bytes memory payload) public view returns (bytes memory) {\n // Walkaround to get the forge coverage working on libraries, see\n // https://github.com/foundry-rs/foundry/pull/3128#issuecomment-1241245086\n Receipt receipt = ReceiptLib.castToReceipt(payload);\n return receipt.unwrap().clone();\n }\n\n /// @notice Returns receipt's origin field\n function origin(bytes memory payload) public pure returns (uint32) {\n return payload.castToReceipt().origin();\n }\n\n /// @notice Returns receipt's destination field\n function destination(bytes memory payload) public pure returns (uint32) {\n return payload.castToReceipt().destination();\n }\n\n /// @notice Returns receipt's \"message hash\" field\n function messageHash(bytes memory payload) public pure returns (bytes32) {\n return payload.castToReceipt().messageHash();\n }\n\n /// @notice Returns receipt's \"snapshot root\" field\n function snapshotRoot(bytes memory payload) public pure returns (bytes32) {\n return payload.castToReceipt().snapshotRoot();\n }\n\n /// @notice Returns receipt's \"state index\" field\n function stateIndex(bytes memory payload) public pure returns (uint8) {\n return payload.castToReceipt().stateIndex();\n }\n\n /// @notice Returns receipt's \"attestation notary\" field\n function attNotary(bytes memory payload) public pure returns (address) {\n return payload.castToReceipt().attNotary();\n }\n\n /// @notice Returns receipt's \"first executor\" field\n function firstExecutor(bytes memory payload) public pure returns (address) {\n return payload.castToReceipt().firstExecutor();\n }\n\n /// @notice Returns receipt's \"final executor\" field\n function finalExecutor(bytes memory payload) public pure returns (address) {\n return payload.castToReceipt().finalExecutor();\n }\n\n function equals(bytes memory a, bytes memory b) public pure returns (bool) {\n return a.ref().castToReceipt().equals(b.ref().castToReceipt());\n }\n\n function isReceipt(bytes memory payload) public pure returns (bool) {\n return payload.ref().isReceipt();\n }\n\n function hashValid(bytes memory payload) public pure returns (bytes32) {\n return payload.ref().castToReceipt().hashValid();\n }\n\n function hashInvalid(bytes memory payload) public pure returns (bytes32) {\n return payload.ref().castToReceipt().hashInvalid();\n }\n\n // ════════════════════════════════════════════════ FORMATTERS ═════════════════════════════════════════════════════\n\n function formatReceipt(\n uint32 origin_,\n uint32 destination_,\n bytes32 messageHash_,\n bytes32 snapshotRoot_,\n uint8 stateIndex_,\n address attNotary_,\n address firstExecutor_,\n address finalExecutor_\n ) public pure returns (bytes memory) {\n return ReceiptLib.formatReceipt(\n origin_, destination_, messageHash_, snapshotRoot_, stateIndex_, attNotary_, firstExecutor_, finalExecutor_\n );\n }\n}\n","language":"Solidity","languageVersion":"0.8.17","compilerVersion":"0.8.17","compilerOptions":"--combined-json bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc,metadata,hashes --optimize --optimize-runs 10000 --allow-paths ., ./, ../","srcMap":"5720:19162:0:-:0;;;;;;;;;;;;;;;-1:-1:-1;;;5720:19162:0;;;;;;;;;;;;;;;;;","srcMapRuntime":"5720:19162:0:-:0;;;;;;;;","abiDefinition":[],"userDoc":{"kind":"user","methods":{},"notice":"Library for operations with the memory views. Forked from https://github.com/summa-tx/memview-sol with several breaking changes: - The codebase is ported to Solidity 0.8 - Custom errors are added - The runtime type checking is replaced with compile-time check provided by User-Defined Value Types https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types - uint256 is used as the underlying type for the \"memory view\" instead of bytes29. It is wrapped into MemView custom type in order not to be confused with actual integers. - Therefore the \"type\" field is discarded, allowing to allocate 16 bytes for both view location and length - The documentation is expanded - Library functions unused by the rest of the codebase are removed","version":1},"developerDoc":{"kind":"dev","methods":{},"version":1},"metadata":"{\"compiler\":{\"version\":\"0.8.17+commit.8df45f5f\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"Library for operations with the memory views. Forked from https://github.com/summa-tx/memview-sol with several breaking changes: - The codebase is ported to Solidity 0.8 - Custom errors are added - The runtime type checking is replaced with compile-time check provided by User-Defined Value Types https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types - uint256 is used as the underlying type for the \\\"memory view\\\" instead of bytes29. It is wrapped into MemView custom type in order not to be confused with actual integers. - Therefore the \\\"type\\\" field is discarded, allowing to allocate 16 bytes for both view location and length - The documentation is expanded - Library functions unused by the rest of the codebase are removed\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"solidity/ReceiptHarness.t.sol\":\"MemViewLib\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"solidity/ReceiptHarness.t.sol\":{\"keccak256\":\"0xf16784d67df259efee36f809c5a0756a6febd1ad6f07aa4b77fa52127ec5f9fa\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a097afb3038e4aaafadb147a3a36cf3bb4751c0ff22351dfe3f2c90e65194922\",\"dweb:/ipfs/QmTkBucx2oKmEGDMzHirHmRgzpCsp62KZ7WZYoRHPXjFuA\"]}},\"version\":1}"},"hashes":{}},"solidity/ReceiptHarness.t.sol:ReceiptHarness":{"code":"0x608060405234801561001057600080fd5b50610c09806100206000396000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c8063854dfcd71161008c578063cb3eb0e111610066578063cb3eb0e1146102f5578063e152a8cb14610308578063ed54c3b61461031b578063f7e6a05b1461032e57600080fd5b8063854dfcd7146101db578063c3bfda6c146101ee578063c81aa9c8146102cd57600080fd5b8063595271d1116100c8578063595271d1146101625780635cab5d3b1461018757806360cf3bf0146101a7578063730dbf63146101c857600080fd5b80630bb3b580146100ef578063137e618a1461011757806327f2ee361461012a575b600080fd5b6101026100fd3660046109c6565b610341565b60405190151581526020015b60405180910390f35b6101026101253660046109fb565b61036d565b61013d6101383660046109c6565b61039c565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161010e565b6101756101703660046109c6565b6103af565b60405160ff909116815260200161010e565b61019a6101953660046109c6565b6103c2565b60405161010e9190610a5f565b6101ba6101b53660046109c6565b6103da565b60405190815260200161010e565b6101ba6101d63660046109c6565b6103f0565b6101ba6101e93660046109c6565b610406565b61019a6101fc366004610b08565b6040805160e0998a1b7fffffffff0000000000000000000000000000000000000000000000000000000090811660208301529890991b90971660248901526028880195909552604887019390935260f89190911b7fff00000000000000000000000000000000000000000000000000000000000000166068860152606090811b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000908116606987015291811b8216607d8601529190911b1660918301528051608581840301815260a5909201905290565b6102e06102db3660046109c6565b610419565b60405163ffffffff909116815260200161010e565b6102e06103033660046109c6565b61042c565b61013d6103163660046109c6565b61043f565b6101ba6103293660046109c6565b610452565b61013d61033c3660046109c6565b610465565b600061036761034f83610478565b6fffffffffffffffffffffffffffffffff1660851490565b92915050565b600061039561038361037e84610478565b610493565b61038f61037e86610478565b906104e5565b9392505050565b60006103676103aa83610501565b61050f565b60006103676103bd83610501565b61051e565b606060006103cf83610501565b905061039581610530565b60006103676103eb61037e84610478565b61058d565b600061036761040161037e84610478565b6105bb565b600061036761041483610501565b6105e7565b600061036761042783610501565b6105f9565b600061036761043a83610501565b610607565b600061036761044d83610501565b610615565b600061036761046083610501565b610622565b600061036761047383610501565b610631565b80516000906020830161048b818361063e565b949350505050565b600060856fffffffffffffffffffffffffffffffff8316146104e1576040517f76b4e13c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5090565b60006104f0826106a1565b6104f9846106a1565b149392505050565b600061036761037e83610478565b6000610367605d835b906106cc565b600061036760486001845b91906106d6565b6040518061054183602083016106f7565b506fffffffffffffffffffffffffffffffff83166000601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168301602001604052509052919050565b60006103677fdf42b2c0137811ba604f5c79e20c4d6b94770aa819cc524eca444056544f8ab7835b906107a6565b60006103677fb38669e8ca41a27fcd85729b868e8ab047d0f142073a017213e58f0a91e88ef3836105b5565b600061036760286020845b91906107e2565b600061036760048084610529565b600061036781600484610529565b6000610367604983610518565b600061036760086020846105f2565b6000610367607183610518565b60008061064b8385610b99565b905060405181111561065b575060005b80600003610695576040517f10bef38600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b608084901b831761048b565b6000806106ae8360801c90565b6fffffffffffffffffffffffffffffffff9390931690922092915050565b6000610395838360145b6000806106e48585856107e2565b602084900360031b1c9150509392505050565b6040516000906fffffffffffffffffffffffffffffffff841690608085901c9080851015610751576040517f4b2a158c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008386858560045afa905080610794576040517f7c7d772f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b608086901b8417979650505050505050565b6000816107b2846106a1565b60408051602081019390935282015260600160405160208183030381529060405280519060200120905092915050565b6000816000036107f457506000610395565b602082111561082f576040517f31d784a800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6fffffffffffffffffffffffffffffffff841661084c8385610b99565b1115610884576040517fa3b99ded00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600382901b60006108958660801c90565b909401517f80000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff929092019190911d16949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f83011261092c57600080fd5b813567ffffffffffffffff80821115610947576109476108ec565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171561098d5761098d6108ec565b816040528381528660208588010111156109a657600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000602082840312156109d857600080fd5b813567ffffffffffffffff8111156109ef57600080fd5b61048b8482850161091b565b60008060408385031215610a0e57600080fd5b823567ffffffffffffffff80821115610a2657600080fd5b610a328683870161091b565b93506020850135915080821115610a4857600080fd5b50610a558582860161091b565b9150509250929050565b600060208083528351808285015260005b81811015610a8c57858101830151858201604001528201610a70565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b803563ffffffff81168114610adf57600080fd5b919050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610adf57600080fd5b600080600080600080600080610100898b031215610b2557600080fd5b610b2e89610acb565b9750610b3c60208a01610acb565b96506040890135955060608901359450608089013560ff81168114610b6057600080fd5b9350610b6e60a08a01610ae4565b9250610b7c60c08a01610ae4565b9150610b8a60e08a01610ae4565b90509295985092959890939650565b80820180821115610367577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea264697066735822122039924413aea489827d6ab16c8f3b9ba78c921ec5fa110ef66f52da8359b9ee1064736f6c63430008110033","runtime-code":"0x608060405234801561001057600080fd5b50600436106100ea5760003560e01c8063854dfcd71161008c578063cb3eb0e111610066578063cb3eb0e1146102f5578063e152a8cb14610308578063ed54c3b61461031b578063f7e6a05b1461032e57600080fd5b8063854dfcd7146101db578063c3bfda6c146101ee578063c81aa9c8146102cd57600080fd5b8063595271d1116100c8578063595271d1146101625780635cab5d3b1461018757806360cf3bf0146101a7578063730dbf63146101c857600080fd5b80630bb3b580146100ef578063137e618a1461011757806327f2ee361461012a575b600080fd5b6101026100fd3660046109c6565b610341565b60405190151581526020015b60405180910390f35b6101026101253660046109fb565b61036d565b61013d6101383660046109c6565b61039c565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161010e565b6101756101703660046109c6565b6103af565b60405160ff909116815260200161010e565b61019a6101953660046109c6565b6103c2565b60405161010e9190610a5f565b6101ba6101b53660046109c6565b6103da565b60405190815260200161010e565b6101ba6101d63660046109c6565b6103f0565b6101ba6101e93660046109c6565b610406565b61019a6101fc366004610b08565b6040805160e0998a1b7fffffffff0000000000000000000000000000000000000000000000000000000090811660208301529890991b90971660248901526028880195909552604887019390935260f89190911b7fff00000000000000000000000000000000000000000000000000000000000000166068860152606090811b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000908116606987015291811b8216607d8601529190911b1660918301528051608581840301815260a5909201905290565b6102e06102db3660046109c6565b610419565b60405163ffffffff909116815260200161010e565b6102e06103033660046109c6565b61042c565b61013d6103163660046109c6565b61043f565b6101ba6103293660046109c6565b610452565b61013d61033c3660046109c6565b610465565b600061036761034f83610478565b6fffffffffffffffffffffffffffffffff1660851490565b92915050565b600061039561038361037e84610478565b610493565b61038f61037e86610478565b906104e5565b9392505050565b60006103676103aa83610501565b61050f565b60006103676103bd83610501565b61051e565b606060006103cf83610501565b905061039581610530565b60006103676103eb61037e84610478565b61058d565b600061036761040161037e84610478565b6105bb565b600061036761041483610501565b6105e7565b600061036761042783610501565b6105f9565b600061036761043a83610501565b610607565b600061036761044d83610501565b610615565b600061036761046083610501565b610622565b600061036761047383610501565b610631565b80516000906020830161048b818361063e565b949350505050565b600060856fffffffffffffffffffffffffffffffff8316146104e1576040517f76b4e13c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5090565b60006104f0826106a1565b6104f9846106a1565b149392505050565b600061036761037e83610478565b6000610367605d835b906106cc565b600061036760486001845b91906106d6565b6040518061054183602083016106f7565b506fffffffffffffffffffffffffffffffff83166000601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168301602001604052509052919050565b60006103677fdf42b2c0137811ba604f5c79e20c4d6b94770aa819cc524eca444056544f8ab7835b906107a6565b60006103677fb38669e8ca41a27fcd85729b868e8ab047d0f142073a017213e58f0a91e88ef3836105b5565b600061036760286020845b91906107e2565b600061036760048084610529565b600061036781600484610529565b6000610367604983610518565b600061036760086020846105f2565b6000610367607183610518565b60008061064b8385610b99565b905060405181111561065b575060005b80600003610695576040517f10bef38600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b608084901b831761048b565b6000806106ae8360801c90565b6fffffffffffffffffffffffffffffffff9390931690922092915050565b6000610395838360145b6000806106e48585856107e2565b602084900360031b1c9150509392505050565b6040516000906fffffffffffffffffffffffffffffffff841690608085901c9080851015610751576040517f4b2a158c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008386858560045afa905080610794576040517f7c7d772f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b608086901b8417979650505050505050565b6000816107b2846106a1565b60408051602081019390935282015260600160405160208183030381529060405280519060200120905092915050565b6000816000036107f457506000610395565b602082111561082f576040517f31d784a800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6fffffffffffffffffffffffffffffffff841661084c8385610b99565b1115610884576040517fa3b99ded00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600382901b60006108958660801c90565b909401517f80000000000000000000000000000000000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff929092019190911d16949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f83011261092c57600080fd5b813567ffffffffffffffff80821115610947576109476108ec565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171561098d5761098d6108ec565b816040528381528660208588010111156109a657600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000602082840312156109d857600080fd5b813567ffffffffffffffff8111156109ef57600080fd5b61048b8482850161091b565b60008060408385031215610a0e57600080fd5b823567ffffffffffffffff80821115610a2657600080fd5b610a328683870161091b565b93506020850135915080821115610a4857600080fd5b50610a558582860161091b565b9150509250929050565b600060208083528351808285015260005b81811015610a8c57858101830151858201604001528201610a70565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b803563ffffffff81168114610adf57600080fd5b919050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610adf57600080fd5b600080600080600080600080610100898b031215610b2557600080fd5b610b2e89610acb565b9750610b3c60208a01610acb565b96506040890135955060608901359450608089013560ff81168114610b6057600080fd5b9350610b6e60a08a01610ae4565b9250610b7c60c08a01610ae4565b9150610b8a60e08a01610ae4565b90509295985092959890939650565b80820180821115610367577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea264697066735822122039924413aea489827d6ab16c8f3b9ba78c921ec5fa110ef66f52da8359b9ee1064736f6c63430008110033","info":{"source":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.17;\n\n// ══════════════════════════════ INVALID CALLER ═══════════════════════════════\n\nerror CallerNotAgentManager();\nerror CallerNotDestination();\nerror CallerNotInbox();\nerror CallerNotSummit();\n\n// ══════════════════════════════ INCORRECT DATA ═══════════════════════════════\n\nerror IncorrectAttestation();\nerror IncorrectAgentDomain();\nerror IncorrectAgentIndex();\nerror IncorrectAgentProof();\nerror IncorrectDataHash();\nerror IncorrectDestinationDomain();\nerror IncorrectOriginDomain();\nerror IncorrectSnapshotProof();\nerror IncorrectSnapshotRoot();\nerror IncorrectState();\nerror IncorrectStatesAmount();\nerror IncorrectTipsProof();\nerror IncorrectVersionLength();\n\nerror IncorrectNonce();\nerror IncorrectSender();\nerror IncorrectRecipient();\n\nerror FlagOutOfRange();\nerror IndexOutOfRange();\nerror NonceOutOfRange();\n\nerror OutdatedNonce();\n\nerror UnformattedAttestation();\nerror UnformattedAttestationReport();\nerror UnformattedBaseMessage();\nerror UnformattedCallData();\nerror UnformattedCallDataPrefix();\nerror UnformattedMessage();\nerror UnformattedReceipt();\nerror UnformattedReceiptReport();\nerror UnformattedSignature();\nerror UnformattedSnapshot();\nerror UnformattedState();\nerror UnformattedStateReport();\n\n// ═══════════════════════════════ MERKLE TREES ════════════════════════════════\n\nerror LeafNotProven();\nerror MerkleTreeFull();\nerror NotEnoughLeafs();\nerror TreeHeightTooLow();\n\n// ═════════════════════════════ OPTIMISTIC PERIOD ═════════════════════════════\n\nerror BaseClientOptimisticPeriod();\nerror MessageOptimisticPeriod();\nerror SlashAgentOptimisticPeriod();\nerror WithdrawTipsOptimisticPeriod();\nerror ZeroProofMaturity();\n\n// ═══════════════════════════════ AGENT MANAGER ═══════════════════════════════\n\nerror AgentNotGuard();\nerror AgentNotNotary();\n\nerror AgentCantBeAdded();\nerror AgentNotActive();\nerror AgentNotActiveNorUnstaking();\nerror AgentNotFraudulent();\nerror AgentNotUnstaking();\nerror AgentUnknown();\n\nerror DisputeAlreadyResolved();\nerror DisputeNotOpened();\nerror DisputeNotStuck();\nerror GuardInDispute();\nerror NotaryInDispute();\n\nerror MustBeSynapseDomain();\nerror SynapseDomainForbidden();\n\n// ════════════════════════════════ DESTINATION ════════════════════════════════\n\nerror AlreadyExecuted();\nerror AlreadyFailed();\nerror DuplicatedSnapshotRoot();\nerror IncorrectMagicValue();\nerror GasLimitTooLow();\nerror GasSuppliedTooLow();\n\n// ══════════════════════════════════ ORIGIN ═══════════════════════════════════\n\nerror ContentLengthTooBig();\nerror EthTransferFailed();\nerror InsufficientEthBalance();\n\n// ════════════════════════════════ GAS ORACLE ═════════════════════════════════\n\nerror LocalGasDataNotSet();\nerror RemoteGasDataNotSet();\n\n// ═══════════════════════════════════ TIPS ════════════════════════════════════\n\nerror TipsClaimMoreThanEarned();\nerror TipsClaimZero();\nerror TipsOverflow();\nerror TipsValueTooLow();\n\n// ════════════════════════════════ MEMORY VIEW ════════════════════════════════\n\nerror IndexedTooMuch();\nerror ViewOverrun();\nerror OccupiedMemory();\nerror UnallocatedMemory();\nerror PrecompileOutOfGas();\n\n// ═════════════════════════════════ MULTICALL ═════════════════════════════════\n\nerror MulticallFailed();\n\n/// @dev MemView is an untyped view over a portion of memory to be used instead of `bytes memory`\ntype MemView is uint256;\n\n/// @dev Attach library functions to MemView\nusing MemViewLib for MemView global;\n\n/// @notice Library for operations with the memory views.\n/// Forked from https://github.com/summa-tx/memview-sol with several breaking changes:\n/// - The codebase is ported to Solidity 0.8\n/// - Custom errors are added\n/// - The runtime type checking is replaced with compile-time check provided by User-Defined Value Types\n/// https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types\n/// - uint256 is used as the underlying type for the \"memory view\" instead of bytes29.\n/// It is wrapped into MemView custom type in order not to be confused with actual integers.\n/// - Therefore the \"type\" field is discarded, allowing to allocate 16 bytes for both view location and length\n/// - The documentation is expanded\n/// - Library functions unused by the rest of the codebase are removed\n// - Very pretty code separators are added :)\nlibrary MemViewLib {\n /// @notice Stack layout for uint256 (from highest bits to lowest)\n /// (32 .. 16] loc 16 bytes Memory address of underlying bytes\n /// (16 .. 00] len 16 bytes Length of underlying bytes\n\n // ═══════════════════════════════════════════ BUILDING MEMORY VIEW ════════════════════════════════════════════════\n\n /**\n * @notice Instantiate a new untyped memory view. This should generally not be called directly.\n * Prefer `ref` wherever possible.\n * @param loc_ The memory address\n * @param len_ The length\n * @return The new view with the specified location and length\n */\n function build(uint256 loc_, uint256 len_) internal pure returns (MemView) {\n uint256 end_ = loc_ + len_;\n // Make sure that a view is not constructed that points to unallocated memory\n // as this could be indicative of a buffer overflow attack\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n if gt(end_, mload(0x40)) { end_ := 0 }\n }\n if (end_ == 0) {\n revert UnallocatedMemory();\n }\n return _unsafeBuildUnchecked(loc_, len_);\n }\n\n /**\n * @notice Instantiate a memory view from a byte array.\n * @dev Note that due to Solidity memory representation, it is not possible to\n * implement a deref, as the `bytes` type stores its len in memory.\n * @param arr The byte array\n * @return The memory view over the provided byte array\n */\n function ref(bytes memory arr) internal pure returns (MemView) {\n uint256 len_ = arr.length;\n // `bytes arr` is stored in memory in the following way\n // 1. First, uint256 arr.length is stored. That requires 32 bytes (0x20).\n // 2. Then, the array data is stored.\n uint256 loc_;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // We add 0x20, so that the view starts exactly where the array data starts\n loc_ := add(arr, 0x20)\n }\n return build(loc_, len_);\n }\n\n // ════════════════════════════════════════════ CLONING MEMORY VIEW ════════════════════════════════════════════════\n\n /**\n * @notice Copies the referenced memory to a new loc in memory, returning a `bytes` pointing to the new memory.\n * @param memView The memory view\n * @return arr The cloned byte array\n */\n function clone(MemView memView) internal view returns (bytes memory arr) {\n uint256 ptr;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Load unused memory pointer\n ptr := mload(0x40)\n // This is where the byte array will be stored\n arr := ptr\n }\n unchecked {\n _unsafeCopyTo(memView, ptr + 0x20);\n }\n // `bytes arr` is stored in memory in the following way\n // 1. First, uint256 arr.length is stored. That requires 32 bytes (0x20).\n // 2. Then, the array data is stored.\n uint256 len_ = memView.len();\n uint256 footprint_ = memView.footprint();\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Write new unused pointer: the old value + array footprint + 32 bytes to store the length\n mstore(0x40, add(add(ptr, footprint_), 0x20))\n // Write len of new array (in bytes)\n mstore(ptr, len_)\n }\n }\n\n /**\n * @notice Copies all views, joins them into a new bytearray.\n * @param memViews The memory views\n * @return arr The new byte array with joined data behind the given views\n */\n function join(MemView[] memory memViews) internal view returns (bytes memory arr) {\n uint256 ptr;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Load unused memory pointer\n ptr := mload(0x40)\n // This is where the byte array will be stored\n arr := ptr\n }\n MemView newView;\n unchecked {\n newView = _unsafeJoin(memViews, ptr + 0x20);\n }\n uint256 len_ = newView.len();\n uint256 footprint_ = newView.footprint();\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Write new unused pointer: the old value + array footprint + 32 bytes to store the length\n mstore(0x40, add(add(ptr, footprint_), 0x20))\n // Write len of new array (in bytes)\n mstore(ptr, len_)\n }\n }\n\n // ══════════════════════════════════════════ INSPECTING MEMORY VIEW ═══════════════════════════════════════════════\n\n /**\n * @notice Returns the memory address of the underlying bytes.\n * @param memView The memory view\n * @return loc_ The memory address\n */\n function loc(MemView memView) internal pure returns (uint256 loc_) {\n // loc is stored in the highest 16 bytes of the underlying uint256\n return MemView.unwrap(memView) \u003e\u003e 128;\n }\n\n /**\n * @notice Returns the number of bytes of the view.\n * @param memView The memory view\n * @return len_ The length of the view\n */\n function len(MemView memView) internal pure returns (uint256 len_) {\n // len is stored in the lowest 16 bytes of the underlying uint256\n return MemView.unwrap(memView) \u0026 type(uint128).max;\n }\n\n /**\n * @notice Returns the endpoint of `memView`.\n * @param memView The memory view\n * @return end_ The endpoint of `memView`\n */\n function end(MemView memView) internal pure returns (uint256 end_) {\n // The endpoint never overflows uint128, let alone uint256, so we could use unchecked math here\n unchecked {\n return memView.loc() + memView.len();\n }\n }\n\n /**\n * @notice Returns the number of memory words this memory view occupies, rounded up.\n * @param memView The memory view\n * @return words_ The number of memory words\n */\n function words(MemView memView) internal pure returns (uint256 words_) {\n // returning ceil(length / 32.0)\n unchecked {\n return (memView.len() + 31) \u003e\u003e 5;\n }\n }\n\n /**\n * @notice Returns the in-memory footprint of a fresh copy of the view.\n * @param memView The memory view\n * @return footprint_ The in-memory footprint of a fresh copy of the view.\n */\n function footprint(MemView memView) internal pure returns (uint256 footprint_) {\n // words() * 32\n return memView.words() \u003c\u003c 5;\n }\n\n // ════════════════════════════════════════════ HASHING MEMORY VIEW ════════════════════════════════════════════════\n\n /**\n * @notice Returns the keccak256 hash of the underlying memory\n * @param memView The memory view\n * @return digest The keccak256 hash of the underlying memory\n */\n function keccak(MemView memView) internal pure returns (bytes32 digest) {\n uint256 loc_ = memView.loc();\n uint256 len_ = memView.len();\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n digest := keccak256(loc_, len_)\n }\n }\n\n /**\n * @notice Adds a salt to the keccak256 hash of the underlying data and returns the keccak256 hash of the\n * resulting data.\n * @param memView The memory view\n * @return digestSalted keccak256(salt, keccak256(memView))\n */\n function keccakSalted(MemView memView, bytes32 salt) internal pure returns (bytes32 digestSalted) {\n return keccak256(bytes.concat(salt, memView.keccak()));\n }\n\n // ════════════════════════════════════════════ SLICING MEMORY VIEW ════════════════════════════════════════════════\n\n /**\n * @notice Safe slicing without memory modification.\n * @param memView The memory view\n * @param index_ The start index\n * @param len_ The length\n * @return The new view for the slice of the given length starting from the given index\n */\n function slice(MemView memView, uint256 index_, uint256 len_) internal pure returns (MemView) {\n uint256 loc_ = memView.loc();\n // Ensure it doesn't overrun the view\n if (loc_ + index_ + len_ \u003e memView.end()) {\n revert ViewOverrun();\n }\n // Build a view starting from index with the given length\n unchecked {\n // loc_ + index_ \u003c= memView.end()\n return build({loc_: loc_ + index_, len_: len_});\n }\n }\n\n /**\n * @notice Shortcut to `slice`. Gets a view representing bytes from `index` to end(memView).\n * @param memView The memory view\n * @param index_ The start index\n * @return The new view for the slice starting from the given index until the initial view endpoint\n */\n function sliceFrom(MemView memView, uint256 index_) internal pure returns (MemView) {\n uint256 len_ = memView.len();\n // Ensure it doesn't overrun the view\n if (index_ \u003e len_) {\n revert ViewOverrun();\n }\n // Build a view starting from index with the given length\n unchecked {\n // index_ \u003c= len_ =\u003e memView.loc() + index_ \u003c= memView.loc() + memView.len() == memView.end()\n return build({loc_: memView.loc() + index_, len_: len_ - index_});\n }\n }\n\n /**\n * @notice Shortcut to `slice`. Gets a view representing the first `len` bytes.\n * @param memView The memory view\n * @param len_ The length\n * @return The new view for the slice of the given length starting from the initial view beginning\n */\n function prefix(MemView memView, uint256 len_) internal pure returns (MemView) {\n return memView.slice({index_: 0, len_: len_});\n }\n\n /**\n * @notice Shortcut to `slice`. Gets a view representing the last `len` byte.\n * @param memView The memory view\n * @param len_ The length\n * @return The new view for the slice of the given length until the initial view endpoint\n */\n function postfix(MemView memView, uint256 len_) internal pure returns (MemView) {\n uint256 viewLen = memView.len();\n // Ensure it doesn't overrun the view\n if (len_ \u003e viewLen) {\n revert ViewOverrun();\n }\n // Could do the unchecked math due to the check above\n uint256 index_;\n unchecked {\n index_ = viewLen - len_;\n }\n // Build a view starting from index with the given length\n unchecked {\n // len_ \u003c= memView.len() =\u003e memView.loc() \u003c= loc_ \u003c= memView.end()\n return build({loc_: memView.loc() + viewLen - len_, len_: len_});\n }\n }\n\n // ═══════════════════════════════════════════ INDEXING MEMORY VIEW ════════════════════════════════════════════════\n\n /**\n * @notice Load up to 32 bytes from the view onto the stack.\n * @dev Returns a bytes32 with only the `bytes_` HIGHEST bytes set.\n * This can be immediately cast to a smaller fixed-length byte array.\n * To automatically cast to an integer, use `indexUint`.\n * @param memView The memory view\n * @param index_ The index\n * @param bytes_ The amount of bytes to load onto the stack\n * @return result The 32 byte result having only `bytes_` highest bytes set\n */\n function index(MemView memView, uint256 index_, uint256 bytes_) internal pure returns (bytes32 result) {\n if (bytes_ == 0) {\n return bytes32(0);\n }\n // Can't load more than 32 bytes to the stack in one go\n if (bytes_ \u003e 32) {\n revert IndexedTooMuch();\n }\n // The last indexed byte should be within view boundaries\n if (index_ + bytes_ \u003e memView.len()) {\n revert ViewOverrun();\n }\n uint256 bitLength = bytes_ \u003c\u003c 3; // bytes_ * 8\n uint256 loc_ = memView.loc();\n // Get a mask with `bitLength` highest bits set\n uint256 mask;\n // 0x800...00 binary representation is 100...00\n // sar stands for \"signed arithmetic shift\": https://en.wikipedia.org/wiki/Arithmetic_shift\n // sar(N-1, 100...00) = 11...100..00, with exactly N highest bits set to 1\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n mask := sar(sub(bitLength, 1), 0x8000000000000000000000000000000000000000000000000000000000000000)\n }\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Load a full word using index offset, and apply mask to ignore non-relevant bytes\n result := and(mload(add(loc_, index_)), mask)\n }\n }\n\n /**\n * @notice Parse an unsigned integer from the view at `index`.\n * @dev Requires that the view have \u003e= `bytes_` bytes following that index.\n * @param memView The memory view\n * @param index_ The index\n * @param bytes_ The amount of bytes to load onto the stack\n * @return The unsigned integer\n */\n function indexUint(MemView memView, uint256 index_, uint256 bytes_) internal pure returns (uint256) {\n bytes32 indexedBytes = memView.index(index_, bytes_);\n // `index()` returns left-aligned `bytes_`, while integers are right-aligned\n // Shifting here to right-align with the full 32 bytes word: need to shift right `(32 - bytes_)` bytes\n unchecked {\n // memView.index() reverts when bytes_ \u003e 32, thus unchecked math\n return uint256(indexedBytes) \u003e\u003e ((32 - bytes_) \u003c\u003c 3);\n }\n }\n\n /**\n * @notice Parse an address from the view at `index`.\n * @dev Requires that the view have \u003e= 20 bytes following that index.\n * @param memView The memory view\n * @param index_ The index\n * @return The address\n */\n function indexAddress(MemView memView, uint256 index_) internal pure returns (address) {\n // index 20 bytes as `uint160`, and then cast to `address`\n return address(uint160(memView.indexUint(index_, 20)));\n }\n\n // ══════════════════════════════════════════════ PRIVATE HELPERS ══════════════════════════════════════════════════\n\n /// @dev Returns a memory view over the specified memory location\n /// without checking if it points to unallocated memory.\n function _unsafeBuildUnchecked(uint256 loc_, uint256 len_) private pure returns (MemView) {\n // There is no scenario where loc or len would overflow uint128, so we omit this check.\n // We use the highest 128 bits to encode the location and the lowest 128 bits to encode the length.\n return MemView.wrap((loc_ \u003c\u003c 128) | len_);\n }\n\n /**\n * @notice Copy the view to a location, return an unsafe memory reference\n * @dev Super Dangerous direct memory access.\n * This reference can be overwritten if anything else modifies memory (!!!).\n * As such it MUST be consumed IMMEDIATELY. Update the free memory pointer to ensure the copied data\n * is not overwritten. This function is private to prevent unsafe usage by callers.\n * @param memView The memory view\n * @param newLoc The new location to copy the underlying view data\n * @return The memory view over the unsafe memory with the copied underlying data\n */\n function _unsafeCopyTo(MemView memView, uint256 newLoc) private view returns (MemView) {\n uint256 len_ = memView.len();\n uint256 oldLoc = memView.loc();\n\n uint256 ptr;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Load unused memory pointer\n ptr := mload(0x40)\n }\n // Revert if we're writing in occupied memory\n if (newLoc \u003c ptr) {\n revert OccupiedMemory();\n }\n bool res;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // use the identity precompile (0x04) to copy\n res := staticcall(gas(), 0x04, oldLoc, len_, newLoc, len_)\n }\n if (!res) revert PrecompileOutOfGas();\n return _unsafeBuildUnchecked({loc_: newLoc, len_: len_});\n }\n\n /**\n * @notice Join the views in memory, return an unsafe reference to the memory.\n * @dev Super Dangerous direct memory access.\n * This reference can be overwritten if anything else modifies memory (!!!).\n * As such it MUST be consumed IMMEDIATELY. Update the free memory pointer to ensure the copied data\n * is not overwritten. This function is private to prevent unsafe usage by callers.\n * @param memViews The memory views\n * @return The conjoined view pointing to the new memory\n */\n function _unsafeJoin(MemView[] memory memViews, uint256 location) private view returns (MemView) {\n uint256 ptr;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Load unused memory pointer\n ptr := mload(0x40)\n }\n // Revert if we're writing in occupied memory\n if (location \u003c ptr) {\n revert OccupiedMemory();\n }\n // Copy the views to the specified location one by one, by tracking the amount of copied bytes so far\n uint256 offset = 0;\n for (uint256 i = 0; i \u003c memViews.length;) {\n MemView memView = memViews[i];\n // We can use the unchecked math here as location + sum(view.length) will never overflow uint256\n unchecked {\n _unsafeCopyTo(memView, location + offset);\n offset += memView.len();\n ++i;\n }\n }\n return _unsafeBuildUnchecked({loc_: location, len_: offset});\n }\n}\n\n// Here we define common constants to enable their easier reusing later.\n\n// ══════════════════════════════════ MERKLE ═══════════════════════════════════\n/// @dev Height of the Agent Merkle Tree\nuint256 constant AGENT_TREE_HEIGHT = 32;\n/// @dev Height of the Origin Merkle Tree\nuint256 constant ORIGIN_TREE_HEIGHT = 32;\n/// @dev Height of the Snapshot Merkle Tree. Allows up to 64 leafs, e.g. up to 32 states\nuint256 constant SNAPSHOT_TREE_HEIGHT = 6;\n// ══════════════════════════════════ STRUCTS ══════════════════════════════════\n/// @dev See Attestation.sol: (bytes32,bytes32,uint32,uint40,uint40): 32+32+4+5+5\nuint256 constant ATTESTATION_LENGTH = 78;\n/// @dev See GasData.sol: (uint16,uint16,uint16,uint16,uint16,uint16): 2+2+2+2+2+2\nuint256 constant GAS_DATA_LENGTH = 12;\n/// @dev See Receipt.sol: (uint32,uint32,bytes32,bytes32,uint8,address,address,address): 4+4+32+32+1+20+20+20\nuint256 constant RECEIPT_LENGTH = 133;\n/// @dev See State.sol: (bytes32,uint32,uint32,uint40,uint40,GasData): 32+4+4+5+5+len(GasData)\nuint256 constant STATE_LENGTH = 50 + GAS_DATA_LENGTH;\n/// @dev Maximum amount of states in a single snapshot. Each state produces two leafs in the tree\nuint256 constant SNAPSHOT_MAX_STATES = 1 \u003c\u003c (SNAPSHOT_TREE_HEIGHT - 1);\n// ══════════════════════════════════ MESSAGE ══════════════════════════════════\n/// @dev See Header.sol: (uint8,uint32,uint32,uint32,uint32): 1+4+4+4+4\nuint256 constant HEADER_LENGTH = 17;\n/// @dev See Request.sol: (uint96,uint64,uint32): 12+8+4\nuint256 constant REQUEST_LENGTH = 24;\n/// @dev See Tips.sol: (uint64,uint64,uint64,uint64): 8+8+8+8\nuint256 constant TIPS_LENGTH = 32;\n/// @dev The amount of discarded last bits when encoding tip values\nuint256 constant TIPS_GRANULARITY = 32;\n/// @dev Tip values could be only the multiples of TIPS_MULTIPLIER\nuint256 constant TIPS_MULTIPLIER = 1 \u003c\u003c TIPS_GRANULARITY;\n// ══════════════════════════════ STATEMENT SALTS ══════════════════════════════\n/// @dev Salts for signing various statements\nbytes32 constant ATTESTATION_VALID_SALT = keccak256(\"ATTESTATION_VALID_SALT\");\nbytes32 constant ATTESTATION_INVALID_SALT = keccak256(\"ATTESTATION_INVALID_SALT\");\nbytes32 constant RECEIPT_VALID_SALT = keccak256(\"RECEIPT_VALID_SALT\");\nbytes32 constant RECEIPT_INVALID_SALT = keccak256(\"RECEIPT_INVALID_SALT\");\nbytes32 constant SNAPSHOT_VALID_SALT = keccak256(\"SNAPSHOT_VALID_SALT\");\nbytes32 constant STATE_INVALID_SALT = keccak256(\"STATE_INVALID_SALT\");\n// ═════════════════════════════════ PROTOCOL ══════════════════════════════════\n/// @dev Optimistic period for new agent roots in LightManager\nuint32 constant AGENT_ROOT_OPTIMISTIC_PERIOD = 1 days;\nuint32 constant BONDING_OPTIMISTIC_PERIOD = 1 days;\n/// @dev Amount of time without fresh data from Notaries before contract owner can resolve stuck disputes manually\nuint256 constant FRESH_DATA_TIMEOUT = 4 hours;\n/// @dev Maximum bytes per message = 2 KiB (somewhat arbitrarily set to begin)\nuint256 constant MAX_CONTENT_BYTES = 2 * 2 ** 10;\n/// @dev Domain of the Synapse Chain\n// TODO: replace the placeholder with actual value (for MVP this is Optimism chainId)\nuint32 constant SYNAPSE_DOMAIN = 10;\n\n/// Receipt is a memory view over a formatted \"full receipt\" payload.\ntype Receipt is uint256;\n\nusing ReceiptLib for Receipt global;\n\n/// Receipt structure represents a Notary statement that a certain message has been executed in `ExecutionHub`.\n/// - It is possible to prove the correctness of the tips payload using the message hash, therefore tips are not\n/// included in the receipt.\n/// - Receipt is signed by a Notary and submitted to `Summit` in order to initiate the tips distribution for an\n/// executed message.\n/// - If a message execution fails the first time, the `finalExecutor` field will be set to zero address. In this\n/// case, when the message is finally executed successfully, the `finalExecutor` field will be updated. Both\n/// receipts will be considered valid.\n/// # Memory layout of Receipt fields\n///\n/// | Position | Field | Type | Bytes | Description |\n/// | ---------- | ------------- | ------- | ----- | ------------------------------------------------ |\n/// | [000..004) | origin | uint32 | 4 | Domain where message originated |\n/// | [004..008) | destination | uint32 | 4 | Domain where message was executed |\n/// | [008..040) | messageHash | bytes32 | 32 | Hash of the message |\n/// | [040..072) | snapshotRoot | bytes32 | 32 | Snapshot root used for proving the message |\n/// | [072..073) | stateIndex | uint8 | 1 | Index of state used for the snapshot proof |\n/// | [073..093) | attNotary | address | 20 | Notary who posted attestation with snapshot root |\n/// | [093..113) | firstExecutor | address | 20 | Executor who performed first valid execution |\n/// | [113..133) | finalExecutor | address | 20 | Executor who successfully executed the message |\nlibrary ReceiptLib {\n using MemViewLib for bytes;\n\n /// @dev The variables below are not supposed to be used outside of the library directly.\n uint256 private constant OFFSET_ORIGIN = 0;\n uint256 private constant OFFSET_DESTINATION = 4;\n uint256 private constant OFFSET_MESSAGE_HASH = 8;\n uint256 private constant OFFSET_SNAPSHOT_ROOT = 40;\n uint256 private constant OFFSET_STATE_INDEX = 72;\n uint256 private constant OFFSET_ATT_NOTARY = 73;\n uint256 private constant OFFSET_FIRST_EXECUTOR = 93;\n uint256 private constant OFFSET_FINAL_EXECUTOR = 113;\n\n // ═════════════════════════════════════════════════ RECEIPT ═════════════════════════════════════════════════════\n\n /**\n * @notice Returns a formatted Receipt payload with provided fields.\n * @param origin_ Domain where message originated\n * @param destination_ Domain where message was executed\n * @param messageHash_ Hash of the message\n * @param snapshotRoot_ Snapshot root used for proving the message\n * @param stateIndex_ Index of state used for the snapshot proof\n * @param attNotary_ Notary who posted attestation with snapshot root\n * @param firstExecutor_ Executor who performed first valid execution attempt\n * @param finalExecutor_ Executor who successfully executed the message\n * @return Formatted receipt\n */\n function formatReceipt(\n uint32 origin_,\n uint32 destination_,\n bytes32 messageHash_,\n bytes32 snapshotRoot_,\n uint8 stateIndex_,\n address attNotary_,\n address firstExecutor_,\n address finalExecutor_\n ) internal pure returns (bytes memory) {\n return abi.encodePacked(\n origin_, destination_, messageHash_, snapshotRoot_, stateIndex_, attNotary_, firstExecutor_, finalExecutor_\n );\n }\n\n /**\n * @notice Returns a Receipt view over the given payload.\n * @dev Will revert if the payload is not a receipt.\n */\n function castToReceipt(bytes memory payload) internal pure returns (Receipt) {\n return castToReceipt(payload.ref());\n }\n\n /**\n * @notice Casts a memory view to a Receipt view.\n * @dev Will revert if the memory view is not over a receipt.\n */\n function castToReceipt(MemView memView) internal pure returns (Receipt) {\n if (!isReceipt(memView)) revert UnformattedReceipt();\n return Receipt.wrap(MemView.unwrap(memView));\n }\n\n /// @notice Checks that a payload is a formatted Receipt.\n function isReceipt(MemView memView) internal pure returns (bool) {\n // Check payload length\n return memView.len() == RECEIPT_LENGTH;\n }\n\n /// @notice Returns the hash of an Receipt, that could be later signed by a Notary to signal\n /// that the receipt is valid.\n function hashValid(Receipt receipt) internal pure returns (bytes32) {\n // The final hash to sign is keccak(receiptSalt, keccak(receipt))\n return receipt.unwrap().keccakSalted(RECEIPT_VALID_SALT);\n }\n\n /// @notice Returns the hash of a Receipt, that could be later signed by a Guard to signal\n /// that the receipt is invalid.\n function hashInvalid(Receipt receipt) internal pure returns (bytes32) {\n // The final hash to sign is keccak(receiptBodyInvalidSalt, keccak(receipt))\n return receipt.unwrap().keccakSalted(RECEIPT_INVALID_SALT);\n }\n\n /// @notice Convenience shortcut for unwrapping a view.\n function unwrap(Receipt receipt) internal pure returns (MemView) {\n return MemView.wrap(Receipt.unwrap(receipt));\n }\n\n /// @notice Compares two Receipt structures.\n function equals(Receipt a, Receipt b) internal pure returns (bool) {\n // Length of a Receipt payload is fixed, so we just need to compare the hashes\n return a.unwrap().keccak() == b.unwrap().keccak();\n }\n\n // ═════════════════════════════════════════════ RECEIPT SLICING ═════════════════════════════════════════════════\n\n /// @notice Returns receipt's origin field\n function origin(Receipt receipt) internal pure returns (uint32) {\n return uint32(receipt.unwrap().indexUint({index_: OFFSET_ORIGIN, bytes_: 4}));\n }\n\n /// @notice Returns receipt's destination field\n function destination(Receipt receipt) internal pure returns (uint32) {\n return uint32(receipt.unwrap().indexUint({index_: OFFSET_DESTINATION, bytes_: 4}));\n }\n\n /// @notice Returns receipt's \"message hash\" field\n function messageHash(Receipt receipt) internal pure returns (bytes32) {\n return receipt.unwrap().index({index_: OFFSET_MESSAGE_HASH, bytes_: 32});\n }\n\n /// @notice Returns receipt's \"snapshot root\" field\n function snapshotRoot(Receipt receipt) internal pure returns (bytes32) {\n return receipt.unwrap().index({index_: OFFSET_SNAPSHOT_ROOT, bytes_: 32});\n }\n\n /// @notice Returns receipt's \"state index\" field\n function stateIndex(Receipt receipt) internal pure returns (uint8) {\n return uint8(receipt.unwrap().indexUint({index_: OFFSET_STATE_INDEX, bytes_: 1}));\n }\n\n /// @notice Returns receipt's \"attestation notary\" field\n function attNotary(Receipt receipt) internal pure returns (address) {\n return receipt.unwrap().indexAddress({index_: OFFSET_ATT_NOTARY});\n }\n\n /// @notice Returns receipt's \"first executor\" field\n function firstExecutor(Receipt receipt) internal pure returns (address) {\n return receipt.unwrap().indexAddress({index_: OFFSET_FIRST_EXECUTOR});\n }\n\n /// @notice Returns receipt's \"final executor\" field\n function finalExecutor(Receipt receipt) internal pure returns (address) {\n return receipt.unwrap().indexAddress({index_: OFFSET_FINAL_EXECUTOR});\n }\n}\n\n// solhint-disable ordering\ncontract ReceiptHarness {\n using ReceiptLib for bytes;\n using ReceiptLib for MemView;\n using MemViewLib for bytes;\n\n // Note: we don't add an empty test() function here, as it currently leads\n // to zero coverage on the corresponding library.\n\n // ══════════════════════════════════════════════════ GETTERS ══════════════════════════════════════════════════════\n\n function castToReceipt(bytes memory payload) public view returns (bytes memory) {\n // Walkaround to get the forge coverage working on libraries, see\n // https://github.com/foundry-rs/foundry/pull/3128#issuecomment-1241245086\n Receipt receipt = ReceiptLib.castToReceipt(payload);\n return receipt.unwrap().clone();\n }\n\n /// @notice Returns receipt's origin field\n function origin(bytes memory payload) public pure returns (uint32) {\n return payload.castToReceipt().origin();\n }\n\n /// @notice Returns receipt's destination field\n function destination(bytes memory payload) public pure returns (uint32) {\n return payload.castToReceipt().destination();\n }\n\n /// @notice Returns receipt's \"message hash\" field\n function messageHash(bytes memory payload) public pure returns (bytes32) {\n return payload.castToReceipt().messageHash();\n }\n\n /// @notice Returns receipt's \"snapshot root\" field\n function snapshotRoot(bytes memory payload) public pure returns (bytes32) {\n return payload.castToReceipt().snapshotRoot();\n }\n\n /// @notice Returns receipt's \"state index\" field\n function stateIndex(bytes memory payload) public pure returns (uint8) {\n return payload.castToReceipt().stateIndex();\n }\n\n /// @notice Returns receipt's \"attestation notary\" field\n function attNotary(bytes memory payload) public pure returns (address) {\n return payload.castToReceipt().attNotary();\n }\n\n /// @notice Returns receipt's \"first executor\" field\n function firstExecutor(bytes memory payload) public pure returns (address) {\n return payload.castToReceipt().firstExecutor();\n }\n\n /// @notice Returns receipt's \"final executor\" field\n function finalExecutor(bytes memory payload) public pure returns (address) {\n return payload.castToReceipt().finalExecutor();\n }\n\n function equals(bytes memory a, bytes memory b) public pure returns (bool) {\n return a.ref().castToReceipt().equals(b.ref().castToReceipt());\n }\n\n function isReceipt(bytes memory payload) public pure returns (bool) {\n return payload.ref().isReceipt();\n }\n\n function hashValid(bytes memory payload) public pure returns (bytes32) {\n return payload.ref().castToReceipt().hashValid();\n }\n\n function hashInvalid(bytes memory payload) public pure returns (bytes32) {\n return payload.ref().castToReceipt().hashInvalid();\n }\n\n // ════════════════════════════════════════════════ FORMATTERS ═════════════════════════════════════════════════════\n\n function formatReceipt(\n uint32 origin_,\n uint32 destination_,\n bytes32 messageHash_,\n bytes32 snapshotRoot_,\n uint8 stateIndex_,\n address attNotary_,\n address firstExecutor_,\n address finalExecutor_\n ) public pure returns (bytes memory) {\n return ReceiptLib.formatReceipt(\n origin_, destination_, messageHash_, snapshotRoot_, stateIndex_, attNotary_, firstExecutor_, finalExecutor_\n );\n }\n}\n","language":"Solidity","languageVersion":"0.8.17","compilerVersion":"0.8.17","compilerOptions":"--combined-json bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc,metadata,hashes --optimize --optimize-runs 10000 --allow-paths ., ./, ../","srcMap":"36765:3867:0:-:0;;;;;;;;;;;;;;;;;;;","srcMapRuntime":"36765:3867:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;39419:117;;;;;;:::i;:::-;;:::i;:::-;;;1475:14:1;;1468:22;1450:41;;1438:2;1423:18;39419:117:0;;;;;;;;39259:154;;;;;;:::i;:::-;;:::i;38914:138::-;;;;;;:::i;:::-;;:::i;:::-;;;2222:42:1;2210:55;;;2192:74;;2180:2;2165:18;38914:138:0;2046:226:1;38524:130:0;;;;;;:::i;:::-;;:::i;:::-;;;2449:4:1;2437:17;;;2419:36;;2407:2;2392:18;38524:130:0;2277:184:1;37358:346:0;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;39684:140::-;;;;;;:::i;:::-;;:::i;:::-;;;3222:25:1;;;3210:2;3195:18;39684:140:0;3076:177:1;39542:136:0;;;;;;:::i;:::-;;:::i;38328:::-;;;;;;:::i;:::-;;:::i;40154:476::-;;;;;;:::i;:::-;32492:147;;;5027:3:1;5023:16;;;4932:66;5019:25;;;32492:147:0;;;5007:38:1;5078:16;;;;5074:25;;;5061:11;;;5054:46;5116:11;;;5109:27;;;;5152:12;;;5145:28;;;;5211:3;5207:16;;;;5225:66;5203:89;5189:12;;;5182:111;40437:12:0;5412:15:1;;;5312:66;5408:24;;;5394:12;;;5387:46;5467:15;;;5463:24;;5449:12;;;5442:46;5523:15;;;;5519:24;5504:13;;;5497:47;32492:147:0;;;;;;;;;5560:13:1;;;;32492:147:0;;;40154:476;37938:133;;;;;;:::i;:::-;;:::i;:::-;;;4582:10:1;4570:23;;;4552:42;;4540:2;4525:18;37938:133:0;4408:192:1;37757:123:0;;;;;;:::i;:::-;;:::i;38721:130::-;;;;;;:::i;:::-;;:::i;38132:134::-;;;;;;:::i;:::-;;:::i;39115:138::-;;;;;;:::i;:::-;;:::i;39419:117::-;39481:4;39504:25;:13;:7;:11;:13::i;:::-;11752:17;11726:43;26082:3;33435:31;;33321:152;39504:25;39497:32;39419:117;-1:-1:-1;;39419:117:0:o;39259:154::-;39328:4;39351:55;39382:23;:7;:1;:5;:7::i;:::-;:21;:23::i;:::-;39351;:7;:1;:5;:7::i;:23::-;:30;;:55::i;:::-;39344:62;39259:154;-1:-1:-1;;;39259:154:0:o;38914:138::-;38980:7;39006:39;:23;:7;:21;:23::i;:::-;:37;:39::i;38524:130::-;38587:5;38611:36;:23;:7;:21;:23::i;:::-;:34;:36::i;37358:346::-;37424:12;37605:15;37623:33;37648:7;37623:24;:33::i;:::-;37605:51;-1:-1:-1;37673:24:0;37605:51;37673:22;:24::i;39684:140::-;39748:7;39774:43;:29;:13;:7;:11;:13::i;:29::-;:41;:43::i;39542:136::-;39604:7;39630:41;:29;:13;:7;:11;:13::i;:29::-;:39;:41::i;38328:136::-;38393:7;38419:38;:23;:7;:21;:23::i;:::-;:36;:38::i;37938:133::-;38002:6;38027:37;:23;:7;:21;:23::i;:::-;:35;:37::i;37757:123::-;37816:6;37841:32;:23;:7;:21;:23::i;:::-;:30;:32::i;38721:130::-;38783:7;38809:35;:23;:7;:21;:23::i;:::-;:33;:35::i;38132:134::-;38196:7;38222:37;:23;:7;:21;:23::i;:::-;:35;:37::i;39115:138::-;39181:7;39207:39;:23;:7;:21;:23::i;:::-;:37;:39::i;7459:569::-;7547:10;;7513:7;;7973:4;7964:14;;8004:17;7964:14;7547:10;8004:5;:17::i;:::-;7997:24;7459:569;-1:-1:-1;;;;7459:569:0:o;33058:195::-;33121:7;26082:3;11752:17;11726:43;;33435:31;33140:52;;33172:20;;;;;;;;;;;;;;33140:52;-1:-1:-1;33237:7:0;33058:195::o;34441:220::-;34502:4;34635:19;:1;:17;:19::i;:::-;34612;:1;34635:17;:19::i;34612:::-;:42;;34441:220;-1:-1:-1;;;34441:220:0:o;32787:129::-;32855:7;32881:28;32895:13;:7;:11;:13::i;36354:158::-;36417:7;36443:62;31086:2;36443:7;:16;:29;;:62::i;35909:165::-;35969:5;35999:67;30976:2;36063:1;35999:7;:16;:26;:67;:26;:67::i;8566:1041::-;8812:4;8806:11;;8942:34;8956:7;8971:4;8965:10;;8942:13;:34::i;:::-;-1:-1:-1;11752:17:0;11726:43;;9188:12;12584:2;12568:18;;12952:20;;9484;;9506:4;9480:31;9474:4;9467:45;-1:-1:-1;9574:17:0;;8566:1041;;-1:-1:-1;8566:1041:0:o;33964:230::-;34025:7;34136:51;27677:33;34136:7;:16;:29;;:51::i;33611:215::-;33670:7;33770:49;27604:31;33770:7;:16;34260:126;35688:161;35750:7;35776:66;30922:2;35838;35776:7;:16;:22;:66;:22;:66::i;35238:168::-;35299:6;35331:67;30813:1;;35331:7;:16;34260:126;35022:158;35078:6;35110:62;35078:6;35169:1;35110:7;:16;34260:126;36141:150;36200:7;36226:58;31029:2;36226:7;:16;34260:126;35467:159;35528:7;35554:65;30867:1;35615:2;35554:7;:16;34260:126;36575:158;36638:7;36664:62;31143:3;36664:7;:16;34260:126;6579:540;6645:7;;6679:11;6686:4;6679;:11;:::i;:::-;6664:26;;6958:4;6952:11;6946:4;6943:21;6940:38;;;-1:-1:-1;6975:1:0;6940:38;7001:4;7009:1;7001:9;6997:66;;7033:19;;;;;;;;;;;;;;6997:66;21849:3;21841:11;;;21840:20;;7079:33;21516:352;13490:292;13546:14;13572:12;13587:13;:7;11385:3;11358:30;;11199:196;13587:13;11752:17;11726:43;;;;13745:21;;;;;-1:-1:-1;;13490:292:0:o;20840:225::-;20918:7;21027:29;:7;21045:6;21053:2;20039:538;20130:7;;20172:29;:7;20186:6;20194;20172:13;:29::i;:::-;20542:2;:11;;;20558:1;20541:18;20515:45;;-1:-1:-1;;20039:538:0;;;;;:::o;22500:842::-;22839:4;22833:11;22578:7;;11752:17;11726:43;;;11385:3;11358:30;;;;22921:12;;;22917:66;;;22956:16;;;;;;;;;;;;;;22917:66;22992:8;23208:4;23200:6;23194:4;23186:6;23180:4;23173:5;23162:51;23155:58;;23237:3;23232:37;;23249:20;;;;;;;;;;;;;;23232:37;21849:3;21841:11;;;21840:20;;23279:56;22500:842;-1:-1:-1;;;;;;;22500:842:0:o;14045:169::-;14121:20;14183:4;14189:16;:7;:14;:16::i;:::-;14170:36;;;;;;6025:19:1;;;;6060:12;;6053:28;6097:12;;14170:36:0;;;;;;;;;;;;14160:47;;;;;;14153:54;;14045:169;;;;:::o;18347:1334::-;18434:14;18464:6;18474:1;18464:11;18460:59;;-1:-1:-1;18506:1:0;18491:17;;18460:59;18605:2;18596:6;:11;18592:65;;;18630:16;;;;;;;;;;;;;;18592:65;11752:17;11726:43;;18736:15;18745:6;18736;:15;:::i;:::-;:31;18732:82;;;18790:13;;;;;;;;;;;;;;18732:82;18853:1;18843:11;;;18823:17;18893:13;:7;11385:3;11358:30;;11199:196;18893:13;19640:17;;;19634:24;19351:66;19332:17;;;;;19328:90;;;;19630:35;;18347:1334;-1:-1:-1;;;;18347:1334:0:o;14:184:1:-;66:77;63:1;56:88;163:4;160:1;153:15;187:4;184:1;177:15;203:777;245:5;298:3;291:4;283:6;279:17;275:27;265:55;;316:1;313;306:12;265:55;352:6;339:20;378:18;415:2;411;408:10;405:36;;;421:18;;:::i;:::-;555:2;549:9;617:4;609:13;;460:66;605:22;;;629:2;601:31;597:40;585:53;;;653:18;;;673:22;;;650:46;647:72;;;699:18;;:::i;:::-;739:10;735:2;728:22;774:2;766:6;759:18;820:3;813:4;808:2;800:6;796:15;792:26;789:35;786:55;;;837:1;834;827:12;786:55;901:2;894:4;886:6;882:17;875:4;867:6;863:17;850:54;948:1;941:4;936:2;928:6;924:15;920:26;913:37;968:6;959:15;;;;;;203:777;;;;:::o;985:320::-;1053:6;1106:2;1094:9;1085:7;1081:23;1077:32;1074:52;;;1122:1;1119;1112:12;1074:52;1162:9;1149:23;1195:18;1187:6;1184:30;1181:50;;;1227:1;1224;1217:12;1181:50;1250:49;1291:7;1282:6;1271:9;1267:22;1250:49;:::i;1502:539::-;1588:6;1596;1649:2;1637:9;1628:7;1624:23;1620:32;1617:52;;;1665:1;1662;1655:12;1617:52;1705:9;1692:23;1734:18;1775:2;1767:6;1764:14;1761:34;;;1791:1;1788;1781:12;1761:34;1814:49;1855:7;1846:6;1835:9;1831:22;1814:49;:::i;:::-;1804:59;;1916:2;1905:9;1901:18;1888:32;1872:48;;1945:2;1935:8;1932:16;1929:36;;;1961:1;1958;1951:12;1929:36;;1984:51;2027:7;2016:8;2005:9;2001:24;1984:51;:::i;:::-;1974:61;;;1502:539;;;;;:::o;2466:605::-;2576:4;2605:2;2634;2623:9;2616:21;2666:6;2660:13;2709:6;2704:2;2693:9;2689:18;2682:34;2734:1;2744:140;2758:6;2755:1;2752:13;2744:140;;;2853:14;;;2849:23;;2843:30;2819:17;;;2838:2;2815:26;2808:66;2773:10;;2744:140;;;2748:3;2933:1;2928:2;2919:6;2908:9;2904:22;2900:31;2893:42;3062:2;2992:66;2987:2;2979:6;2975:15;2971:88;2960:9;2956:104;2952:113;2944:121;;;;2466:605;;;;:::o;3258:163::-;3325:20;;3385:10;3374:22;;3364:33;;3354:61;;3411:1;3408;3401:12;3354:61;3258:163;;;:::o;3426:196::-;3494:20;;3554:42;3543:54;;3533:65;;3523:93;;3612:1;3609;3602:12;3627:776;3745:6;3753;3761;3769;3777;3785;3793;3801;3854:3;3842:9;3833:7;3829:23;3825:33;3822:53;;;3871:1;3868;3861:12;3822:53;3894:28;3912:9;3894:28;:::i;:::-;3884:38;;3941:37;3974:2;3963:9;3959:18;3941:37;:::i;:::-;3931:47;;4025:2;4014:9;4010:18;3997:32;3987:42;;4076:2;4065:9;4061:18;4048:32;4038:42;;4130:3;4119:9;4115:19;4102:33;4175:4;4168:5;4164:16;4157:5;4154:27;4144:55;;4195:1;4192;4185:12;4144:55;4218:5;-1:-1:-1;4242:39:1;4276:3;4261:19;;4242:39;:::i;:::-;4232:49;;4300:39;4334:3;4323:9;4319:19;4300:39;:::i;:::-;4290:49;;4358:39;4392:3;4381:9;4377:19;4358:39;:::i;:::-;4348:49;;3627:776;;;;;;;;;;;:::o;5584:279::-;5649:9;;;5670:10;;;5667:190;;;5713:77;5710:1;5703:88;5814:4;5811:1;5804:15;5842:4;5839:1;5832:15","abiDefinition":[{"inputs":[],"name":"IndexedTooMuch","type":"error"},{"inputs":[],"name":"OccupiedMemory","type":"error"},{"inputs":[],"name":"PrecompileOutOfGas","type":"error"},{"inputs":[],"name":"UnallocatedMemory","type":"error"},{"inputs":[],"name":"UnformattedReceipt","type":"error"},{"inputs":[],"name":"ViewOverrun","type":"error"},{"inputs":[{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"attNotary","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"castToReceipt","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"destination","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"a","type":"bytes"},{"internalType":"bytes","name":"b","type":"bytes"}],"name":"equals","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"finalExecutor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"firstExecutor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint32","name":"origin_","type":"uint32"},{"internalType":"uint32","name":"destination_","type":"uint32"},{"internalType":"bytes32","name":"messageHash_","type":"bytes32"},{"internalType":"bytes32","name":"snapshotRoot_","type":"bytes32"},{"internalType":"uint8","name":"stateIndex_","type":"uint8"},{"internalType":"address","name":"attNotary_","type":"address"},{"internalType":"address","name":"firstExecutor_","type":"address"},{"internalType":"address","name":"finalExecutor_","type":"address"}],"name":"formatReceipt","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"hashInvalid","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"hashValid","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"isReceipt","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"messageHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"origin","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"snapshotRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"stateIndex","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"}],"userDoc":{"kind":"user","methods":{"attNotary(bytes)":{"notice":"Returns receipt's \"attestation notary\" field"},"destination(bytes)":{"notice":"Returns receipt's destination field"},"finalExecutor(bytes)":{"notice":"Returns receipt's \"final executor\" field"},"firstExecutor(bytes)":{"notice":"Returns receipt's \"first executor\" field"},"messageHash(bytes)":{"notice":"Returns receipt's \"message hash\" field"},"origin(bytes)":{"notice":"Returns receipt's origin field"},"snapshotRoot(bytes)":{"notice":"Returns receipt's \"snapshot root\" field"},"stateIndex(bytes)":{"notice":"Returns receipt's \"state index\" field"}},"version":1},"developerDoc":{"kind":"dev","methods":{},"version":1},"metadata":"{\"compiler\":{\"version\":\"0.8.17+commit.8df45f5f\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"IndexedTooMuch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OccupiedMemory\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PrecompileOutOfGas\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnallocatedMemory\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnformattedReceipt\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ViewOverrun\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"attNotary\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"castToReceipt\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"destination\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"a\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"b\",\"type\":\"bytes\"}],\"name\":\"equals\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"finalExecutor\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"firstExecutor\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"origin_\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"destination_\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"messageHash_\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"snapshotRoot_\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"stateIndex_\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"attNotary_\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"firstExecutor_\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"finalExecutor_\",\"type\":\"address\"}],\"name\":\"formatReceipt\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"hashInvalid\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"hashValid\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"isReceipt\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"messageHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"origin\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"snapshotRoot\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"payload\",\"type\":\"bytes\"}],\"name\":\"stateIndex\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"pure\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"attNotary(bytes)\":{\"notice\":\"Returns receipt's \\\"attestation notary\\\" field\"},\"destination(bytes)\":{\"notice\":\"Returns receipt's destination field\"},\"finalExecutor(bytes)\":{\"notice\":\"Returns receipt's \\\"final executor\\\" field\"},\"firstExecutor(bytes)\":{\"notice\":\"Returns receipt's \\\"first executor\\\" field\"},\"messageHash(bytes)\":{\"notice\":\"Returns receipt's \\\"message hash\\\" field\"},\"origin(bytes)\":{\"notice\":\"Returns receipt's origin field\"},\"snapshotRoot(bytes)\":{\"notice\":\"Returns receipt's \\\"snapshot root\\\" field\"},\"stateIndex(bytes)\":{\"notice\":\"Returns receipt's \\\"state index\\\" field\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"solidity/ReceiptHarness.t.sol\":\"ReceiptHarness\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"solidity/ReceiptHarness.t.sol\":{\"keccak256\":\"0xf16784d67df259efee36f809c5a0756a6febd1ad6f07aa4b77fa52127ec5f9fa\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a097afb3038e4aaafadb147a3a36cf3bb4751c0ff22351dfe3f2c90e65194922\",\"dweb:/ipfs/QmTkBucx2oKmEGDMzHirHmRgzpCsp62KZ7WZYoRHPXjFuA\"]}},\"version\":1}"},"hashes":{"attNotary(bytes)":"e152a8cb","castToReceipt(bytes)":"5cab5d3b","destination(bytes)":"c81aa9c8","equals(bytes,bytes)":"137e618a","finalExecutor(bytes)":"f7e6a05b","firstExecutor(bytes)":"27f2ee36","formatReceipt(uint32,uint32,bytes32,bytes32,uint8,address,address,address)":"c3bfda6c","hashInvalid(bytes)":"60cf3bf0","hashValid(bytes)":"730dbf63","isReceipt(bytes)":"0bb3b580","messageHash(bytes)":"ed54c3b6","origin(bytes)":"cb3eb0e1","snapshotRoot(bytes)":"854dfcd7","stateIndex(bytes)":"595271d1"}},"solidity/ReceiptHarness.t.sol:ReceiptLib":{"code":"0x60566037600b82828239805160001a607314602a57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220f103584dc9fa399972815cba256acdf18451db63a1ed6da7fafad4bb4653dcf564736f6c63430008110033","runtime-code":"0x73000000000000000000000000000000000000000030146080604052600080fdfea2646970667358221220f103584dc9fa399972815cba256acdf18451db63a1ed6da7fafad4bb4653dcf564736f6c63430008110033","info":{"source":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.17;\n\n// ══════════════════════════════ INVALID CALLER ═══════════════════════════════\n\nerror CallerNotAgentManager();\nerror CallerNotDestination();\nerror CallerNotInbox();\nerror CallerNotSummit();\n\n// ══════════════════════════════ INCORRECT DATA ═══════════════════════════════\n\nerror IncorrectAttestation();\nerror IncorrectAgentDomain();\nerror IncorrectAgentIndex();\nerror IncorrectAgentProof();\nerror IncorrectDataHash();\nerror IncorrectDestinationDomain();\nerror IncorrectOriginDomain();\nerror IncorrectSnapshotProof();\nerror IncorrectSnapshotRoot();\nerror IncorrectState();\nerror IncorrectStatesAmount();\nerror IncorrectTipsProof();\nerror IncorrectVersionLength();\n\nerror IncorrectNonce();\nerror IncorrectSender();\nerror IncorrectRecipient();\n\nerror FlagOutOfRange();\nerror IndexOutOfRange();\nerror NonceOutOfRange();\n\nerror OutdatedNonce();\n\nerror UnformattedAttestation();\nerror UnformattedAttestationReport();\nerror UnformattedBaseMessage();\nerror UnformattedCallData();\nerror UnformattedCallDataPrefix();\nerror UnformattedMessage();\nerror UnformattedReceipt();\nerror UnformattedReceiptReport();\nerror UnformattedSignature();\nerror UnformattedSnapshot();\nerror UnformattedState();\nerror UnformattedStateReport();\n\n// ═══════════════════════════════ MERKLE TREES ════════════════════════════════\n\nerror LeafNotProven();\nerror MerkleTreeFull();\nerror NotEnoughLeafs();\nerror TreeHeightTooLow();\n\n// ═════════════════════════════ OPTIMISTIC PERIOD ═════════════════════════════\n\nerror BaseClientOptimisticPeriod();\nerror MessageOptimisticPeriod();\nerror SlashAgentOptimisticPeriod();\nerror WithdrawTipsOptimisticPeriod();\nerror ZeroProofMaturity();\n\n// ═══════════════════════════════ AGENT MANAGER ═══════════════════════════════\n\nerror AgentNotGuard();\nerror AgentNotNotary();\n\nerror AgentCantBeAdded();\nerror AgentNotActive();\nerror AgentNotActiveNorUnstaking();\nerror AgentNotFraudulent();\nerror AgentNotUnstaking();\nerror AgentUnknown();\n\nerror DisputeAlreadyResolved();\nerror DisputeNotOpened();\nerror DisputeNotStuck();\nerror GuardInDispute();\nerror NotaryInDispute();\n\nerror MustBeSynapseDomain();\nerror SynapseDomainForbidden();\n\n// ════════════════════════════════ DESTINATION ════════════════════════════════\n\nerror AlreadyExecuted();\nerror AlreadyFailed();\nerror DuplicatedSnapshotRoot();\nerror IncorrectMagicValue();\nerror GasLimitTooLow();\nerror GasSuppliedTooLow();\n\n// ══════════════════════════════════ ORIGIN ═══════════════════════════════════\n\nerror ContentLengthTooBig();\nerror EthTransferFailed();\nerror InsufficientEthBalance();\n\n// ════════════════════════════════ GAS ORACLE ═════════════════════════════════\n\nerror LocalGasDataNotSet();\nerror RemoteGasDataNotSet();\n\n// ═══════════════════════════════════ TIPS ════════════════════════════════════\n\nerror TipsClaimMoreThanEarned();\nerror TipsClaimZero();\nerror TipsOverflow();\nerror TipsValueTooLow();\n\n// ════════════════════════════════ MEMORY VIEW ════════════════════════════════\n\nerror IndexedTooMuch();\nerror ViewOverrun();\nerror OccupiedMemory();\nerror UnallocatedMemory();\nerror PrecompileOutOfGas();\n\n// ═════════════════════════════════ MULTICALL ═════════════════════════════════\n\nerror MulticallFailed();\n\n/// @dev MemView is an untyped view over a portion of memory to be used instead of `bytes memory`\ntype MemView is uint256;\n\n/// @dev Attach library functions to MemView\nusing MemViewLib for MemView global;\n\n/// @notice Library for operations with the memory views.\n/// Forked from https://github.com/summa-tx/memview-sol with several breaking changes:\n/// - The codebase is ported to Solidity 0.8\n/// - Custom errors are added\n/// - The runtime type checking is replaced with compile-time check provided by User-Defined Value Types\n/// https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types\n/// - uint256 is used as the underlying type for the \"memory view\" instead of bytes29.\n/// It is wrapped into MemView custom type in order not to be confused with actual integers.\n/// - Therefore the \"type\" field is discarded, allowing to allocate 16 bytes for both view location and length\n/// - The documentation is expanded\n/// - Library functions unused by the rest of the codebase are removed\n// - Very pretty code separators are added :)\nlibrary MemViewLib {\n /// @notice Stack layout for uint256 (from highest bits to lowest)\n /// (32 .. 16] loc 16 bytes Memory address of underlying bytes\n /// (16 .. 00] len 16 bytes Length of underlying bytes\n\n // ═══════════════════════════════════════════ BUILDING MEMORY VIEW ════════════════════════════════════════════════\n\n /**\n * @notice Instantiate a new untyped memory view. This should generally not be called directly.\n * Prefer `ref` wherever possible.\n * @param loc_ The memory address\n * @param len_ The length\n * @return The new view with the specified location and length\n */\n function build(uint256 loc_, uint256 len_) internal pure returns (MemView) {\n uint256 end_ = loc_ + len_;\n // Make sure that a view is not constructed that points to unallocated memory\n // as this could be indicative of a buffer overflow attack\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n if gt(end_, mload(0x40)) { end_ := 0 }\n }\n if (end_ == 0) {\n revert UnallocatedMemory();\n }\n return _unsafeBuildUnchecked(loc_, len_);\n }\n\n /**\n * @notice Instantiate a memory view from a byte array.\n * @dev Note that due to Solidity memory representation, it is not possible to\n * implement a deref, as the `bytes` type stores its len in memory.\n * @param arr The byte array\n * @return The memory view over the provided byte array\n */\n function ref(bytes memory arr) internal pure returns (MemView) {\n uint256 len_ = arr.length;\n // `bytes arr` is stored in memory in the following way\n // 1. First, uint256 arr.length is stored. That requires 32 bytes (0x20).\n // 2. Then, the array data is stored.\n uint256 loc_;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // We add 0x20, so that the view starts exactly where the array data starts\n loc_ := add(arr, 0x20)\n }\n return build(loc_, len_);\n }\n\n // ════════════════════════════════════════════ CLONING MEMORY VIEW ════════════════════════════════════════════════\n\n /**\n * @notice Copies the referenced memory to a new loc in memory, returning a `bytes` pointing to the new memory.\n * @param memView The memory view\n * @return arr The cloned byte array\n */\n function clone(MemView memView) internal view returns (bytes memory arr) {\n uint256 ptr;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Load unused memory pointer\n ptr := mload(0x40)\n // This is where the byte array will be stored\n arr := ptr\n }\n unchecked {\n _unsafeCopyTo(memView, ptr + 0x20);\n }\n // `bytes arr` is stored in memory in the following way\n // 1. First, uint256 arr.length is stored. That requires 32 bytes (0x20).\n // 2. Then, the array data is stored.\n uint256 len_ = memView.len();\n uint256 footprint_ = memView.footprint();\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Write new unused pointer: the old value + array footprint + 32 bytes to store the length\n mstore(0x40, add(add(ptr, footprint_), 0x20))\n // Write len of new array (in bytes)\n mstore(ptr, len_)\n }\n }\n\n /**\n * @notice Copies all views, joins them into a new bytearray.\n * @param memViews The memory views\n * @return arr The new byte array with joined data behind the given views\n */\n function join(MemView[] memory memViews) internal view returns (bytes memory arr) {\n uint256 ptr;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Load unused memory pointer\n ptr := mload(0x40)\n // This is where the byte array will be stored\n arr := ptr\n }\n MemView newView;\n unchecked {\n newView = _unsafeJoin(memViews, ptr + 0x20);\n }\n uint256 len_ = newView.len();\n uint256 footprint_ = newView.footprint();\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Write new unused pointer: the old value + array footprint + 32 bytes to store the length\n mstore(0x40, add(add(ptr, footprint_), 0x20))\n // Write len of new array (in bytes)\n mstore(ptr, len_)\n }\n }\n\n // ══════════════════════════════════════════ INSPECTING MEMORY VIEW ═══════════════════════════════════════════════\n\n /**\n * @notice Returns the memory address of the underlying bytes.\n * @param memView The memory view\n * @return loc_ The memory address\n */\n function loc(MemView memView) internal pure returns (uint256 loc_) {\n // loc is stored in the highest 16 bytes of the underlying uint256\n return MemView.unwrap(memView) \u003e\u003e 128;\n }\n\n /**\n * @notice Returns the number of bytes of the view.\n * @param memView The memory view\n * @return len_ The length of the view\n */\n function len(MemView memView) internal pure returns (uint256 len_) {\n // len is stored in the lowest 16 bytes of the underlying uint256\n return MemView.unwrap(memView) \u0026 type(uint128).max;\n }\n\n /**\n * @notice Returns the endpoint of `memView`.\n * @param memView The memory view\n * @return end_ The endpoint of `memView`\n */\n function end(MemView memView) internal pure returns (uint256 end_) {\n // The endpoint never overflows uint128, let alone uint256, so we could use unchecked math here\n unchecked {\n return memView.loc() + memView.len();\n }\n }\n\n /**\n * @notice Returns the number of memory words this memory view occupies, rounded up.\n * @param memView The memory view\n * @return words_ The number of memory words\n */\n function words(MemView memView) internal pure returns (uint256 words_) {\n // returning ceil(length / 32.0)\n unchecked {\n return (memView.len() + 31) \u003e\u003e 5;\n }\n }\n\n /**\n * @notice Returns the in-memory footprint of a fresh copy of the view.\n * @param memView The memory view\n * @return footprint_ The in-memory footprint of a fresh copy of the view.\n */\n function footprint(MemView memView) internal pure returns (uint256 footprint_) {\n // words() * 32\n return memView.words() \u003c\u003c 5;\n }\n\n // ════════════════════════════════════════════ HASHING MEMORY VIEW ════════════════════════════════════════════════\n\n /**\n * @notice Returns the keccak256 hash of the underlying memory\n * @param memView The memory view\n * @return digest The keccak256 hash of the underlying memory\n */\n function keccak(MemView memView) internal pure returns (bytes32 digest) {\n uint256 loc_ = memView.loc();\n uint256 len_ = memView.len();\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n digest := keccak256(loc_, len_)\n }\n }\n\n /**\n * @notice Adds a salt to the keccak256 hash of the underlying data and returns the keccak256 hash of the\n * resulting data.\n * @param memView The memory view\n * @return digestSalted keccak256(salt, keccak256(memView))\n */\n function keccakSalted(MemView memView, bytes32 salt) internal pure returns (bytes32 digestSalted) {\n return keccak256(bytes.concat(salt, memView.keccak()));\n }\n\n // ════════════════════════════════════════════ SLICING MEMORY VIEW ════════════════════════════════════════════════\n\n /**\n * @notice Safe slicing without memory modification.\n * @param memView The memory view\n * @param index_ The start index\n * @param len_ The length\n * @return The new view for the slice of the given length starting from the given index\n */\n function slice(MemView memView, uint256 index_, uint256 len_) internal pure returns (MemView) {\n uint256 loc_ = memView.loc();\n // Ensure it doesn't overrun the view\n if (loc_ + index_ + len_ \u003e memView.end()) {\n revert ViewOverrun();\n }\n // Build a view starting from index with the given length\n unchecked {\n // loc_ + index_ \u003c= memView.end()\n return build({loc_: loc_ + index_, len_: len_});\n }\n }\n\n /**\n * @notice Shortcut to `slice`. Gets a view representing bytes from `index` to end(memView).\n * @param memView The memory view\n * @param index_ The start index\n * @return The new view for the slice starting from the given index until the initial view endpoint\n */\n function sliceFrom(MemView memView, uint256 index_) internal pure returns (MemView) {\n uint256 len_ = memView.len();\n // Ensure it doesn't overrun the view\n if (index_ \u003e len_) {\n revert ViewOverrun();\n }\n // Build a view starting from index with the given length\n unchecked {\n // index_ \u003c= len_ =\u003e memView.loc() + index_ \u003c= memView.loc() + memView.len() == memView.end()\n return build({loc_: memView.loc() + index_, len_: len_ - index_});\n }\n }\n\n /**\n * @notice Shortcut to `slice`. Gets a view representing the first `len` bytes.\n * @param memView The memory view\n * @param len_ The length\n * @return The new view for the slice of the given length starting from the initial view beginning\n */\n function prefix(MemView memView, uint256 len_) internal pure returns (MemView) {\n return memView.slice({index_: 0, len_: len_});\n }\n\n /**\n * @notice Shortcut to `slice`. Gets a view representing the last `len` byte.\n * @param memView The memory view\n * @param len_ The length\n * @return The new view for the slice of the given length until the initial view endpoint\n */\n function postfix(MemView memView, uint256 len_) internal pure returns (MemView) {\n uint256 viewLen = memView.len();\n // Ensure it doesn't overrun the view\n if (len_ \u003e viewLen) {\n revert ViewOverrun();\n }\n // Could do the unchecked math due to the check above\n uint256 index_;\n unchecked {\n index_ = viewLen - len_;\n }\n // Build a view starting from index with the given length\n unchecked {\n // len_ \u003c= memView.len() =\u003e memView.loc() \u003c= loc_ \u003c= memView.end()\n return build({loc_: memView.loc() + viewLen - len_, len_: len_});\n }\n }\n\n // ═══════════════════════════════════════════ INDEXING MEMORY VIEW ════════════════════════════════════════════════\n\n /**\n * @notice Load up to 32 bytes from the view onto the stack.\n * @dev Returns a bytes32 with only the `bytes_` HIGHEST bytes set.\n * This can be immediately cast to a smaller fixed-length byte array.\n * To automatically cast to an integer, use `indexUint`.\n * @param memView The memory view\n * @param index_ The index\n * @param bytes_ The amount of bytes to load onto the stack\n * @return result The 32 byte result having only `bytes_` highest bytes set\n */\n function index(MemView memView, uint256 index_, uint256 bytes_) internal pure returns (bytes32 result) {\n if (bytes_ == 0) {\n return bytes32(0);\n }\n // Can't load more than 32 bytes to the stack in one go\n if (bytes_ \u003e 32) {\n revert IndexedTooMuch();\n }\n // The last indexed byte should be within view boundaries\n if (index_ + bytes_ \u003e memView.len()) {\n revert ViewOverrun();\n }\n uint256 bitLength = bytes_ \u003c\u003c 3; // bytes_ * 8\n uint256 loc_ = memView.loc();\n // Get a mask with `bitLength` highest bits set\n uint256 mask;\n // 0x800...00 binary representation is 100...00\n // sar stands for \"signed arithmetic shift\": https://en.wikipedia.org/wiki/Arithmetic_shift\n // sar(N-1, 100...00) = 11...100..00, with exactly N highest bits set to 1\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n mask := sar(sub(bitLength, 1), 0x8000000000000000000000000000000000000000000000000000000000000000)\n }\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Load a full word using index offset, and apply mask to ignore non-relevant bytes\n result := and(mload(add(loc_, index_)), mask)\n }\n }\n\n /**\n * @notice Parse an unsigned integer from the view at `index`.\n * @dev Requires that the view have \u003e= `bytes_` bytes following that index.\n * @param memView The memory view\n * @param index_ The index\n * @param bytes_ The amount of bytes to load onto the stack\n * @return The unsigned integer\n */\n function indexUint(MemView memView, uint256 index_, uint256 bytes_) internal pure returns (uint256) {\n bytes32 indexedBytes = memView.index(index_, bytes_);\n // `index()` returns left-aligned `bytes_`, while integers are right-aligned\n // Shifting here to right-align with the full 32 bytes word: need to shift right `(32 - bytes_)` bytes\n unchecked {\n // memView.index() reverts when bytes_ \u003e 32, thus unchecked math\n return uint256(indexedBytes) \u003e\u003e ((32 - bytes_) \u003c\u003c 3);\n }\n }\n\n /**\n * @notice Parse an address from the view at `index`.\n * @dev Requires that the view have \u003e= 20 bytes following that index.\n * @param memView The memory view\n * @param index_ The index\n * @return The address\n */\n function indexAddress(MemView memView, uint256 index_) internal pure returns (address) {\n // index 20 bytes as `uint160`, and then cast to `address`\n return address(uint160(memView.indexUint(index_, 20)));\n }\n\n // ══════════════════════════════════════════════ PRIVATE HELPERS ══════════════════════════════════════════════════\n\n /// @dev Returns a memory view over the specified memory location\n /// without checking if it points to unallocated memory.\n function _unsafeBuildUnchecked(uint256 loc_, uint256 len_) private pure returns (MemView) {\n // There is no scenario where loc or len would overflow uint128, so we omit this check.\n // We use the highest 128 bits to encode the location and the lowest 128 bits to encode the length.\n return MemView.wrap((loc_ \u003c\u003c 128) | len_);\n }\n\n /**\n * @notice Copy the view to a location, return an unsafe memory reference\n * @dev Super Dangerous direct memory access.\n * This reference can be overwritten if anything else modifies memory (!!!).\n * As such it MUST be consumed IMMEDIATELY. Update the free memory pointer to ensure the copied data\n * is not overwritten. This function is private to prevent unsafe usage by callers.\n * @param memView The memory view\n * @param newLoc The new location to copy the underlying view data\n * @return The memory view over the unsafe memory with the copied underlying data\n */\n function _unsafeCopyTo(MemView memView, uint256 newLoc) private view returns (MemView) {\n uint256 len_ = memView.len();\n uint256 oldLoc = memView.loc();\n\n uint256 ptr;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Load unused memory pointer\n ptr := mload(0x40)\n }\n // Revert if we're writing in occupied memory\n if (newLoc \u003c ptr) {\n revert OccupiedMemory();\n }\n bool res;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // use the identity precompile (0x04) to copy\n res := staticcall(gas(), 0x04, oldLoc, len_, newLoc, len_)\n }\n if (!res) revert PrecompileOutOfGas();\n return _unsafeBuildUnchecked({loc_: newLoc, len_: len_});\n }\n\n /**\n * @notice Join the views in memory, return an unsafe reference to the memory.\n * @dev Super Dangerous direct memory access.\n * This reference can be overwritten if anything else modifies memory (!!!).\n * As such it MUST be consumed IMMEDIATELY. Update the free memory pointer to ensure the copied data\n * is not overwritten. This function is private to prevent unsafe usage by callers.\n * @param memViews The memory views\n * @return The conjoined view pointing to the new memory\n */\n function _unsafeJoin(MemView[] memory memViews, uint256 location) private view returns (MemView) {\n uint256 ptr;\n assembly {\n // solhint-disable-previous-line no-inline-assembly\n // Load unused memory pointer\n ptr := mload(0x40)\n }\n // Revert if we're writing in occupied memory\n if (location \u003c ptr) {\n revert OccupiedMemory();\n }\n // Copy the views to the specified location one by one, by tracking the amount of copied bytes so far\n uint256 offset = 0;\n for (uint256 i = 0; i \u003c memViews.length;) {\n MemView memView = memViews[i];\n // We can use the unchecked math here as location + sum(view.length) will never overflow uint256\n unchecked {\n _unsafeCopyTo(memView, location + offset);\n offset += memView.len();\n ++i;\n }\n }\n return _unsafeBuildUnchecked({loc_: location, len_: offset});\n }\n}\n\n// Here we define common constants to enable their easier reusing later.\n\n// ══════════════════════════════════ MERKLE ═══════════════════════════════════\n/// @dev Height of the Agent Merkle Tree\nuint256 constant AGENT_TREE_HEIGHT = 32;\n/// @dev Height of the Origin Merkle Tree\nuint256 constant ORIGIN_TREE_HEIGHT = 32;\n/// @dev Height of the Snapshot Merkle Tree. Allows up to 64 leafs, e.g. up to 32 states\nuint256 constant SNAPSHOT_TREE_HEIGHT = 6;\n// ══════════════════════════════════ STRUCTS ══════════════════════════════════\n/// @dev See Attestation.sol: (bytes32,bytes32,uint32,uint40,uint40): 32+32+4+5+5\nuint256 constant ATTESTATION_LENGTH = 78;\n/// @dev See GasData.sol: (uint16,uint16,uint16,uint16,uint16,uint16): 2+2+2+2+2+2\nuint256 constant GAS_DATA_LENGTH = 12;\n/// @dev See Receipt.sol: (uint32,uint32,bytes32,bytes32,uint8,address,address,address): 4+4+32+32+1+20+20+20\nuint256 constant RECEIPT_LENGTH = 133;\n/// @dev See State.sol: (bytes32,uint32,uint32,uint40,uint40,GasData): 32+4+4+5+5+len(GasData)\nuint256 constant STATE_LENGTH = 50 + GAS_DATA_LENGTH;\n/// @dev Maximum amount of states in a single snapshot. Each state produces two leafs in the tree\nuint256 constant SNAPSHOT_MAX_STATES = 1 \u003c\u003c (SNAPSHOT_TREE_HEIGHT - 1);\n// ══════════════════════════════════ MESSAGE ══════════════════════════════════\n/// @dev See Header.sol: (uint8,uint32,uint32,uint32,uint32): 1+4+4+4+4\nuint256 constant HEADER_LENGTH = 17;\n/// @dev See Request.sol: (uint96,uint64,uint32): 12+8+4\nuint256 constant REQUEST_LENGTH = 24;\n/// @dev See Tips.sol: (uint64,uint64,uint64,uint64): 8+8+8+8\nuint256 constant TIPS_LENGTH = 32;\n/// @dev The amount of discarded last bits when encoding tip values\nuint256 constant TIPS_GRANULARITY = 32;\n/// @dev Tip values could be only the multiples of TIPS_MULTIPLIER\nuint256 constant TIPS_MULTIPLIER = 1 \u003c\u003c TIPS_GRANULARITY;\n// ══════════════════════════════ STATEMENT SALTS ══════════════════════════════\n/// @dev Salts for signing various statements\nbytes32 constant ATTESTATION_VALID_SALT = keccak256(\"ATTESTATION_VALID_SALT\");\nbytes32 constant ATTESTATION_INVALID_SALT = keccak256(\"ATTESTATION_INVALID_SALT\");\nbytes32 constant RECEIPT_VALID_SALT = keccak256(\"RECEIPT_VALID_SALT\");\nbytes32 constant RECEIPT_INVALID_SALT = keccak256(\"RECEIPT_INVALID_SALT\");\nbytes32 constant SNAPSHOT_VALID_SALT = keccak256(\"SNAPSHOT_VALID_SALT\");\nbytes32 constant STATE_INVALID_SALT = keccak256(\"STATE_INVALID_SALT\");\n// ═════════════════════════════════ PROTOCOL ══════════════════════════════════\n/// @dev Optimistic period for new agent roots in LightManager\nuint32 constant AGENT_ROOT_OPTIMISTIC_PERIOD = 1 days;\nuint32 constant BONDING_OPTIMISTIC_PERIOD = 1 days;\n/// @dev Amount of time without fresh data from Notaries before contract owner can resolve stuck disputes manually\nuint256 constant FRESH_DATA_TIMEOUT = 4 hours;\n/// @dev Maximum bytes per message = 2 KiB (somewhat arbitrarily set to begin)\nuint256 constant MAX_CONTENT_BYTES = 2 * 2 ** 10;\n/// @dev Domain of the Synapse Chain\n// TODO: replace the placeholder with actual value (for MVP this is Optimism chainId)\nuint32 constant SYNAPSE_DOMAIN = 10;\n\n/// Receipt is a memory view over a formatted \"full receipt\" payload.\ntype Receipt is uint256;\n\nusing ReceiptLib for Receipt global;\n\n/// Receipt structure represents a Notary statement that a certain message has been executed in `ExecutionHub`.\n/// - It is possible to prove the correctness of the tips payload using the message hash, therefore tips are not\n/// included in the receipt.\n/// - Receipt is signed by a Notary and submitted to `Summit` in order to initiate the tips distribution for an\n/// executed message.\n/// - If a message execution fails the first time, the `finalExecutor` field will be set to zero address. In this\n/// case, when the message is finally executed successfully, the `finalExecutor` field will be updated. Both\n/// receipts will be considered valid.\n/// # Memory layout of Receipt fields\n///\n/// | Position | Field | Type | Bytes | Description |\n/// | ---------- | ------------- | ------- | ----- | ------------------------------------------------ |\n/// | [000..004) | origin | uint32 | 4 | Domain where message originated |\n/// | [004..008) | destination | uint32 | 4 | Domain where message was executed |\n/// | [008..040) | messageHash | bytes32 | 32 | Hash of the message |\n/// | [040..072) | snapshotRoot | bytes32 | 32 | Snapshot root used for proving the message |\n/// | [072..073) | stateIndex | uint8 | 1 | Index of state used for the snapshot proof |\n/// | [073..093) | attNotary | address | 20 | Notary who posted attestation with snapshot root |\n/// | [093..113) | firstExecutor | address | 20 | Executor who performed first valid execution |\n/// | [113..133) | finalExecutor | address | 20 | Executor who successfully executed the message |\nlibrary ReceiptLib {\n using MemViewLib for bytes;\n\n /// @dev The variables below are not supposed to be used outside of the library directly.\n uint256 private constant OFFSET_ORIGIN = 0;\n uint256 private constant OFFSET_DESTINATION = 4;\n uint256 private constant OFFSET_MESSAGE_HASH = 8;\n uint256 private constant OFFSET_SNAPSHOT_ROOT = 40;\n uint256 private constant OFFSET_STATE_INDEX = 72;\n uint256 private constant OFFSET_ATT_NOTARY = 73;\n uint256 private constant OFFSET_FIRST_EXECUTOR = 93;\n uint256 private constant OFFSET_FINAL_EXECUTOR = 113;\n\n // ═════════════════════════════════════════════════ RECEIPT ═════════════════════════════════════════════════════\n\n /**\n * @notice Returns a formatted Receipt payload with provided fields.\n * @param origin_ Domain where message originated\n * @param destination_ Domain where message was executed\n * @param messageHash_ Hash of the message\n * @param snapshotRoot_ Snapshot root used for proving the message\n * @param stateIndex_ Index of state used for the snapshot proof\n * @param attNotary_ Notary who posted attestation with snapshot root\n * @param firstExecutor_ Executor who performed first valid execution attempt\n * @param finalExecutor_ Executor who successfully executed the message\n * @return Formatted receipt\n */\n function formatReceipt(\n uint32 origin_,\n uint32 destination_,\n bytes32 messageHash_,\n bytes32 snapshotRoot_,\n uint8 stateIndex_,\n address attNotary_,\n address firstExecutor_,\n address finalExecutor_\n ) internal pure returns (bytes memory) {\n return abi.encodePacked(\n origin_, destination_, messageHash_, snapshotRoot_, stateIndex_, attNotary_, firstExecutor_, finalExecutor_\n );\n }\n\n /**\n * @notice Returns a Receipt view over the given payload.\n * @dev Will revert if the payload is not a receipt.\n */\n function castToReceipt(bytes memory payload) internal pure returns (Receipt) {\n return castToReceipt(payload.ref());\n }\n\n /**\n * @notice Casts a memory view to a Receipt view.\n * @dev Will revert if the memory view is not over a receipt.\n */\n function castToReceipt(MemView memView) internal pure returns (Receipt) {\n if (!isReceipt(memView)) revert UnformattedReceipt();\n return Receipt.wrap(MemView.unwrap(memView));\n }\n\n /// @notice Checks that a payload is a formatted Receipt.\n function isReceipt(MemView memView) internal pure returns (bool) {\n // Check payload length\n return memView.len() == RECEIPT_LENGTH;\n }\n\n /// @notice Returns the hash of an Receipt, that could be later signed by a Notary to signal\n /// that the receipt is valid.\n function hashValid(Receipt receipt) internal pure returns (bytes32) {\n // The final hash to sign is keccak(receiptSalt, keccak(receipt))\n return receipt.unwrap().keccakSalted(RECEIPT_VALID_SALT);\n }\n\n /// @notice Returns the hash of a Receipt, that could be later signed by a Guard to signal\n /// that the receipt is invalid.\n function hashInvalid(Receipt receipt) internal pure returns (bytes32) {\n // The final hash to sign is keccak(receiptBodyInvalidSalt, keccak(receipt))\n return receipt.unwrap().keccakSalted(RECEIPT_INVALID_SALT);\n }\n\n /// @notice Convenience shortcut for unwrapping a view.\n function unwrap(Receipt receipt) internal pure returns (MemView) {\n return MemView.wrap(Receipt.unwrap(receipt));\n }\n\n /// @notice Compares two Receipt structures.\n function equals(Receipt a, Receipt b) internal pure returns (bool) {\n // Length of a Receipt payload is fixed, so we just need to compare the hashes\n return a.unwrap().keccak() == b.unwrap().keccak();\n }\n\n // ═════════════════════════════════════════════ RECEIPT SLICING ═════════════════════════════════════════════════\n\n /// @notice Returns receipt's origin field\n function origin(Receipt receipt) internal pure returns (uint32) {\n return uint32(receipt.unwrap().indexUint({index_: OFFSET_ORIGIN, bytes_: 4}));\n }\n\n /// @notice Returns receipt's destination field\n function destination(Receipt receipt) internal pure returns (uint32) {\n return uint32(receipt.unwrap().indexUint({index_: OFFSET_DESTINATION, bytes_: 4}));\n }\n\n /// @notice Returns receipt's \"message hash\" field\n function messageHash(Receipt receipt) internal pure returns (bytes32) {\n return receipt.unwrap().index({index_: OFFSET_MESSAGE_HASH, bytes_: 32});\n }\n\n /// @notice Returns receipt's \"snapshot root\" field\n function snapshotRoot(Receipt receipt) internal pure returns (bytes32) {\n return receipt.unwrap().index({index_: OFFSET_SNAPSHOT_ROOT, bytes_: 32});\n }\n\n /// @notice Returns receipt's \"state index\" field\n function stateIndex(Receipt receipt) internal pure returns (uint8) {\n return uint8(receipt.unwrap().indexUint({index_: OFFSET_STATE_INDEX, bytes_: 1}));\n }\n\n /// @notice Returns receipt's \"attestation notary\" field\n function attNotary(Receipt receipt) internal pure returns (address) {\n return receipt.unwrap().indexAddress({index_: OFFSET_ATT_NOTARY});\n }\n\n /// @notice Returns receipt's \"first executor\" field\n function firstExecutor(Receipt receipt) internal pure returns (address) {\n return receipt.unwrap().indexAddress({index_: OFFSET_FIRST_EXECUTOR});\n }\n\n /// @notice Returns receipt's \"final executor\" field\n function finalExecutor(Receipt receipt) internal pure returns (address) {\n return receipt.unwrap().indexAddress({index_: OFFSET_FINAL_EXECUTOR});\n }\n}\n\n// solhint-disable ordering\ncontract ReceiptHarness {\n using ReceiptLib for bytes;\n using ReceiptLib for MemView;\n using MemViewLib for bytes;\n\n // Note: we don't add an empty test() function here, as it currently leads\n // to zero coverage on the corresponding library.\n\n // ══════════════════════════════════════════════════ GETTERS ══════════════════════════════════════════════════════\n\n function castToReceipt(bytes memory payload) public view returns (bytes memory) {\n // Walkaround to get the forge coverage working on libraries, see\n // https://github.com/foundry-rs/foundry/pull/3128#issuecomment-1241245086\n Receipt receipt = ReceiptLib.castToReceipt(payload);\n return receipt.unwrap().clone();\n }\n\n /// @notice Returns receipt's origin field\n function origin(bytes memory payload) public pure returns (uint32) {\n return payload.castToReceipt().origin();\n }\n\n /// @notice Returns receipt's destination field\n function destination(bytes memory payload) public pure returns (uint32) {\n return payload.castToReceipt().destination();\n }\n\n /// @notice Returns receipt's \"message hash\" field\n function messageHash(bytes memory payload) public pure returns (bytes32) {\n return payload.castToReceipt().messageHash();\n }\n\n /// @notice Returns receipt's \"snapshot root\" field\n function snapshotRoot(bytes memory payload) public pure returns (bytes32) {\n return payload.castToReceipt().snapshotRoot();\n }\n\n /// @notice Returns receipt's \"state index\" field\n function stateIndex(bytes memory payload) public pure returns (uint8) {\n return payload.castToReceipt().stateIndex();\n }\n\n /// @notice Returns receipt's \"attestation notary\" field\n function attNotary(bytes memory payload) public pure returns (address) {\n return payload.castToReceipt().attNotary();\n }\n\n /// @notice Returns receipt's \"first executor\" field\n function firstExecutor(bytes memory payload) public pure returns (address) {\n return payload.castToReceipt().firstExecutor();\n }\n\n /// @notice Returns receipt's \"final executor\" field\n function finalExecutor(bytes memory payload) public pure returns (address) {\n return payload.castToReceipt().finalExecutor();\n }\n\n function equals(bytes memory a, bytes memory b) public pure returns (bool) {\n return a.ref().castToReceipt().equals(b.ref().castToReceipt());\n }\n\n function isReceipt(bytes memory payload) public pure returns (bool) {\n return payload.ref().isReceipt();\n }\n\n function hashValid(bytes memory payload) public pure returns (bytes32) {\n return payload.ref().castToReceipt().hashValid();\n }\n\n function hashInvalid(bytes memory payload) public pure returns (bytes32) {\n return payload.ref().castToReceipt().hashInvalid();\n }\n\n // ════════════════════════════════════════════════ FORMATTERS ═════════════════════════════════════════════════════\n\n function formatReceipt(\n uint32 origin_,\n uint32 destination_,\n bytes32 messageHash_,\n bytes32 snapshotRoot_,\n uint8 stateIndex_,\n address attNotary_,\n address firstExecutor_,\n address finalExecutor_\n ) public pure returns (bytes memory) {\n return ReceiptLib.formatReceipt(\n origin_, destination_, messageHash_, snapshotRoot_, stateIndex_, attNotary_, firstExecutor_, finalExecutor_\n );\n }\n}\n","language":"Solidity","languageVersion":"0.8.17","compilerVersion":"0.8.17","compilerOptions":"--combined-json bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc,metadata,hashes --optimize --optimize-runs 10000 --allow-paths ., ./, ../","srcMap":"30567:6168:0:-:0;;;;;;;;;;;;;;;-1:-1:-1;;;30567:6168:0;;;;;;;;;;;;;;;;;","srcMapRuntime":"30567:6168:0:-:0;;;;;;;;","abiDefinition":[],"userDoc":{"kind":"user","methods":{},"notice":"Receipt structure represents a Notary statement that a certain message has been executed in `ExecutionHub`. - It is possible to prove the correctness of the tips payload using the message hash, therefore tips are not included in the receipt. - Receipt is signed by a Notary and submitted to `Summit` in order to initiate the tips distribution for an executed message. - If a message execution fails the first time, the `finalExecutor` field will be set to zero address. In this case, when the message is finally executed successfully, the `finalExecutor` field will be updated. Both receipts will be considered valid. # Memory layout of Receipt fields | Position | Field | Type | Bytes | Description | | ---------- | ------------- | ------- | ----- | ------------------------------------------------ | | [000..004) | origin | uint32 | 4 | Domain where message originated | | [004..008) | destination | uint32 | 4 | Domain where message was executed | | [008..040) | messageHash | bytes32 | 32 | Hash of the message | | [040..072) | snapshotRoot | bytes32 | 32 | Snapshot root used for proving the message | | [072..073) | stateIndex | uint8 | 1 | Index of state used for the snapshot proof | | [073..093) | attNotary | address | 20 | Notary who posted attestation with snapshot root | | [093..113) | firstExecutor | address | 20 | Executor who performed first valid execution | | [113..133) | finalExecutor | address | 20 | Executor who successfully executed the message |","version":1},"developerDoc":{"kind":"dev","methods":{},"stateVariables":{"OFFSET_ORIGIN":{"details":"The variables below are not supposed to be used outside of the library directly."}},"version":1},"metadata":"{\"compiler\":{\"version\":\"0.8.17+commit.8df45f5f\"},\"language\":\"Solidity\",\"output\":{\"abi\":[],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"stateVariables\":{\"OFFSET_ORIGIN\":{\"details\":\"The variables below are not supposed to be used outside of the library directly.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"Receipt structure represents a Notary statement that a certain message has been executed in `ExecutionHub`. - It is possible to prove the correctness of the tips payload using the message hash, therefore tips are not included in the receipt. - Receipt is signed by a Notary and submitted to `Summit` in order to initiate the tips distribution for an executed message. - If a message execution fails the first time, the `finalExecutor` field will be set to zero address. In this case, when the message is finally executed successfully, the `finalExecutor` field will be updated. Both receipts will be considered valid. # Memory layout of Receipt fields | Position | Field | Type | Bytes | Description | | ---------- | ------------- | ------- | ----- | ------------------------------------------------ | | [000..004) | origin | uint32 | 4 | Domain where message originated | | [004..008) | destination | uint32 | 4 | Domain where message was executed | | [008..040) | messageHash | bytes32 | 32 | Hash of the message | | [040..072) | snapshotRoot | bytes32 | 32 | Snapshot root used for proving the message | | [072..073) | stateIndex | uint8 | 1 | Index of state used for the snapshot proof | | [073..093) | attNotary | address | 20 | Notary who posted attestation with snapshot root | | [093..113) | firstExecutor | address | 20 | Executor who performed first valid execution | | [113..133) | finalExecutor | address | 20 | Executor who successfully executed the message |\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"solidity/ReceiptHarness.t.sol\":\"ReceiptLib\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"solidity/ReceiptHarness.t.sol\":{\"keccak256\":\"0xf16784d67df259efee36f809c5a0756a6febd1ad6f07aa4b77fa52127ec5f9fa\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a097afb3038e4aaafadb147a3a36cf3bb4751c0ff22351dfe3f2c90e65194922\",\"dweb:/ipfs/QmTkBucx2oKmEGDMzHirHmRgzpCsp62KZ7WZYoRHPXjFuA\"]}},\"version\":1}"},"hashes":{}}} \ No newline at end of file diff --git a/agents/contracts/test/receiptharness/receiptharness.metadata.go b/agents/contracts/test/receiptharness/receiptharness.metadata.go new file mode 100644 index 0000000000..3284c5eb36 --- /dev/null +++ b/agents/contracts/test/receiptharness/receiptharness.metadata.go @@ -0,0 +1,25 @@ +// Code generated by synapse abigen DO NOT EDIT. +package receiptharness + +import ( + _ "embed" + "encoding/json" + "github.com/ethereum/go-ethereum/common/compiler" +) + +// rawContracts are the json we use to dervive the processed contracts +// +//go:embed receiptharness.contractinfo.json +var rawContracts []byte + +// Contracts are unmarshalled on start +var Contracts map[string]*compiler.Contract + +func init() { + // load contract metadata + var err error + err = json.Unmarshal(rawContracts, &Contracts) + if err != nil { + panic(err) + } +} diff --git a/agents/domains/domain.go b/agents/domains/domain.go index a42624bba8..8bde0b509b 100644 --- a/agents/domains/domain.go +++ b/agents/domains/domain.go @@ -3,9 +3,10 @@ package domains import ( "context" "errors" + "math/big" + "github.com/ethereum/go-ethereum/accounts/abi/bind" ethTypes "github.com/ethereum/go-ethereum/core/types" - "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" @@ -45,6 +46,8 @@ type DomainClient interface { // OriginContract represents the origin contract on a particular chain. type OriginContract interface { + // IsValidState checks if the given state is valid on its origin. + IsValidState(ctx context.Context, statePayload []byte) (isValid bool, err error) // SuggestLatestState gets the latest state on the origin SuggestLatestState(ctx context.Context) (types.State, error) // SuggestState gets the state on the origin with the given nonce if it exists @@ -61,22 +64,51 @@ type SummitContract interface { GetLatestNotaryAttestation(ctx context.Context, notarySigner signer.Signer) (types.NotaryAttestation, error) // WatchAttestationSaved looks for attesation saved events WatchAttestationSaved(ctx context.Context, sink chan<- *summit.SummitAttestationSaved) (event.Subscription, error) + // IsValidAttestation checks if the given attestation is valid on the summit + IsValidAttestation(ctx context.Context, attestation []byte) (bool, error) + // GetNotarySnapshot gets the snapshot payload corresponding to a given attestation + GetNotarySnapshot(ctx context.Context, attPayload []byte) (types.Snapshot, error) + // GetAttestation gets an attestation for a given attestationNonce. + GetAttestation(ctx context.Context, attNonce uint32) (types.NotaryAttestation, error) } // InboxContract contains the interface for the inbox. type InboxContract interface { + // SubmitStateReportWithSnapshot reports to the inbox that a state within a snapshot is invalid. + SubmitStateReportWithSnapshot(transactor *bind.TransactOpts, stateIndex int64, signature signer.Signature, snapPayload []byte, snapSignature []byte) (tx *ethTypes.Transaction, err error) // SubmitSnapshot submits a snapshot to the inbox (via the Inbox). - SubmitSnapshot(transactor *bind.TransactOpts, signer signer.Signer, encodedSnapshot []byte, signature signer.Signature) (tx *ethTypes.Transaction, err error) + SubmitSnapshot(transactor *bind.TransactOpts, encodedSnapshot []byte, signature signer.Signature) (tx *ethTypes.Transaction, err error) + // VerifyAttestation verifies a snapshot on the inbox. + VerifyAttestation(transactor *bind.TransactOpts, attestation []byte, attSignature []byte) (tx *ethTypes.Transaction, err error) + // SubmitStateReportWithAttestation submits a state report corresponding to an attesation for an invalid state. + SubmitStateReportWithAttestation(transactor *bind.TransactOpts, stateIndex int64, signature signer.Signature, snapPayload, attPayload, attSignature []byte) (tx *ethTypes.Transaction, err error) + // SubmitReceipt submits a receipt to the inbox. + SubmitReceipt(transactor *bind.TransactOpts, rcptPayload []byte, rcptSignature signer.Signature, paddedTips *big.Int, headerHash [32]byte, bodyHash [32]byte) (tx *ethTypes.Transaction, err error) + // VerifyReceipt verifies a receipt on the inbox. + VerifyReceipt(transactor *bind.TransactOpts, rcptPayload []byte, rcptSignature []byte) (tx *ethTypes.Transaction, err error) + // SubmitReceiptReport submits a receipt report to the inbox. + SubmitReceiptReport(transactor *bind.TransactOpts, rcptPayload []byte, rcptSignature []byte, rrSignature []byte) (tx *ethTypes.Transaction, err error) } // BondingManagerContract contains the interface for the bonding manager. type BondingManagerContract interface { // GetAgentStatus returns the current agent status for the given agent. - GetAgentStatus(ctx context.Context, signer signer.Signer) (types.AgentStatus, error) + GetAgentStatus(ctx context.Context, address common.Address) (types.AgentStatus, error) // GetAgentRoot gets the current agent root GetAgentRoot(ctx context.Context) ([32]byte, error) // GetProof gets the proof that the agent is in the Agent Merkle Tree - GetProof(ctx context.Context, bondedAgentSigner signer.Signer) ([][32]byte, error) + GetProof(ctx context.Context, address common.Address) ([][32]byte, error) + // DisputeStatus gets the dispute status for the given agent. + DisputeStatus(ctx context.Context, address common.Address) (disputeStatus DisputeStatus, err error) + // GetDispute gets the dispute for a given dispute index. + // TODO: Add more returned values here as needed. + GetDispute(ctx context.Context, index *big.Int) (err error) + // GetDisputeStatus gets the dispute status for the given agent. + GetDisputeStatus(ctx context.Context, agent common.Address) (disputeStatus types.DisputeStatus, err error) + // CompleteSlashing completes the slashing of an agent. + CompleteSlashing(transactor *bind.TransactOpts, domain uint32, agent common.Address, proof [][32]byte) (tx *ethTypes.Transaction, err error) + // GetAgent gets an agent status and address for a given agent index. + GetAgent(ctx context.Context, index *big.Int) (types.AgentStatus, common.Address, error) } // DestinationContract contains the interface for the destination. @@ -89,10 +121,16 @@ type DestinationContract interface { GetAttestationNonce(ctx context.Context, snapRoot [32]byte) (uint32, error) // MessageStatus takes a message and returns whether it has been executed or not. 0: None, 1: Failed, 2: Success. MessageStatus(ctx context.Context, message types.Message) (uint8, error) + // IsValidReceipt checks if the given receipt is valid on the destination + IsValidReceipt(ctx context.Context, rcptPayload []byte) (bool, error) + // PassAgentRoot passes the agent root to the destination. + PassAgentRoot(transactor *bind.TransactOpts) (tx *ethTypes.Transaction, err error) } // LightInboxContract contains the interface for the light inbox. type LightInboxContract interface { + // SubmitStateReportWithSnapshot reports to the inbox that a state within a snapshot is invalid. + SubmitStateReportWithSnapshot(transactor *bind.TransactOpts, stateIndex int64, signature signer.Signature, snapPayload []byte, snapSignature []byte) (tx *ethTypes.Transaction, err error) // SubmitAttestation submits an attestation to the destination chain (via the light inbox contract) SubmitAttestation( transactor *bind.TransactOpts, @@ -101,21 +139,33 @@ type LightInboxContract interface { agentRoot [32]byte, snapGas []*big.Int, ) (tx *ethTypes.Transaction, err error) + // SubmitStateReportWithAttestation submits a state report corresponding to an attesation for an invalid state. + SubmitStateReportWithAttestation(transactor *bind.TransactOpts, stateIndex int64, signature signer.Signature, snapPayload, attPayload, attSignature []byte) (tx *ethTypes.Transaction, err error) + // VerifyStateWithSnapshot verifies a state within a snapshot. + VerifyStateWithSnapshot(transactor *bind.TransactOpts, stateIndex int64, snapPayload []byte, snapSignature []byte) (tx *ethTypes.Transaction, err error) + // SubmitAttestationReport submits an attestation report to the inbox (via the light inbox contract) + SubmitAttestationReport(transactor *bind.TransactOpts, attestation, arSignature, attSignature []byte) (tx *ethTypes.Transaction, err error) + // VerifyStateWithAttestation verifies a state with attestation. + VerifyStateWithAttestation(transactor *bind.TransactOpts, stateIndex int64, snapPayload []byte, attPayload []byte, attSignature []byte) (tx *ethTypes.Transaction, err error) + // VerifyReceipt verifies a receipt on the inbox. + VerifyReceipt(transactor *bind.TransactOpts, rcptPayload []byte, rcptSignature []byte) (tx *ethTypes.Transaction, err error) } // LightManagerContract contains the interface for the light manager. type LightManagerContract interface { // GetAgentStatus returns the current agent status for the given agent. - GetAgentStatus(ctx context.Context, signer signer.Signer) (types.AgentStatus, error) + GetAgentStatus(ctx context.Context, address common.Address) (types.AgentStatus, error) // GetAgentRoot gets the current agent root GetAgentRoot(ctx context.Context) ([32]byte, error) // UpdateAgentStatus updates the agent status on the remote chain. UpdateAgentStatus( - ctx context.Context, - unbondedSigner signer.Signer, - bondedSigner signer.Signer, + transactor *bind.TransactOpts, + agentAddress common.Address, agentStatus types.AgentStatus, - agentProof [][32]byte) error + agentProof [][32]byte) (*ethTypes.Transaction, error) + // GetDispute gets the dispute for a given dispute index. + // TODO: Add more returned values here as needed. + GetDispute(ctx context.Context, index *big.Int) (err error) } // TestClientContract contains the interface for the test client. diff --git a/agents/domains/evm/bondingmanager.go b/agents/domains/evm/bondingmanager.go index a99135d017..e50b0783b3 100644 --- a/agents/domains/evm/bondingmanager.go +++ b/agents/domains/evm/bondingmanager.go @@ -6,14 +6,16 @@ package evm import ( "context" "fmt" + "math/big" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/synapsecns/sanguine/agents/contracts/bondingmanager" "github.com/synapsecns/sanguine/agents/domains" "github.com/synapsecns/sanguine/agents/types" "github.com/synapsecns/sanguine/ethergo/chain" "github.com/synapsecns/sanguine/ethergo/signer/nonce" - "github.com/synapsecns/sanguine/ethergo/signer/signer" ) // NewBondingManagerContract returns a bound bonding manager contract. @@ -44,13 +46,13 @@ type bondingManagerContract struct { } //nolint:dupl -func (a bondingManagerContract) GetAgentStatus(ctx context.Context, bondedAgentSigner signer.Signer) (types.AgentStatus, error) { - rawStatus, err := a.contract.AgentStatus(&bind.CallOpts{Context: ctx}, bondedAgentSigner.Address()) +func (a bondingManagerContract) GetAgentStatus(ctx context.Context, address common.Address) (types.AgentStatus, error) { + rawStatus, err := a.contract.AgentStatus(&bind.CallOpts{Context: ctx}, address) if err != nil { return nil, fmt.Errorf("could not retrieve agent status: %w", err) } - agentStatus := types.NewAgentStatus(rawStatus.Flag, rawStatus.Domain, rawStatus.Index) + agentStatus := types.NewAgentStatus(types.AgentFlagType(rawStatus.Flag), rawStatus.Domain, rawStatus.Index) return agentStatus, nil } @@ -66,11 +68,64 @@ func (a bondingManagerContract) GetAgentRoot(ctx context.Context) ([32]byte, err } //nolint:dupl -func (a bondingManagerContract) GetProof(ctx context.Context, bondedAgentSigner signer.Signer) ([][32]byte, error) { - proof, err := a.contract.GetProof(&bind.CallOpts{Context: ctx}, bondedAgentSigner.Address()) +func (a bondingManagerContract) GetProof(ctx context.Context, address common.Address) ([][32]byte, error) { + proof, err := a.contract.GetProof(&bind.CallOpts{Context: ctx}, address) if err != nil { return nil, fmt.Errorf("could not retrieve agent proof: %w", err) } return proof, nil } + +func (a bondingManagerContract) DisputeStatus(ctx context.Context, address common.Address) (disputeStatus domains.DisputeStatus, err error) { + rawDispute, err := a.contract.DisputeStatus(&bind.CallOpts{Context: ctx}, address) + if err != nil { + return domains.DisputeStatus{}, fmt.Errorf("could not retrieve dispute status: %w", err) + } + + return domains.DisputeStatus{ + DisputeFlag: rawDispute.Flag, + Rival: rawDispute.Rival, + FraudProver: rawDispute.FraudProver, + DisputePtr: rawDispute.DisputePtr, + }, nil +} + +func (a bondingManagerContract) GetDispute(ctx context.Context, index *big.Int) (err error) { + _, err = a.contract.GetDispute(&bind.CallOpts{Context: ctx}, index) + if err != nil { + return fmt.Errorf("could not retrieve dispute: %w", err) + } + + return nil +} + +func (a bondingManagerContract) CompleteSlashing(transactor *bind.TransactOpts, domain uint32, agent common.Address, proof [][32]byte) (tx *ethTypes.Transaction, err error) { + tx, err = a.contract.CompleteSlashing(transactor, domain, agent, proof) + if err != nil { + return nil, fmt.Errorf("could not submit state report: %w", err) + } + + return tx, nil +} + +func (a bondingManagerContract) GetDisputeStatus(ctx context.Context, agent common.Address) (disputeStatus types.DisputeStatus, err error) { + rawStatus, err := a.contract.DisputeStatus(&bind.CallOpts{Context: ctx}, agent) + if err != nil { + return nil, fmt.Errorf("could not retrieve dispute status: %w", err) + } + + disputeStatus = types.NewDisputeStatus(types.DisputeFlagType(rawStatus.Flag), rawStatus.Rival, rawStatus.FraudProver, rawStatus.DisputePtr) + return disputeStatus, nil +} + +func (a bondingManagerContract) GetAgent(ctx context.Context, index *big.Int) (types.AgentStatus, common.Address, error) { + rawStatus, err := a.contract.GetAgent(&bind.CallOpts{Context: ctx}, index) + if err != nil { + return nil, common.Address{}, fmt.Errorf("could not retrieve agent status: %w", err) + } + + agentStatus := types.NewAgentStatus(types.AgentFlagType(rawStatus.Status.Flag), rawStatus.Status.Domain, rawStatus.Status.Index) + + return agentStatus, rawStatus.Agent, nil +} diff --git a/agents/domains/evm/destination.go b/agents/domains/evm/destination.go index 29581f015c..24f635d4c0 100644 --- a/agents/domains/evm/destination.go +++ b/agents/domains/evm/destination.go @@ -92,3 +92,16 @@ func (a destinationContract) MessageStatus(ctx context.Context, message types.Me return status, nil } + +//nolint:wrapcheck +func (a destinationContract) IsValidReceipt(ctx context.Context, rcptPayload []byte) (bool, error) { + return a.contract.IsValidReceipt(&bind.CallOpts{Context: ctx}, rcptPayload) +} + +func (a destinationContract) PassAgentRoot(transactor *bind.TransactOpts) (*ethTypes.Transaction, error) { + tx, err := a.contract.PassAgentRoot(transactor) + if err != nil { + return nil, fmt.Errorf("could not pass agent root: %w", err) + } + return tx, nil +} diff --git a/agents/domains/evm/inbox.go b/agents/domains/evm/inbox.go index 6ddb049106..f7afd534fb 100644 --- a/agents/domains/evm/inbox.go +++ b/agents/domains/evm/inbox.go @@ -3,6 +3,8 @@ package evm import ( "context" "fmt" + "math/big" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" ethTypes "github.com/ethereum/go-ethereum/core/types" @@ -12,7 +14,6 @@ import ( "github.com/synapsecns/sanguine/ethergo/chain" "github.com/synapsecns/sanguine/ethergo/signer/nonce" "github.com/synapsecns/sanguine/ethergo/signer/signer" - "strings" ) // NewInboxContract returns a bound inbox contract. @@ -33,6 +34,7 @@ func NewInboxContract(ctx context.Context, client chain.Chain, inboxAddress comm } type inboxContract struct { + lightInboxContract // contract contains the conract handle contract *inbox.InboxRef // client contains the evm client @@ -42,7 +44,22 @@ type inboxContract struct { nonceManager nonce.Manager } -func (a inboxContract) SubmitSnapshot(transactor *bind.TransactOpts, signer signer.Signer, encodedSnapshot []byte, signature signer.Signature) (tx *ethTypes.Transaction, err error) { +//nolint:dupl +func (a inboxContract) SubmitStateReportWithSnapshot(transactor *bind.TransactOpts, stateIndex int64, signature signer.Signature, snapPayload []byte, snapSignature []byte) (tx *ethTypes.Transaction, err error) { + rawSig, err := types.EncodeSignature(signature) + if err != nil { + return nil, fmt.Errorf("could not encode signature: %w", err) + } + + tx, err = a.contract.SubmitStateReportWithSnapshot(transactor, big.NewInt(stateIndex), rawSig, snapPayload, snapSignature) + if err != nil { + return nil, fmt.Errorf("could not submit state report: %w", err) + } + + return tx, nil +} + +func (a inboxContract) SubmitSnapshot(transactor *bind.TransactOpts, encodedSnapshot []byte, signature signer.Signature) (tx *ethTypes.Transaction, err error) { rawSig, err := types.EncodeSignature(signature) if err != nil { return nil, fmt.Errorf("could not encode signature: %w", err) @@ -50,11 +67,63 @@ func (a inboxContract) SubmitSnapshot(transactor *bind.TransactOpts, signer sign tx, err = a.contract.SubmitSnapshot(transactor, encodedSnapshot, rawSig) if err != nil { - if strings.Contains(err.Error(), "nonce too low") { - a.nonceManager.ClearNonce(signer.Address()) - } return nil, fmt.Errorf("could not submit sanpshot: %w", err) } return tx, nil } + +func (a inboxContract) VerifyAttestation(transactor *bind.TransactOpts, attestation []byte, attSignature []byte) (tx *ethTypes.Transaction, err error) { + tx, err = a.contract.VerifyAttestation(transactor, attestation, attSignature) + if err != nil { + return nil, fmt.Errorf("could not submit attestation: %w", err) + } + + return tx, nil +} + +func (a inboxContract) SubmitStateReportWithAttestation(transactor *bind.TransactOpts, stateIndex int64, signature signer.Signature, snapPayload, attPayload, attSignature []byte) (tx *ethTypes.Transaction, err error) { + rawSig, err := types.EncodeSignature(signature) + if err != nil { + return nil, fmt.Errorf("could not encode signature: %w", err) + } + + tx, err = a.contract.SubmitStateReportWithAttestation(transactor, big.NewInt(stateIndex), rawSig, snapPayload, attPayload, attSignature) + if err != nil { + return nil, fmt.Errorf("could not submit state report: %w", err) + } + + return tx, nil +} + +func (a inboxContract) SubmitReceipt(transactor *bind.TransactOpts, rcptPayload []byte, rcptSignature signer.Signature, paddedTips *big.Int, headerHash [32]byte, bodyHash [32]byte) (tx *ethTypes.Transaction, err error) { + rawSig, err := types.EncodeSignature(rcptSignature) + if err != nil { + return nil, fmt.Errorf("could not encode signature: %w", err) + } + + tx, err = a.contract.SubmitReceipt(transactor, rcptPayload, rawSig, paddedTips, headerHash, bodyHash) + if err != nil { + return nil, fmt.Errorf("could not submit state report: %w", err) + } + + return tx, nil +} + +func (a inboxContract) VerifyReceipt(transactor *bind.TransactOpts, rcptPayload []byte, rcptSignature []byte) (tx *ethTypes.Transaction, err error) { + tx, err = a.contract.VerifyReceipt(transactor, rcptPayload, rcptSignature) + if err != nil { + return nil, fmt.Errorf("could not submit state report: %w", err) + } + + return tx, nil +} + +func (a inboxContract) SubmitReceiptReport(transactor *bind.TransactOpts, rcptPayload []byte, rcptSignature []byte, rrSignature []byte) (tx *ethTypes.Transaction, err error) { + tx, err = a.contract.SubmitReceiptReport(transactor, rcptPayload, rcptSignature, rrSignature) + if err != nil { + return nil, fmt.Errorf("could not submit receipt report: %w", err) + } + + return tx, nil +} diff --git a/agents/domains/evm/lightinbox.go b/agents/domains/evm/lightinbox.go index cc16a82d8d..371fb4ad2f 100644 --- a/agents/domains/evm/lightinbox.go +++ b/agents/domains/evm/lightinbox.go @@ -3,6 +3,8 @@ package evm import ( "context" "fmt" + "math/big" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" ethTypes "github.com/ethereum/go-ethereum/core/types" @@ -12,7 +14,6 @@ import ( "github.com/synapsecns/sanguine/ethergo/chain" "github.com/synapsecns/sanguine/ethergo/signer/nonce" "github.com/synapsecns/sanguine/ethergo/signer/signer" - "math/big" ) // NewLightInboxContract returns a bound light inbox contract. @@ -61,3 +62,70 @@ func (a lightInboxContract) SubmitAttestation( return tx, nil } + +//nolint:dupl +func (a lightInboxContract) SubmitStateReportWithSnapshot(transactor *bind.TransactOpts, stateIndex int64, signature signer.Signature, snapPayload []byte, snapSignature []byte) (tx *ethTypes.Transaction, err error) { + rawSig, err := types.EncodeSignature(signature) + if err != nil { + return nil, fmt.Errorf("could not encode signature: %w", err) + } + + // TODO: Is there a way to get a return value from a contractTransactor call? + tx, err = a.contract.SubmitStateReportWithSnapshot(transactor, big.NewInt(stateIndex), rawSig, snapPayload, snapSignature) + if err != nil { + return nil, fmt.Errorf("could not submit state report: %w", err) + } + + return tx, nil +} + +func (a lightInboxContract) VerifyStateWithSnapshot(transactor *bind.TransactOpts, stateIndex int64, snapPayload []byte, snapSignature []byte) (tx *ethTypes.Transaction, err error) { + tx, err = a.contract.VerifyStateWithSnapshot(transactor, big.NewInt(stateIndex), snapPayload, snapSignature) + if err != nil { + return nil, fmt.Errorf("could not submit state report: %w", err) + } + + return tx, nil +} + +func (a lightInboxContract) SubmitAttestationReport(transactor *bind.TransactOpts, attestation, arSignature, attSignature []byte) (tx *ethTypes.Transaction, err error) { + tx, err = a.contract.SubmitAttestationReport(transactor, attestation, arSignature, attSignature) + if err != nil { + return nil, fmt.Errorf("could not submit state report: %w", err) + } + + return tx, nil +} + +func (a lightInboxContract) VerifyStateWithAttestation(transactor *bind.TransactOpts, stateIndex int64, snapPayload []byte, attPayload []byte, attSignature []byte) (tx *ethTypes.Transaction, err error) { + tx, err = a.contract.VerifyStateWithAttestation(transactor, big.NewInt(stateIndex), snapPayload, attPayload, attSignature) + if err != nil { + return nil, fmt.Errorf("could not submit state report: %w", err) + } + + return tx, nil +} + +func (a lightInboxContract) VerifyReceipt(transactor *bind.TransactOpts, rcptPayload []byte, rcptSignature []byte) (tx *ethTypes.Transaction, err error) { + tx, err = a.contract.VerifyReceipt(transactor, rcptPayload, rcptSignature) + if err != nil { + return nil, fmt.Errorf("could not submit state report: %w", err) + } + + return tx, nil +} + +//nolint:dupl +func (a lightInboxContract) SubmitStateReportWithAttestation(transactor *bind.TransactOpts, stateIndex int64, signature signer.Signature, snapPayload, attPayload, attSignature []byte) (tx *ethTypes.Transaction, err error) { + rawSig, err := types.EncodeSignature(signature) + if err != nil { + return nil, fmt.Errorf("could not encode signature: %w", err) + } + + tx, err = a.contract.SubmitStateReportWithAttestation(transactor, big.NewInt(stateIndex), rawSig, snapPayload, attPayload, attSignature) + if err != nil { + return nil, fmt.Errorf("could not submit state report: %w", err) + } + + return tx, nil +} diff --git a/agents/domains/evm/lightmanager.go b/agents/domains/evm/lightmanager.go index 4cb4451021..9c5672784e 100644 --- a/agents/domains/evm/lightmanager.go +++ b/agents/domains/evm/lightmanager.go @@ -6,14 +6,16 @@ package evm import ( "context" "fmt" + "math/big" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/synapsecns/sanguine/agents/contracts/lightmanager" "github.com/synapsecns/sanguine/agents/domains" "github.com/synapsecns/sanguine/agents/types" "github.com/synapsecns/sanguine/ethergo/chain" "github.com/synapsecns/sanguine/ethergo/signer/nonce" - "github.com/synapsecns/sanguine/ethergo/signer/signer" ) // NewLightManagerContract returns a bound light manager contract. @@ -44,30 +46,13 @@ type lightManagerContract struct { } //nolint:dupl -func (a lightManagerContract) transactOptsSetup(ctx context.Context, signer signer.Signer) (*bind.TransactOpts, error) { - transactor, err := signer.GetTransactor(ctx, a.client.GetBigChainID()) - if err != nil { - return nil, fmt.Errorf("could not sign tx: %w", err) - } - - transactOpts, err := a.nonceManager.NewKeyedTransactor(transactor) - if err != nil { - return nil, fmt.Errorf("could not create tx: %w", err) - } - - transactOpts.Context = ctx - - return transactOpts, nil -} - -//nolint:dupl -func (a lightManagerContract) GetAgentStatus(ctx context.Context, bondedAgentSigner signer.Signer) (types.AgentStatus, error) { - rawStatus, err := a.contract.AgentStatus(&bind.CallOpts{Context: ctx}, bondedAgentSigner.Address()) +func (a lightManagerContract) GetAgentStatus(ctx context.Context, address common.Address) (types.AgentStatus, error) { + rawStatus, err := a.contract.AgentStatus(&bind.CallOpts{Context: ctx}, address) if err != nil { return nil, fmt.Errorf("could not retrieve agent status: %w", err) } - agentStatus := types.NewAgentStatus(rawStatus.Flag, rawStatus.Domain, rawStatus.Index) + agentStatus := types.NewAgentStatus(types.AgentFlagType(rawStatus.Flag), rawStatus.Domain, rawStatus.Index) return agentStatus, nil } @@ -83,24 +68,27 @@ func (a lightManagerContract) GetAgentRoot(ctx context.Context) ([32]byte, error } func (a lightManagerContract) UpdateAgentStatus( - ctx context.Context, - unbondedSigner signer.Signer, - bondedSigner signer.Signer, + transactor *bind.TransactOpts, + agentAddress common.Address, agentStatus types.AgentStatus, - agentProof [][32]byte) error { - transactOpts, err := a.transactOptsSetup(ctx, unbondedSigner) - if err != nil { - return fmt.Errorf("could not setup transact opts: %w", err) - } - + agentProof [][32]byte) (*ethTypes.Transaction, error) { lightManagerAgentStatus := lightmanager.AgentStatus{ - Flag: agentStatus.Flag(), + Flag: uint8(agentStatus.Flag()), Domain: agentStatus.Domain(), Index: agentStatus.Index(), } - _, err = a.contract.UpdateAgentStatus(transactOpts, bondedSigner.Address(), lightManagerAgentStatus, agentProof) + tx, err := a.contract.UpdateAgentStatus(transactor, agentAddress, lightManagerAgentStatus, agentProof) + if err != nil { + return nil, fmt.Errorf("could not update agent status: %w", err) + } + + return tx, nil +} + +func (a lightManagerContract) GetDispute(ctx context.Context, index *big.Int) (err error) { + _, err = a.contract.GetDispute(&bind.CallOpts{Context: ctx}, index) if err != nil { - return fmt.Errorf("could not submit attestation: %w", err) + return fmt.Errorf("could not retrieve dispute: %w", err) } return nil diff --git a/agents/domains/evm/origin.go b/agents/domains/evm/origin.go index 5b27ea3a20..8d81e386c1 100644 --- a/agents/domains/evm/origin.go +++ b/agents/domains/evm/origin.go @@ -3,6 +3,7 @@ package evm import ( "context" "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/synapsecns/sanguine/agents/contracts/origin" @@ -34,7 +35,7 @@ func NewOriginContract(ctx context.Context, client chain.Chain, originAddress co // domains.OriginContract. type originContract struct { // contract contains the contract handle - contract origin.IOrigin + contract *origin.OriginRef // client is the client //nolint: staticcheck client chain.Chain @@ -42,6 +43,19 @@ type originContract struct { nonceManager nonce.Manager } +func (o originContract) GetContractRef() *origin.OriginRef { + return o.contract +} + +func (o originContract) IsValidState(ctx context.Context, statePayload []byte) (isValid bool, err error) { + isValid, err = o.contract.IsValidState(&bind.CallOpts{Context: ctx}, statePayload) + if err != nil { + return false, fmt.Errorf("could not check if state is valid: %w", err) + } + + return isValid, nil +} + func (o originContract) SuggestLatestState(ctx context.Context) (types.State, error) { suggestedStateRaw, err := o.contract.SuggestLatestState(&bind.CallOpts{Context: ctx}) if err != nil { diff --git a/agents/domains/evm/summit.go b/agents/domains/evm/summit.go index f6eebc0cb1..1536a8acb3 100644 --- a/agents/domains/evm/summit.go +++ b/agents/domains/evm/summit.go @@ -3,6 +3,7 @@ package evm import ( "context" "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" @@ -95,3 +96,41 @@ func (a summitContract) WatchAttestationSaved(ctx context.Context, sink chan<- * return sub, nil } + +//nolint:wrapcheck +func (a summitContract) IsValidAttestation(ctx context.Context, attestation []byte) (bool, error) { + return a.contract.IsValidAttestation(&bind.CallOpts{Context: ctx}, attestation) +} + +func (a summitContract) GetNotarySnapshot(ctx context.Context, attPayload []byte) (types.Snapshot, error) { + rawSnapshot, err := a.contract.GetNotarySnapshot(&bind.CallOpts{Context: ctx}, attPayload) + if err != nil { + return nil, fmt.Errorf("could not retrieve notary snapshot: %w", err) + } + + snapshot, err := types.DecodeSnapshot(rawSnapshot.SnapPayload) + if err != nil { + return nil, fmt.Errorf("could not decode snapshot: %w", err) + } + + return snapshot, nil +} + +func (a summitContract) GetAttestation(ctx context.Context, attNonce uint32) (types.NotaryAttestation, error) { + rawAttestation, err := a.contract.GetAttestation(&bind.CallOpts{Context: ctx}, attNonce) + if err != nil { + return nil, fmt.Errorf("could not retrieve attestation: %w", err) + } + + if len(rawAttestation.AttPayload) == 0 { + //nolint:nil,nil + return nil, nil + } + + attestation, err := types.NewNotaryAttestation(rawAttestation.AttPayload, rawAttestation.AgentRoot, rawAttestation.SnapGas) + if err != nil { + return nil, fmt.Errorf("could not decode attestation: %w", err) + } + + return attestation, nil +} diff --git a/agents/domains/types.go b/agents/domains/types.go new file mode 100644 index 0000000000..170888f674 --- /dev/null +++ b/agents/domains/types.go @@ -0,0 +1,18 @@ +package domains + +import ( + "github.com/ethereum/go-ethereum/common" + "math/big" +) + +// DisputeStatus is the return type from DisputeStatus. +type DisputeStatus struct { + // DisputeFlag is the 0: None; 1: Pending; 2: Slashed. + DisputeFlag uint8 + // Rival is the address of the rival. + Rival common.Address + // FraudProver is the address of the fraud prover. + FraudProver common.Address + // DisputePtr is the value of the dispute pointer. + DisputePtr *big.Int +} diff --git a/agents/testutil/contracttype.go b/agents/testutil/contracttype.go index b36bb9cc4e..4b3c3cbb03 100644 --- a/agents/testutil/contracttype.go +++ b/agents/testutil/contracttype.go @@ -20,6 +20,7 @@ import ( "github.com/synapsecns/sanguine/agents/contracts/test/messageharness" "github.com/synapsecns/sanguine/agents/contracts/test/originharness" "github.com/synapsecns/sanguine/agents/contracts/test/pingpongclient" + "github.com/synapsecns/sanguine/agents/contracts/test/receiptharness" "github.com/synapsecns/sanguine/agents/contracts/test/requestharness" "github.com/synapsecns/sanguine/agents/contracts/test/snapshotharness" "github.com/synapsecns/sanguine/agents/contracts/test/stateharness" @@ -32,7 +33,7 @@ import ( // set all contact types. func init() { - for i := 0; i < len(_contractTypeImpl_index)-1; i++ { + for i := 1; i < len(_contractTypeImpl_index); i++ { contractType := contractTypeImpl(i) AllContractTypes = append(AllContractTypes, contractType) // assert type is correct @@ -64,11 +65,13 @@ type contractTypeImpl int const ( // OriginType is the type of the origin. - OriginType contractTypeImpl = iota // Origin + OriginType contractTypeImpl = iota + 1 // Origin // MessageHarnessType is the type of the message harness contract. MessageHarnessType // MessageHarness // BaseMessageHarnessType is the type of the base message harness contract. BaseMessageHarnessType // BaseMessageHarness + // ReceiptHarnessType is the type of the receipt harness contract. + ReceiptHarnessType // ReceiptHarness // RequestHarnessType is the type of the request harness contract. RequestHarnessType // RequestHarness // OriginHarnessType is the origin harness type. @@ -141,6 +144,8 @@ func (c contractTypeImpl) ContractInfo() *compiler.Contract { return messageharness.Contracts["solidity/MessageHarness.t.sol:MessageHarness"] case BaseMessageHarnessType: return basemessageharness.Contracts["solidity/BaseMessageHarness.t.sol:BaseMessageHarness"] + case ReceiptHarnessType: + return receiptharness.Contracts["solidity/ReceiptHarness.t.sol:ReceiptHarness"] case RequestHarnessType: return requestharness.Contracts["solidity/RequestHarness.t.sol:RequestHarness"] case OriginHarnessType: diff --git a/agents/testutil/contracttypeimpl_string.go b/agents/testutil/contracttypeimpl_string.go index 7011c72716..105cddb2df 100644 --- a/agents/testutil/contracttypeimpl_string.go +++ b/agents/testutil/contracttypeimpl_string.go @@ -8,40 +8,42 @@ func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} - _ = x[OriginType-0] - _ = x[MessageHarnessType-1] - _ = x[BaseMessageHarnessType-2] - _ = x[RequestHarnessType-3] - _ = x[OriginHarnessType-4] - _ = x[StateHarnessType-5] - _ = x[SnapshotHarnessType-6] - _ = x[AttestationHarnessType-7] - _ = x[TipsHarnessType-8] - _ = x[HeaderHarnessType-9] - _ = x[DestinationHarnessType-10] - _ = x[SummitHarnessType-11] - _ = x[SummitType-12] - _ = x[DestinationType-13] - _ = x[AgentsTestContractType-14] - _ = x[TestClientType-15] - _ = x[PingPongClientType-16] - _ = x[LightManagerHarnessType-17] - _ = x[BondingManagerHarnessType-18] - _ = x[LightManagerType-19] - _ = x[BondingManagerType-20] - _ = x[GasDataHarnessType-21] - _ = x[GasOracleType-22] - _ = x[InboxType-23] - _ = x[LightInboxType-24] + _ = x[OriginType-1] + _ = x[MessageHarnessType-2] + _ = x[BaseMessageHarnessType-3] + _ = x[ReceiptHarnessType-4] + _ = x[RequestHarnessType-5] + _ = x[OriginHarnessType-6] + _ = x[StateHarnessType-7] + _ = x[SnapshotHarnessType-8] + _ = x[AttestationHarnessType-9] + _ = x[TipsHarnessType-10] + _ = x[HeaderHarnessType-11] + _ = x[DestinationHarnessType-12] + _ = x[SummitHarnessType-13] + _ = x[SummitType-14] + _ = x[DestinationType-15] + _ = x[AgentsTestContractType-16] + _ = x[TestClientType-17] + _ = x[PingPongClientType-18] + _ = x[LightManagerHarnessType-19] + _ = x[BondingManagerHarnessType-20] + _ = x[LightManagerType-21] + _ = x[BondingManagerType-22] + _ = x[GasDataHarnessType-23] + _ = x[GasOracleType-24] + _ = x[InboxType-25] + _ = x[LightInboxType-26] } -const _contractTypeImpl_name = "OriginMessageHarnessBaseMessageHarnessRequestHarnessOriginHarnessStateHarnessTypeSnapshotHarnessTypeAttestationHarnessTypeTipsHarnessTypeHeaderHarnessTypeDestinationHarnessSummitHarnessSummitDestinationAgentsTestContractTestClientPingPongClientLightManagerHarnessBondingManagerHarnessLightManagerBondingManagerGasDataHarnessTypeGasOracleInboxLightInbox" +const _contractTypeImpl_name = "OriginMessageHarnessBaseMessageHarnessReceiptHarnessRequestHarnessOriginHarnessStateHarnessTypeSnapshotHarnessTypeAttestationHarnessTypeTipsHarnessTypeHeaderHarnessTypeDestinationHarnessSummitHarnessSummitDestinationAgentsTestContractTestClientPingPongClientLightManagerHarnessBondingManagerHarnessLightManagerBondingManagerGasDataHarnessTypeGasOracleInboxLightInbox" -var _contractTypeImpl_index = [...]uint16{0, 6, 20, 38, 52, 65, 81, 100, 122, 137, 154, 172, 185, 191, 202, 220, 230, 244, 263, 284, 296, 310, 328, 337, 342, 352} +var _contractTypeImpl_index = [...]uint16{0, 6, 20, 38, 52, 66, 79, 95, 114, 136, 151, 168, 186, 199, 205, 216, 234, 244, 258, 277, 298, 310, 324, 342, 351, 356, 366} func (i contractTypeImpl) String() string { + i -= 1 if i < 0 || i >= contractTypeImpl(len(_contractTypeImpl_index)-1) { - return "contractTypeImpl(" + strconv.FormatInt(int64(i), 10) + ")" + return "contractTypeImpl(" + strconv.FormatInt(int64(i+1), 10) + ")" } return _contractTypeImpl_name[_contractTypeImpl_index[i]:_contractTypeImpl_index[i+1]] } diff --git a/agents/testutil/deployers.go b/agents/testutil/deployers.go index ea86d43a80..4ca0cf88ee 100644 --- a/agents/testutil/deployers.go +++ b/agents/testutil/deployers.go @@ -3,6 +3,7 @@ package testutil import ( "context" "fmt" + "github.com/synapsecns/sanguine/agents/contracts/bondingmanager" "github.com/synapsecns/sanguine/agents/contracts/gasoracle" "github.com/synapsecns/sanguine/agents/contracts/inbox" @@ -21,6 +22,10 @@ import ( "github.com/synapsecns/sanguine/ethergo/deployer" ) +// SynChainID the id of the SynChain. +// TODO: no longer needs to be hardcoded: https://github.com/synapsecns/sanguine/pull/1280#discussion_r1320882617 +const SynChainID uint = 10 + // LightInboxDeployer deploys the light inbox contract. type LightInboxDeployer struct { *deployer.BaseDeployer @@ -40,6 +45,10 @@ func (d LightInboxDeployer) Deploy(ctx context.Context) (contracts.DeployedContr originAddress := originContract.Address() destinationAddress := destinationContract.Address()*/ return d.DeploySimpleContract(ctx, func(transactOps *bind.TransactOpts, backend bind.ContractBackend) (address common.Address, tx *types.Transaction, data interface{}, err error) { + if d.Backend().GetChainID() == SynChainID { + return common.Address{}, nil, nil, fmt.Errorf("could not deploy %s on synchain", d.ContractType().ContractName()) + } + // deploy the light inbox contract var rawHandle *lightinbox.LightInbox address, tx, rawHandle, err = lightinbox.DeployLightInbox(transactOps, backend, SynapseChainID) @@ -85,6 +94,10 @@ func (d LightManagerDeployer) Deploy(ctx context.Context) (contracts.DeployedCon originAddress := originContract.Address() destinationAddress := destinationContract.Address()*/ return d.DeploySimpleContract(ctx, func(transactOps *bind.TransactOpts, backend bind.ContractBackend) (address common.Address, tx *types.Transaction, data interface{}, err error) { + if d.Backend().GetChainID() == SynChainID { + return common.Address{}, nil, nil, fmt.Errorf("could not deploy %s on synchain", d.ContractType().ContractName()) + } + // deploy the light manager contract var rawHandle *lightmanager.LightManager address, tx, rawHandle, err = lightmanager.DeployLightManager(transactOps, backend, SynapseChainID) @@ -129,6 +142,10 @@ func (d InboxDeployer) Deploy(ctx context.Context) (contracts.DeployedContract, originAddress := originContract.Address() destinationAddress := destinationContract.Address()*/ return d.DeploySimpleContract(ctx, func(transactOps *bind.TransactOpts, backend bind.ContractBackend) (address common.Address, tx *types.Transaction, data interface{}, err error) { + if d.Backend().GetChainID() != SynChainID { + return common.Address{}, nil, nil, fmt.Errorf("could not deploy %s on non-synchain", d.ContractType().ContractName()) + } + // deploy the inbox contract var rawHandle *inbox.Inbox address, tx, rawHandle, err = inbox.DeployInbox(transactOps, backend, SynapseChainID) @@ -173,6 +190,10 @@ func (d BondingManagerDeployer) Deploy(ctx context.Context) (contracts.DeployedC originAddress := originContract.Address() destinationAddress := destinationContract.Address()*/ return d.DeploySimpleContract(ctx, func(transactOps *bind.TransactOpts, backend bind.ContractBackend) (address common.Address, tx *types.Transaction, data interface{}, err error) { + if d.Backend().GetChainID() != SynChainID { + return common.Address{}, nil, nil, fmt.Errorf("could not deploy %s on a chain that is not synchain", d.ContractType().ContractName()) + } + // deploy the bonding manager contract var rawHandle *bondingmanager.BondingManager address, tx, rawHandle, err = bondingmanager.DeployBondingManager(transactOps, backend, SynapseChainID) @@ -258,7 +279,7 @@ func NewOriginDeployer(registry deployer.GetOnlyContractRegistry, backend backen func (d OriginDeployer) Deploy(ctx context.Context) (contracts.DeployedContract, error) { var agentAddress common.Address var inboxAddress common.Address - if d.Backend().GetChainID() == 10 { + if d.Backend().GetChainID() == SynChainID { bondingManagerContract := d.Registry().Get(ctx, BondingManagerType) agentAddress = bondingManagerContract.Address() @@ -318,7 +339,12 @@ func (a SummitDeployer) Deploy(ctx context.Context) (contracts.DeployedContract, bondingManagerAddress := bondingManagerContract.Address() inboxContract := a.Registry().Get(ctx, InboxType) inboxAddress := inboxContract.Address() + return a.DeploySimpleContract(ctx, func(transactOps *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, interface{}, error) { + if a.Backend().GetChainID() != SynChainID { + return common.Address{}, nil, nil, fmt.Errorf("could not deploy %s on nonsynchain", a.ContractType().ContractName()) + } + summitAddress, summitTx, summit, err := summit.DeploySummit(transactOps, backend, uint32(a.Backend().GetChainID()), bondingManagerAddress, inboxAddress) if err != nil { return common.Address{}, nil, nil, fmt.Errorf("could not deploy summit: %w", err) diff --git a/agents/testutil/harness.go b/agents/testutil/harness.go index 428c64ed13..7378fed1dd 100644 --- a/agents/testutil/harness.go +++ b/agents/testutil/harness.go @@ -9,6 +9,7 @@ import ( gasdataharness "github.com/synapsecns/sanguine/agents/contracts/test/gasdata" "github.com/synapsecns/sanguine/agents/contracts/test/lightmanagerharness" "github.com/synapsecns/sanguine/agents/contracts/test/originharness" + "github.com/synapsecns/sanguine/agents/contracts/test/receiptharness" "github.com/synapsecns/sanguine/agents/contracts/test/requestharness" "github.com/synapsecns/sanguine/agents/contracts/test/snapshotharness" "github.com/synapsecns/sanguine/agents/contracts/test/stateharness" @@ -71,6 +72,25 @@ func (d BaseMessageHarnessDeployer) Deploy(ctx context.Context) (contracts.Deplo }) } +// ReceiptHarnessDeployer deploys the request harness for testing. +type ReceiptHarnessDeployer struct { + *deployer.BaseDeployer +} + +// NewReceiptHarnessDeployer creates a request harness deployer. +func NewReceiptHarnessDeployer(registry deployer.GetOnlyContractRegistry, backend backends.SimulatedTestBackend) deployer.ContractDeployer { + return ReceiptHarnessDeployer{deployer.NewSimpleDeployer(registry, backend, ReceiptHarnessType)} +} + +// Deploy deploys the receipt harness deployer. +func (d ReceiptHarnessDeployer) Deploy(ctx context.Context) (contracts.DeployedContract, error) { + return d.DeploySimpleContract(ctx, func(transactOpts *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, interface{}, error) { + return receiptharness.DeployReceiptHarness(transactOpts, backend) + }, func(address common.Address, backend bind.ContractBackend) (interface{}, error) { + return receiptharness.NewReceiptHarnessRef(address, backend) + }) +} + // RequestHarnessDeployer deploys the request harness for testing. type RequestHarnessDeployer struct { *deployer.BaseDeployer diff --git a/agents/testutil/manager.go b/agents/testutil/manager.go index aa82a7d635..a5bbd0aa97 100644 --- a/agents/testutil/manager.go +++ b/agents/testutil/manager.go @@ -17,7 +17,7 @@ func NewDeployManager(t *testing.T) *DeployManager { NewTipsHarnessDeployer, NewDestinationDeployer, NewDestinationHarnessDeployer, NewSummitHarnessDeployer, NewHeaderHarnessDeployer, NewAgentsTestContractDeployer, NewTestClientDeployer, NewPingPongClientDeployer, NewStateHarnessDeployer, NewSnapshotHarnessDeployer, NewAttestationHarnessDeployer, NewGasDataHarnessDeployer, - NewGasOracleDeployer, NewBaseMessageHarnessDeployer, NewRequestHarnessDeployer, + NewGasOracleDeployer, NewBaseMessageHarnessDeployer, NewRequestHarnessDeployer, NewReceiptHarnessDeployer, ) return &DeployManager{parentManager} } diff --git a/agents/testutil/simulated_backends_suite.go b/agents/testutil/simulated_backends_suite.go index 49a9b71246..52dd5997a2 100644 --- a/agents/testutil/simulated_backends_suite.go +++ b/agents/testutil/simulated_backends_suite.go @@ -104,14 +104,18 @@ type SimulatedBackendsTestSuite struct { TestBackendSummit backends.SimulatedTestBackend NotaryBondedWallet wallet.Wallet NotaryOnOriginBondedWallet wallet.Wallet + NotaryOnDestinationBondedWallet wallet.Wallet GuardBondedWallet wallet.Wallet NotaryBondedSigner signer.Signer NotaryOnOriginBondedSigner signer.Signer + NotaryOnDestinationBondedSigner signer.Signer GuardBondedSigner signer.Signer NotaryUnbondedWallet wallet.Wallet NotaryUnbondedSigner signer.Signer NotaryOnOriginUnbondedWallet wallet.Wallet NotaryOnOriginUnbondedSigner signer.Signer + NotaryOnDestinationUnbondedWallet wallet.Wallet + NotaryOnDestinationUnbondedSigner signer.Signer GuardUnbondedWallet wallet.Wallet GuardUnbondedSigner signer.Signer ExecutorUnbondedWallet wallet.Wallet @@ -148,7 +152,8 @@ func (a *SimulatedBackendsTestSuite) SetupSuite() { a.TestSuite.LogDir = filet.TmpDir(a.T(), "") // don't use metrics on ci for integration tests - useMetrics := core.GetEnvBool("CI", true) + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI metricsHandler := metrics.Null if useMetrics { @@ -201,6 +206,7 @@ func (a *SimulatedBackendsTestSuite) SetupOrigin(deployManager *DeployManager) { a.TestBackendOrigin.FundAccount(a.GetTestContext(), a.NotaryUnbondedSigner.Address(), *big.NewInt(params.Ether)) a.TestBackendOrigin.FundAccount(a.GetTestContext(), a.NotaryOnOriginUnbondedSigner.Address(), *big.NewInt(params.Ether)) + a.TestBackendOrigin.FundAccount(a.GetTestContext(), a.NotaryOnDestinationUnbondedSigner.Address(), *big.NewInt(params.Ether)) a.TestBackendOrigin.FundAccount(a.GetTestContext(), a.GuardUnbondedSigner.Address(), *big.NewInt(params.Ether)) a.TestBackendOrigin.FundAccount(a.GetTestContext(), a.ExecutorUnbondedSigner.Address(), *big.NewInt(params.Ether)) } @@ -241,6 +247,7 @@ func (a *SimulatedBackendsTestSuite) SetupDestination(deployManager *DeployManag a.TestBackendDestination.FundAccount(a.GetTestContext(), a.NotaryUnbondedSigner.Address(), *big.NewInt(params.Ether)) a.TestBackendDestination.FundAccount(a.GetTestContext(), a.NotaryOnOriginUnbondedSigner.Address(), *big.NewInt(params.Ether)) + a.TestBackendDestination.FundAccount(a.GetTestContext(), a.NotaryOnDestinationUnbondedSigner.Address(), *big.NewInt(params.Ether)) a.TestBackendDestination.FundAccount(a.GetTestContext(), a.GuardUnbondedSigner.Address(), *big.NewInt(params.Ether)) a.TestBackendDestination.FundAccount(a.GetTestContext(), a.ExecutorUnbondedSigner.Address(), *big.NewInt(params.Ether)) } @@ -268,6 +275,7 @@ func (a *SimulatedBackendsTestSuite) SetupSummit(deployManager *DeployManager) { a.TestBackendSummit.FundAccount(a.GetTestContext(), a.NotaryUnbondedSigner.Address(), *big.NewInt(params.Ether)) a.TestBackendSummit.FundAccount(a.GetTestContext(), a.NotaryOnOriginUnbondedSigner.Address(), *big.NewInt(params.Ether)) + a.TestBackendSummit.FundAccount(a.GetTestContext(), a.NotaryOnDestinationUnbondedSigner.Address(), *big.NewInt(params.Ether)) a.TestBackendSummit.FundAccount(a.GetTestContext(), a.GuardUnbondedSigner.Address(), *big.NewInt(params.Ether)) a.TestBackendSummit.FundAccount(a.GetTestContext(), a.ExecutorUnbondedSigner.Address(), *big.NewInt(params.Ether)) } @@ -320,6 +328,22 @@ func (a *SimulatedBackendsTestSuite) SetupNotaryOnOrigin() { a.NotaryOnOriginUnbondedSigner = localsigner.NewSigner(a.NotaryOnOriginUnbondedWallet.PrivateKey()) } +// SetupNotaryOnDestination sets up the Notary agent on the origin chain. +func (a *SimulatedBackendsTestSuite) SetupNotaryOnDestination() { + var err error + a.NotaryOnDestinationBondedWallet, err = wallet.FromRandom() + if err != nil { + a.T().Fatal(err) + } + a.NotaryOnDestinationBondedSigner = localsigner.NewSigner(a.NotaryOnDestinationBondedWallet.PrivateKey()) + + a.NotaryOnDestinationUnbondedWallet, err = wallet.FromRandom() + if err != nil { + a.T().Fatal(err) + } + a.NotaryOnDestinationUnbondedSigner = localsigner.NewSigner(a.NotaryOnDestinationUnbondedWallet.PrivateKey()) +} + // SetupExecutor sets up the Executor agent. func (a *SimulatedBackendsTestSuite) SetupExecutor() { var err error @@ -338,6 +362,7 @@ func (a *SimulatedBackendsTestSuite) SetupTest() { a.SetupGuard() a.SetupNotary() a.SetupNotaryOnOrigin() + a.SetupNotaryOnDestination() a.SetupExecutor() a.TestDeployManager = NewDeployManager(a.T()) @@ -346,21 +371,21 @@ func (a *SimulatedBackendsTestSuite) SetupTest() { wg.Add(3) go func() { defer wg.Done() - anvilOpts := anvil.NewAnvilOptionBuilder() - anvilOpts.SetChainID(uint64(params.RinkebyChainConfig.ChainID.Int64())) - a.TestBackendOrigin = anvil.NewAnvilBackend(a.GetTestContext(), a.T(), anvilOpts) + anvilOptsOrigin := anvil.NewAnvilOptionBuilder() + anvilOptsOrigin.SetChainID(uint64(params.RinkebyChainConfig.ChainID.Int64())) + a.TestBackendOrigin = anvil.NewAnvilBackend(a.GetTestContext(), a.T(), anvilOptsOrigin) }() go func() { defer wg.Done() - anvilOpts := anvil.NewAnvilOptionBuilder() - anvilOpts.SetChainID(uint64(client.ChapelChainConfig.ChainID.Int64())) - a.TestBackendDestination = anvil.NewAnvilBackend(a.GetTestContext(), a.T(), anvilOpts) + anvilOptsDestination := anvil.NewAnvilOptionBuilder() + anvilOptsDestination.SetChainID(uint64(client.ChapelChainConfig.ChainID.Int64())) + a.TestBackendDestination = anvil.NewAnvilBackend(a.GetTestContext(), a.T(), anvilOptsDestination) }() go func() { defer wg.Done() - anvilOpts := anvil.NewAnvilOptionBuilder() - anvilOpts.SetChainID(uint64(10)) - a.TestBackendSummit = anvil.NewAnvilBackend(a.GetTestContext(), a.T(), anvilOpts) + anvilOptsSummit := anvil.NewAnvilOptionBuilder() + anvilOptsSummit.SetChainID(uint64(10)) + a.TestBackendSummit = anvil.NewAnvilBackend(a.GetTestContext(), a.T(), anvilOptsSummit) }() wg.Wait() @@ -370,21 +395,6 @@ func (a *SimulatedBackendsTestSuite) SetupTest() { a.TestBackendSummit, } - /* - a.TestDeployManager.BulkDeploy(a.GetTestContext(), testBackends, - InboxType, - BondingManagerHarnessType, - SummitHarnessType, - AgentsTestContractType, - DestinationHarnessType, - OriginHarnessType, - TestClientType, - PingPongClientType, - LightInboxType, - LightManagerHarnessType, - ) - */ - wg.Add(3) go func() { defer wg.Done() @@ -406,8 +416,8 @@ func (a *SimulatedBackendsTestSuite) SetupTest() { a.GetTestContext(), a.TestBackendSummit, []backends.SimulatedTestBackend{a.TestBackendOrigin, a.TestBackendDestination}, - []common.Address{a.GuardBondedSigner.Address(), a.NotaryBondedSigner.Address(), a.NotaryOnOriginBondedSigner.Address()}, - []uint32{uint32(0), uint32(a.TestBackendDestination.GetChainID()), uint32(a.TestBackendOrigin.GetChainID())}) + []common.Address{a.GuardBondedSigner.Address(), a.NotaryBondedSigner.Address(), a.NotaryOnOriginBondedSigner.Address(), a.NotaryOnDestinationBondedSigner.Address()}, + []uint32{uint32(0), uint32(a.TestBackendDestination.GetChainID()), uint32(a.TestBackendOrigin.GetChainID()), uint32(a.TestBackendDestination.GetChainID())}) if err != nil { a.T().Fatal(err) } @@ -438,6 +448,10 @@ func (a *SimulatedBackendsTestSuite) SetupTest() { // cleanAfterTestSuite does cleanup after test suite is finished. func (a *SimulatedBackendsTestSuite) cleanAfterTestSuite() { filet.CleanUp(a.T()) + // This shouldn't be necessary, but is added for a recurring flake + a.TestBackendSummit = nil + a.TestBackendOrigin = nil + a.TestBackendDestination = nil } // BumpBackend is a helper to get the test backend to emit expected events. diff --git a/agents/testutil/typecast.go b/agents/testutil/typecast.go index d1f768b325..074bcd74b4 100644 --- a/agents/testutil/typecast.go +++ b/agents/testutil/typecast.go @@ -3,6 +3,9 @@ package testutil import ( "context" "fmt" + "github.com/synapsecns/sanguine/agents/contracts/test/receiptharness" + "sync" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/synapsecns/sanguine/agents/contracts/bondingmanager" @@ -15,6 +18,7 @@ import ( "github.com/synapsecns/sanguine/agents/contracts/test/lightmanagerharness" "github.com/synapsecns/sanguine/agents/contracts/test/requestharness" "github.com/synapsecns/sanguine/ethergo/manager" + "golang.org/x/sync/errgroup" "github.com/synapsecns/sanguine/agents/contracts/test/attestationharness" "github.com/synapsecns/sanguine/agents/contracts/test/summitharness" @@ -151,6 +155,14 @@ func (d *DeployManager) GetSnapshotHarness(ctx context.Context, backend backends return manager.GetContract[*snapshotharness.SnapshotHarnessRef](ctx, d.T(), d, backend, SnapshotHarnessType) } +// GetReceiptHarness gets the receipt harness. +// nolint:dupl +func (d *DeployManager) GetReceiptHarness(ctx context.Context, backend backends.SimulatedTestBackend) (contract contracts.DeployedContract, handle *receiptharness.ReceiptHarnessRef) { + d.T().Helper() + + return manager.GetContract[*receiptharness.ReceiptHarnessRef](ctx, d.T(), d, backend, ReceiptHarnessType) +} + // GetAttestationHarness gets the attestation harness. // nolint:dupl func (d *DeployManager) GetAttestationHarness(ctx context.Context, backend backends.SimulatedTestBackend) (contract contracts.DeployedContract, handle *attestationharness.AttestationHarnessRef) { @@ -669,24 +681,36 @@ func (d *DeployManager) LoadHarnessContractsOnChains( return fmt.Errorf("could not add agents to bonding manager harness on syn chain: %w", err) } - for _, backend := range backends { - err := d.InitializeRemoteDeployedHarnessContracts( - ctx, - backend, - bondingManagerHarnessAgentRoot) - if err != nil { - return fmt.Errorf("could not initialize remote deplyed harness contracts: %w", err) - } - - err = d.AddAgentsToLightManagerHarnessContract( - ctx, - backend, - agents, - agentProofs, - agentStatuses) - if err != nil { - return fmt.Errorf("could not add agents to remote light manager harness contract: %w", err) - } + wg := sync.WaitGroup{} + g, ctx := errgroup.WithContext(ctx) + for _, b := range backends { + backend := b + wg.Add(1) + g.Go(func() error { + defer wg.Done() + err := d.InitializeRemoteDeployedHarnessContracts( + ctx, + backend, + bondingManagerHarnessAgentRoot) + if err != nil { + return fmt.Errorf("could not initialize remote deplyed harness contracts: %w", err) + } + + err = d.AddAgentsToLightManagerHarnessContract( + ctx, + backend, + agents, + agentProofs, + agentStatuses) + if err != nil { + return fmt.Errorf("could not add agents to remote light manager harness contract: %w", err) + } + return nil + }) + } + err = g.Wait() + if err != nil { + return fmt.Errorf("could not initialize remote deplyed harness contracts: %w", err) } return nil diff --git a/agents/types/agent_status.go b/agents/types/agent_status.go index f49093d5c4..4595217a42 100644 --- a/agents/types/agent_status.go +++ b/agents/types/agent_status.go @@ -10,7 +10,7 @@ const ( // AgentStatus is the agent status interface. type AgentStatus interface { // Flag is the current status flag of the agent. - Flag() uint8 + Flag() AgentFlagType // Domain that agent is assigned to. Domain() uint32 // Index of the agent in list of agents. @@ -18,13 +18,13 @@ type AgentStatus interface { } type agentStatus struct { - flag uint8 + flag AgentFlagType domain uint32 index uint32 } // NewAgentStatus creates a new agent status. -func NewAgentStatus(flag uint8, domain, index uint32) AgentStatus { +func NewAgentStatus(flag AgentFlagType, domain, index uint32) AgentStatus { return &agentStatus{ flag: flag, domain: domain, @@ -32,7 +32,7 @@ func NewAgentStatus(flag uint8, domain, index uint32) AgentStatus { } } -func (s agentStatus) Flag() uint8 { +func (s agentStatus) Flag() AgentFlagType { return s.flag } diff --git a/agents/types/agent_status_relayed_state.go b/agents/types/agent_status_relayed_state.go new file mode 100644 index 0000000000..6489af534f --- /dev/null +++ b/agents/types/agent_status_relayed_state.go @@ -0,0 +1,11 @@ +package types + +// AgentStatusRelayedState represents the state of a RelayableAgentStatus model. +type AgentStatusRelayedState uint8 + +const ( + // Queued is when an agent status has been updated on Summit, but has not been relayed to the remote chain. + Queued AgentStatusRelayedState = iota + // Relayed is when the agent status has been relayed to the remote chain. + Relayed +) diff --git a/agents/types/agent_tree.go b/agents/types/agent_tree.go new file mode 100644 index 0000000000..bf7d4ef3ee --- /dev/null +++ b/agents/types/agent_tree.go @@ -0,0 +1,19 @@ +package types + +import "github.com/ethereum/go-ethereum/common" + +// AgentTree is a version of the database AgentTree model with solidity-compatible types. +type AgentTree struct { + // AgentRoot is the root of the agent tree. + AgentRoot string + // AgentAddress is the address of the agent for the Merkle proof. + AgentAddress common.Address + // AgentDomain is the domain of the agent. + AgentDomain uint32 + // UpdatedAgentFlag is the updated agent flag corresponding to the agent tree. + UpdatedAgentFlag AgentFlagType + // BlockNumber is the block number that the agent tree was updated (on summit). + BlockNumber uint64 + // Proof is the agent tree proof. + Proof [][32]byte +} diff --git a/agents/types/attestation.go b/agents/types/attestation.go index 81eab77a00..d6d0cfa88a 100644 --- a/agents/types/attestation.go +++ b/agents/types/attestation.go @@ -2,12 +2,11 @@ package types import ( "context" - "fmt" + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/ethergo/signer/signer" - "math/big" ) const ( @@ -21,6 +20,7 @@ const ( // Attestation is the attestation interface. type Attestation interface { + Encoder // SnapshotRoot is the root of the Snapshot Merkle Tree. SnapshotRoot() [32]byte // DataHash is the agent root and SnapGasHash combined into a single hash. @@ -32,7 +32,7 @@ type Attestation interface { // Timestamp is the timestamp when the attestation was created in Summit. Timestamp() *big.Int // SignAttestation signs the attestation - SignAttestation(ctx context.Context, signer signer.Signer) (signer.Signature, []byte, common.Hash, error) + SignAttestation(ctx context.Context, signer signer.Signer, valid bool) (signer.Signature, []byte, common.Hash, error) } type attestation struct { @@ -74,25 +74,23 @@ func (a attestation) Timestamp() *big.Int { return a.timestamp } -func (a attestation) SignAttestation(ctx context.Context, signer signer.Signer) (signer.Signature, []byte, common.Hash, error) { - encodedAttestation, err := EncodeAttestation(a) - if err != nil { - return nil, nil, common.Hash{}, fmt.Errorf("could not encode attestation: %w", err) +func (a attestation) SignAttestation(ctx context.Context, signer signer.Signer, valid bool) (signer.Signature, []byte, common.Hash, error) { + var attestationSalt string + if valid { + attestationSalt = AttestationValidSalt + } else { + attestationSalt = AttestationInvalidSalt } + return signEncoder(ctx, signer, a, attestationSalt) +} - attestationSalt := crypto.Keccak256Hash([]byte("ATTESTATION_VALID_SALT")) - - hashedEncodedAttestation := crypto.Keccak256Hash(encodedAttestation).Bytes() - toSign := append(attestationSalt.Bytes(), hashedEncodedAttestation...) +// GetAttestationDataHash generates the data hash from the agent root and SnapGasHash. +func GetAttestationDataHash(agentRoot [32]byte, snapGasHash [32]byte) [32]byte { + concatenatedBytes := append(agentRoot[:], snapGasHash[:]...) + dataHash := crypto.Keccak256(concatenatedBytes) - hashedAttestation, err := HashRawBytes(toSign) - if err != nil { - return nil, nil, common.Hash{}, fmt.Errorf("could not hash attestation: %w", err) - } + var dataHashB32 [32]byte + copy(dataHashB32[:], dataHash) - signature, err := signer.SignMessage(ctx, core.BytesToSlice(hashedAttestation), false) - if err != nil { - return nil, nil, common.Hash{}, fmt.Errorf("could not sign attestation: %w", err) - } - return signature, encodedAttestation, hashedAttestation, nil + return dataHashB32 } diff --git a/agents/types/chain_gas.go b/agents/types/chain_gas.go index 9dfcc1ef12..a3fa98cd95 100644 --- a/agents/types/chain_gas.go +++ b/agents/types/chain_gas.go @@ -1,5 +1,7 @@ package types +import "math/big" + const ( chainGasOffsetDomain = 0 chainGasOffsetGasData = 4 @@ -35,4 +37,19 @@ func (g chainGas) Domain() uint32 { return g.domain } +// ChainGassesToSnapGas converts a slice of ChainGas to a slice of big.Int. +func ChainGassesToSnapGas(chainGasses []ChainGas) (snapGasses []*big.Int, err error) { + snapGasses = make([]*big.Int, len(chainGasses)) + for i, cg := range chainGasses { + snapGas, err := EncodeChainGas(cg) + if err != nil { + return nil, err + } + + snapGasses[i] = new(big.Int).SetBytes(snapGas) + } + + return snapGasses, nil +} + var _ ChainGas = chainGas{} diff --git a/agents/types/dispute_flag_type.go b/agents/types/dispute_flag_type.go new file mode 100644 index 0000000000..9a48df2eb7 --- /dev/null +++ b/agents/types/dispute_flag_type.go @@ -0,0 +1,15 @@ +package types + +// DisputeFlagType is the type for the Dispute Status Flag. +// +//go:generate go run golang.org/x/tools/cmd/stringer -type=DisputeFlagType -linecomment +type DisputeFlagType uint8 + +const ( + // DisputeFlagNone means agent is not in dispute. + DisputeFlagNone DisputeFlagType = iota + // DisputeFlagPending means agent is in unresolved dispute. + DisputeFlagPending + // DisputeFlagSlashed means agent was in dispute that lead to agent being slashed. + DisputeFlagSlashed +) diff --git a/agents/types/dispute_processed_status.go b/agents/types/dispute_processed_status.go new file mode 100644 index 0000000000..e5d5163f4a --- /dev/null +++ b/agents/types/dispute_processed_status.go @@ -0,0 +1,14 @@ +package types + +// DisputeProcessedStatus is the status of a dispute on Summit. +// This enum is used for tracking the status of disputes in the Dispute db model. +type DisputeProcessedStatus uint8 + +const ( + // Opened is when a dispute has been opened but has not been resolved. + Opened DisputeProcessedStatus = iota + // Resolved is when a dispute has been resolved on Summit, but agent status has not been updated on the remote chain. + Resolved + // Propagated is when a dispute has been resolved on Summit, and agent status has been updated on the remote chain. + Propagated +) diff --git a/agents/types/dispute_status.go b/agents/types/dispute_status.go new file mode 100644 index 0000000000..d5054d68e3 --- /dev/null +++ b/agents/types/dispute_status.go @@ -0,0 +1,55 @@ +package types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/synapsecns/sanguine/core" +) + +// DisputeStatus is the dispute status interface. +type DisputeStatus interface { + // Flag is the current status flag of the dispute. + Flag() DisputeFlagType + // Rival is the address of the rival. + Rival() common.Address + // FraudProver is the address of the fraud prover for this dispute. + FraudProver() common.Address + // DisputePtr is the index of the dispute. + DisputePtr() *big.Int +} + +type disputeStatus struct { + flag DisputeFlagType + rival common.Address + fraudProver common.Address + disputePtr *big.Int +} + +// NewDisputeStatus creates a new dispute status. +func NewDisputeStatus(flag DisputeFlagType, rival, fraudProver common.Address, disputePtr *big.Int) DisputeStatus { + return &disputeStatus{ + flag: flag, + rival: rival, + fraudProver: fraudProver, + disputePtr: disputePtr, + } +} + +func (s disputeStatus) Flag() DisputeFlagType { + return s.flag +} + +func (s disputeStatus) Rival() common.Address { + return s.rival +} + +func (s disputeStatus) FraudProver() common.Address { + return s.fraudProver +} + +func (s disputeStatus) DisputePtr() *big.Int { + return core.CopyBigInt(s.disputePtr) +} + +var _ DisputeStatus = disputeStatus{} diff --git a/agents/types/disputeflagtype_string.go b/agents/types/disputeflagtype_string.go new file mode 100644 index 0000000000..28826bd154 --- /dev/null +++ b/agents/types/disputeflagtype_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type=DisputeFlagType -linecomment"; DO NOT EDIT. + +package types + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[DisputeFlagNone-0] + _ = x[DisputeFlagPending-1] + _ = x[DisputeFlagSlashed-2] +} + +const _DisputeFlagType_name = "DisputeFlagNoneDisputeFlagPendingDisputeFlagSlashed" + +var _DisputeFlagType_index = [...]uint8{0, 15, 33, 51} + +func (i DisputeFlagType) String() string { + if i >= DisputeFlagType(len(_DisputeFlagType_index)-1) { + return "DisputeFlagType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _DisputeFlagType_name[_DisputeFlagType_index[i]:_DisputeFlagType_index[i+1]] +} diff --git a/agents/types/encoder.go b/agents/types/encoder.go index de7a602fd8..3873af3b8f 100644 --- a/agents/types/encoder.go +++ b/agents/types/encoder.go @@ -20,6 +20,11 @@ const ( uint40Len = 5 ) +// Encoder encodes a type to bytes. +type Encoder interface { + Encode() ([]byte, error) +} + // EncodeGasData encodes a gasdata. func EncodeGasData(gasData GasData) ([]byte, error) { b := make([]byte, 0) @@ -106,23 +111,34 @@ func DecodeChainGas(toDecode []byte) (ChainGas, error) { }, nil } -// EncodeState encodes a state. -func EncodeState(state State) ([]byte, error) { +// Encode encodes a state. +func (s state) Encode() ([]byte, error) { b := make([]byte, 0) originBytes := make([]byte, uint32Len) nonceBytes := make([]byte, uint32Len) - binary.BigEndian.PutUint32(originBytes, state.Origin()) - binary.BigEndian.PutUint32(nonceBytes, state.Nonce()) - root := state.Root() + binary.BigEndian.PutUint32(originBytes, s.Origin()) + binary.BigEndian.PutUint32(nonceBytes, s.Nonce()) + root := s.Root() + + // Note that since we are packing an 8 byte (int64) number into 5 bytes, we need to + // ensure that the result does not exceed the expected byte length for a valid s. + blockNumberBytes := math.PaddedBigBytes(s.BlockNumber(), uint40Len) + if len(blockNumberBytes) != uint40Len { + return nil, fmt.Errorf("invalid block number length, expected %d, got %d", uint40Len, len(blockNumberBytes)) + } + timestampBytes := math.PaddedBigBytes(s.Timestamp(), uint40Len) + if len(timestampBytes) != uint40Len { + return nil, fmt.Errorf("invalid timestamp length, expected %d, got %d", uint40Len, len(timestampBytes)) + } b = append(b, root[:]...) b = append(b, originBytes...) b = append(b, nonceBytes...) - b = append(b, math.PaddedBigBytes(state.BlockNumber(), uint40Len)...) - b = append(b, math.PaddedBigBytes(state.Timestamp(), uint40Len)...) + b = append(b, blockNumberBytes...) + b = append(b, timestampBytes...) - gasDataEncoded, err := EncodeGasData(state.GasData()) + gasDataEncoded, err := EncodeGasData(s.GasData()) if err != nil { return nil, fmt.Errorf("failed to encode gas data for state %w", err) } @@ -162,9 +178,9 @@ func DecodeState(toDecode []byte) (State, error) { }, nil } -// EncodeSnapshot encodes a snapshot. -func EncodeSnapshot(snapshot Snapshot) ([]byte, error) { - states := snapshot.States() +// Encode encodes a snapshot. +func (s snapshot) Encode() ([]byte, error) { + states := s.States() if len(states) == 0 { return nil, fmt.Errorf("no states to encode") @@ -173,7 +189,7 @@ func EncodeSnapshot(snapshot Snapshot) ([]byte, error) { encodedStates := make([]byte, 0) for _, state := range states { - encodedState, err := EncodeState(state) + encodedState, err := state.Encode() if err != nil { return nil, fmt.Errorf("could not encode state: %w", err) } @@ -204,20 +220,31 @@ func DecodeSnapshot(toDecode []byte) (Snapshot, error) { }, nil } -// EncodeAttestation encodes an attestation. -func EncodeAttestation(attestation Attestation) ([]byte, error) { +// Encode encodes an attestation. +func (a attestation) Encode() ([]byte, error) { b := make([]byte, 0) nonceBytes := make([]byte, uint32Len) - binary.BigEndian.PutUint32(nonceBytes, attestation.Nonce()) - snapshotRoot := attestation.SnapshotRoot() - dataHash := attestation.DataHash() + binary.BigEndian.PutUint32(nonceBytes, a.Nonce()) + snapshotRoot := a.SnapshotRoot() + dataHash := a.DataHash() + + // Note that since we are packing an 8 byte (int64) number into 5 bytes, we need to + // ensure that the result does not exceed the expected byte length for a valid a. + blockNumberBytes := math.PaddedBigBytes(a.BlockNumber(), uint40Len) + if len(blockNumberBytes) != uint40Len { + return nil, fmt.Errorf("invalid block number length, expected %d, got %d", uint40Len, len(blockNumberBytes)) + } + timestampBytes := math.PaddedBigBytes(a.Timestamp(), uint40Len) + if len(timestampBytes) != uint40Len { + return nil, fmt.Errorf("invalid timestamp length, expected %d, got %d", uint40Len, len(timestampBytes)) + } b = append(b, snapshotRoot[:]...) b = append(b, dataHash[:]...) b = append(b, nonceBytes...) - b = append(b, math.PaddedBigBytes(attestation.BlockNumber(), uint40Len)...) - b = append(b, math.PaddedBigBytes(attestation.Timestamp(), uint40Len)...) + b = append(b, blockNumberBytes...) + b = append(b, timestampBytes...) return b, nil } @@ -280,13 +307,28 @@ const ( //nolint:makezero func EncodeTips(tips Tips) ([]byte, error) { b := make([]byte, 0) + b = append(b, wrap64(tips.SummitTip())...) + b = append(b, wrap64(tips.AttestationTip())...) + b = append(b, wrap64(tips.ExecutionTip())...) + b = append(b, wrap64(tips.DeliveryTip())...) + return b, nil +} - b = append(b, math.PaddedBigBytes(tips.SummitTip(), uint64Len)...) - b = append(b, math.PaddedBigBytes(tips.AttestationTip(), uint64Len)...) - b = append(b, math.PaddedBigBytes(tips.ExecutionTip(), uint64Len)...) - b = append(b, math.PaddedBigBytes(tips.DeliveryTip(), uint64Len)...) +func wrap64(wrappable *big.Int) []byte { + wrapper := make([]byte, uint64Len) + binary.BigEndian.PutUint64(wrapper, wrappable.Uint64()) - return b, nil + return wrapper +} + +// EncodeTipsBigInt encodes a list of tips into a big int. +func EncodeTipsBigInt(tips Tips) (*big.Int, error) { + encodedTips, err := EncodeTips(tips) + if err != nil { + return nil, err + } + result := new(big.Int).SetBytes(encodedTips) + return result, nil } // DecodeTips decodes a tips typed mem view. @@ -497,7 +539,7 @@ func EncodeAgentStatus(agentStatus AgentStatus) ([]byte, error) { binary.BigEndian.PutUint32(domainBytes, agentStatus.Domain()) binary.BigEndian.PutUint32(indexBytes, agentStatus.Index()) - b = append(b, agentStatus.Flag()) + b = append(b, uint8(agentStatus.Flag())) b = append(b, domainBytes...) b = append(b, indexBytes...) @@ -515,8 +557,63 @@ func DecodeAgentStatus(toDecode []byte) (AgentStatus, error) { index := binary.BigEndian.Uint32(toDecode[agentStatusOffsetDomain:agentStatusSize]) return agentStatus{ - flag: flagBytes[0], + flag: AgentFlagType(flagBytes[0]), domain: domain, index: index, }, nil } + +// Encode encodes an receipt. +func (r receipt) Encode() ([]byte, error) { + b := make([]byte, 0) + originBytes := make([]byte, uint32Len) + binary.BigEndian.PutUint32(originBytes, r.Origin()) + + destBytes := make([]byte, uint32Len) + binary.BigEndian.PutUint32(destBytes, r.Destination()) + + messageHashBytes := r.MessageHash() + snapshotRootBytes := r.SnapshotRoot() + + b = append(b, originBytes...) + b = append(b, destBytes...) + b = append(b, messageHashBytes[:]...) + b = append(b, snapshotRootBytes[:]...) + b = append(b, []byte{r.StateIndex()}...) + b = append(b, r.AttestationNotary().Bytes()...) + b = append(b, r.FirstExecutor().Bytes()...) + b = append(b, r.FinalExecutor().Bytes()...) + + return b, nil +} + +// DecodeReceipt decodes an receipt. +func DecodeReceipt(toDecode []byte) (Receipt, error) { + if len(toDecode) != receiptSize { + return nil, fmt.Errorf("invalid receipt length, expected %d, got %d", receiptSize, len(toDecode)) + } + + origin := binary.BigEndian.Uint32(toDecode[receiptOffsetOrigin:receiptOffsetDestination]) + destination := binary.BigEndian.Uint32(toDecode[receiptOffsetDestination:receiptOffsetMessageHash]) + messageHash := toDecode[receiptOffsetMessageHash:receiptOffsetSnapshotRoot] + snapshotRoot := toDecode[receiptOffsetSnapshotRoot:receiptOffsetStateIndex] + stateIndex := toDecode[receiptOffsetStateIndex:receiptOffsetAttNotary][0] + attestationNotary := toDecode[receiptOffsetAttNotary:receiptOffsetFirstExecutor] + firstExecutor := toDecode[receiptOffsetFirstExecutor:receiptOffsetFinalExecutor] + finalExecutor := toDecode[receiptOffsetFinalExecutor:receiptSize] + + var messageHashB32, snapshotRootB32 [32]byte + copy(messageHashB32[:], messageHash) + copy(snapshotRootB32[:], snapshotRoot) + + return receipt{ + origin: origin, + destination: destination, + messageHash: messageHashB32, + snapshotRoot: snapshotRootB32, + stateIndex: stateIndex, + attestationNotary: common.BytesToAddress(attestationNotary), + firstExecutor: common.BytesToAddress(firstExecutor), + finalExecutor: common.BytesToAddress(finalExecutor), + }, nil +} diff --git a/agents/types/fraud_attestation.go b/agents/types/fraud_attestation.go new file mode 100644 index 0000000000..2b1a14be97 --- /dev/null +++ b/agents/types/fraud_attestation.go @@ -0,0 +1,33 @@ +package types + +import "github.com/ethereum/go-ethereum/common" + +// FraudAttestation is an attestation that was submitted by a Notary and was deemed fraudulent. +type FraudAttestation struct { + // Attestation is the underlying attestation. + Attestation Attestation + // AgentDomain is the domain of the Notary who signed the attestation. + AgentDomain uint32 + // Notary is the Notary who signed and submitted the attestation. + Notary common.Address + // Payload is the attestation payload. + Payload []byte + // Signature is the signature of the attestation payload signed by the Notary. + Signature []byte +} + +// NewFraudAttestationFromPayload creates a new FraudAttestation from the attestation payload, domain, notary and attestation signature. +func NewFraudAttestationFromPayload(attestationPayload []byte, agentDomain uint32, notary common.Address, attSignature []byte) (*FraudAttestation, error) { + decodedAttestation, err := DecodeAttestation(attestationPayload) + if err != nil { + return nil, err + } + + return &FraudAttestation{ + Attestation: decodedAttestation, + AgentDomain: agentDomain, + Notary: notary, + Payload: attestationPayload, + Signature: attSignature, + }, nil +} diff --git a/agents/types/fraud_snapshot.go b/agents/types/fraud_snapshot.go new file mode 100644 index 0000000000..ea4abe1b56 --- /dev/null +++ b/agents/types/fraud_snapshot.go @@ -0,0 +1,37 @@ +package types + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +// FraudSnapshot is a snapshot type with additional metadata for fraud handling. +type FraudSnapshot struct { + // Snapshot is the underlying snapshot. + Snapshot Snapshot + // AgentDomain is the domain of the agent that submitted the snapshot. + AgentDomain uint32 + // Agent is the agent that signed the snapshot. + Agent common.Address + // Payload is the snapshot payload. + Payload []byte + // Signature is the signature of the snapshot payload signed by the signer. + Signature []byte +} + +// NewFraudSnapshotFromPayload returns a new FraudSnapshot from a Snapshot payload and other metadata. +func NewFraudSnapshotFromPayload(snapshotPayload []byte, agentDomain uint32, agent common.Address, snapSignature []byte) (*FraudSnapshot, error) { + decodedSnapshot, err := DecodeSnapshot(snapshotPayload) + if err != nil { + return nil, fmt.Errorf("could not decode snapshot: %w", err) + } + + return &FraudSnapshot{ + Snapshot: decodedSnapshot, + AgentDomain: agentDomain, + Agent: agent, + Payload: snapshotPayload, + Signature: snapSignature, + }, nil +} diff --git a/agents/types/parity_test.go b/agents/types/parity_test.go index ab570caa03..c910438f6a 100644 --- a/agents/types/parity_test.go +++ b/agents/types/parity_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + "github.com/synapsecns/sanguine/core/testsuite" + "github.com/brianvoe/gofakeit/v6" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -31,10 +33,10 @@ func TestEncodeTipsParity(t *testing.T) { _, handle := deployManager.GetTipsHarness(ctx, testBackend) // we want to make sure we can deal w/ overflows - summitTip := randomUint64BigInt(t) - attestationTip := randomUint64BigInt(t) - executionTip := randomUint64BigInt(t) - deliveryTip := randomUint64BigInt(t) + summitTip := big.NewInt(int64(gofakeit.Uint32())) + attestationTip := big.NewInt(int64(gofakeit.Uint32())) + executionTip := big.NewInt(int64(gofakeit.Uint32())) + deliveryTip := big.NewInt(int64(gofakeit.Uint32())) solidityFormattedTips, err := handle.EncodeTips(&bind.CallOpts{Context: ctx}, summitTip.Uint64(), attestationTip.Uint64(), executionTip.Uint64(), deliveryTip.Uint64()) @@ -43,7 +45,7 @@ func TestEncodeTipsParity(t *testing.T) { goTips, err := types.EncodeTips(types.NewTips(summitTip, attestationTip, executionTip, deliveryTip)) Nil(t, err) - Equal(t, solidityFormattedTips.Bytes(), goTips) + Equal(t, solidityFormattedTips, new(big.Int).SetBytes(goTips), testsuite.BigIntComparer()) decodedTips, err := types.DecodeTips(goTips) Nil(t, err) @@ -52,6 +54,15 @@ func TestEncodeTipsParity(t *testing.T) { Equal(t, decodedTips.AttestationTip(), attestationTip) Equal(t, decodedTips.ExecutionTip(), executionTip) Equal(t, decodedTips.DeliveryTip(), deliveryTip) + + // Check the conversion into a big.Int + goTipsBigInt, err := types.EncodeTipsBigInt(types.NewTips(summitTip, attestationTip, executionTip, deliveryTip)) + Nil(t, err) + + solidityTipsBigInt, err := handle.EncodeTips(&bind.CallOpts{Context: ctx}, summitTip.Uint64(), attestationTip.Uint64(), executionTip.Uint64(), deliveryTip.Uint64()) + Nil(t, err) + + Equal(t, goTipsBigInt.Bytes(), solidityTipsBigInt.Bytes()) } func randomUint40BigInt(tb testing.TB) *big.Int { @@ -163,7 +174,7 @@ func TestEncodeStateParity(t *testing.T) { gasData := types.NewGasData(gasPrice, dataPrice, execBuffer, amortAttCost, etherPrice, markup) - goFormattedData, err := types.EncodeState(types.NewState(rootB32, origin, nonce, blockNumber, timestamp, gasData)) + goFormattedData, err := types.NewState(rootB32, origin, nonce, blockNumber, timestamp, gasData).Encode() Nil(t, err) Equal(t, contractData, goFormattedData) @@ -182,6 +193,47 @@ func TestEncodeStateParity(t *testing.T) { Equal(t, markup, stateFromBytes.GasData().Markup()) } +func TestEncodeReceiptParity(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + testBackend := simulated.NewSimulatedBackend(ctx, t) + deployManager := testutil.NewDeployManager(t) + + _, receiptHarness := deployManager.GetReceiptHarness(ctx, testBackend) + + origin := gofakeit.Uint32() + destination := gofakeit.Uint32() + messageHash := common.BigToHash(big.NewInt(gofakeit.Int64())) + snapshotRoot := common.BigToHash(big.NewInt(gofakeit.Int64())) + stateIndex := gofakeit.Uint8() + attNotary := common.BigToAddress(big.NewInt(gofakeit.Int64())) + firstExecutor := common.BigToAddress(big.NewInt(gofakeit.Int64())) + finalExecutor := common.BigToAddress(big.NewInt(gofakeit.Int64())) + + receipt := types.NewReceipt(origin, destination, messageHash, snapshotRoot, stateIndex, attNotary, firstExecutor, finalExecutor) + + encodedReceipt, err := receipt.Encode() + Nil(t, err) + + solEncodedReceipt, err := receiptHarness.FormatReceipt(&bind.CallOpts{Context: ctx}, origin, destination, messageHash, snapshotRoot, stateIndex, attNotary, firstExecutor, finalExecutor) + Nil(t, err) + + Equal(t, encodedReceipt, solEncodedReceipt) + + decodedReceipt, err := types.DecodeReceipt(encodedReceipt) + Nil(t, err) + + Equal(t, receipt.Origin(), decodedReceipt.Origin()) + Equal(t, receipt.Destination(), decodedReceipt.Destination()) + Equal(t, receipt.MessageHash(), decodedReceipt.MessageHash()) + Equal(t, receipt.SnapshotRoot(), decodedReceipt.SnapshotRoot()) + Equal(t, receipt.StateIndex(), decodedReceipt.StateIndex()) + Equal(t, receipt.AttestationNotary(), decodedReceipt.AttestationNotary()) + Equal(t, receipt.FirstExecutor(), decodedReceipt.FirstExecutor()) + Equal(t, receipt.FinalExecutor(), decodedReceipt.FinalExecutor()) +} + func TestEncodeSnapshotParity(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() @@ -222,17 +274,17 @@ func TestEncodeSnapshotParity(t *testing.T) { stateB := types.NewState(rootB, originB, nonceB, blockNumberB, timestampB, gasDataB) var statesAB [][]byte - stateABytes, err := types.EncodeState(stateA) + stateABytes, err := stateA.Encode() Nil(t, err) statesAB = append(statesAB, stateABytes) - stateBBytes, err := types.EncodeState(stateB) + stateBBytes, err := stateB.Encode() Nil(t, err) statesAB = append(statesAB, stateBBytes) contractData, err := snapshotContract.FormatSnapshot(&bind.CallOpts{Context: ctx}, statesAB) Nil(t, err) - goFormattedData, err := types.EncodeSnapshot(types.NewSnapshot([]types.State{stateA, stateB})) + goFormattedData, err := types.NewSnapshot([]types.State{stateA, stateB}).Encode() Nil(t, err) Equal(t, contractData, goFormattedData) @@ -313,7 +365,7 @@ func TestEncodeAttestationParity(t *testing.T) { contractData, err := attestationContract.FormatAttestation(&bind.CallOpts{Context: ctx}, rootB32, dataHashB32, nonce, blockNumber, timestamp) Nil(t, err) - goFormattedData, err := types.EncodeAttestation(types.NewAttestation(rootB32, dataHashB32, nonce, blockNumber, timestamp)) + goFormattedData, err := types.NewAttestation(rootB32, dataHashB32, nonce, blockNumber, timestamp).Encode() Nil(t, err) Equal(t, contractData, goFormattedData) @@ -325,6 +377,34 @@ func TestEncodeAttestationParity(t *testing.T) { Equal(t, nonce, attestationFromBytes.Nonce()) Equal(t, blockNumber, attestationFromBytes.BlockNumber()) Equal(t, timestamp, attestationFromBytes.Timestamp()) + + // Testing data hash. + gasData := types.NewGasData(gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16(), gofakeit.Uint16()) + + gasDataBytes, err := types.EncodeGasData(gasData) + Nil(t, err) + + gasDataHash := crypto.Keccak256Hash(gasDataBytes) + + var agentRootB32, gasDataHashB32 [32]byte + copy(agentRootB32[:], agentRoot[:]) + copy(gasDataHashB32[:], gasDataHash[:]) + + attestationDataHash := types.GetAttestationDataHash(agentRootB32, gasDataHashB32) + attestation := types.NewAttestation([32]byte{1}, attestationDataHash, 1, big.NewInt(1), big.NewInt(1)) + + contractDataHashFromVals, err := attestationContract.DataHash(&bind.CallOpts{Context: ctx}, agentRootB32, gasDataHashB32) + Nil(t, err) + + Equal(t, contractDataHashFromVals, attestationDataHash) + + encodedDataHashAttestation, err := attestation.Encode() + Nil(t, err) + + contractDataHashFromAtt, err := attestationContract.DataHash0(&bind.CallOpts{Context: ctx}, encodedDataHashAttestation) + Nil(t, err) + + Equal(t, contractDataHashFromAtt, attestationDataHash) } func TestBaseMessageEncodeParity(t *testing.T) { @@ -479,4 +559,12 @@ func TestHeaderEncodeParity(t *testing.T) { Nil(t, err) Equal(t, goHeader, solHeader.Bytes()) + + goHeaderHash, err := types.NewHeader(flag, origin, nonce, destination, optimisticSeconds).Leaf() + Nil(t, err) + + solHeaderHash, err := headerHarnessContract.Leaf(&bind.CallOpts{Context: ctx}, solHeader) + Nil(t, err) + + Equal(t, goHeaderHash, solHeaderHash) } diff --git a/agents/types/receipt.go b/agents/types/receipt.go new file mode 100644 index 0000000000..60a9080345 --- /dev/null +++ b/agents/types/receipt.go @@ -0,0 +1,110 @@ +package types + +import ( + "context" + + "github.com/ethereum/go-ethereum/common" + "github.com/synapsecns/sanguine/ethergo/signer/signer" +) + +const ( + receiptOffsetOrigin = 0 + receiptOffsetDestination = 4 + receiptOffsetMessageHash = 8 + receiptOffsetSnapshotRoot = 40 + receiptOffsetStateIndex = 72 + receiptOffsetAttNotary = 73 + receiptOffsetFirstExecutor = 93 + receiptOffsetFinalExecutor = 113 + receiptSize = 133 +) + +// Receipt is the receipt interface. +type Receipt interface { + Encoder + // Origin is the origin of the receipt. + Origin() uint32 + // Destination is the destination of the receipt. + Destination() uint32 + // MessageHash is the hash of the message. + MessageHash() [32]byte + // SnapshotRoot is the root of the Snapshot Merkle Tree. + SnapshotRoot() [32]byte + // StateIndex is the state index of the receipt. + StateIndex() uint8 + // AttestationNotary is the notary of the attestation. + AttestationNotary() common.Address + // FirstExecutor is the first executor of the receipt. + FirstExecutor() common.Address + // FinalExecutor is the final executor of the receipt. + FinalExecutor() common.Address + // SignReceipt returns the signature of the receipt payload signed by the signer. + SignReceipt(ctx context.Context, signer signer.Signer, valid bool) (signer.Signature, []byte, common.Hash, error) +} + +type receipt struct { + origin uint32 + destination uint32 + messageHash [32]byte + snapshotRoot [32]byte + stateIndex uint8 + attestationNotary common.Address + firstExecutor common.Address + finalExecutor common.Address +} + +// NewReceipt creates a new receipt. +func NewReceipt(origin uint32, destination uint32, messageHash [32]byte, snapshotRoot [32]byte, stateIndex uint8, attestationNotary common.Address, firstExecutor common.Address, finalExecutor common.Address) Receipt { + return &receipt{ + origin: origin, + destination: destination, + messageHash: messageHash, + snapshotRoot: snapshotRoot, + stateIndex: stateIndex, + attestationNotary: attestationNotary, + firstExecutor: firstExecutor, + finalExecutor: finalExecutor, + } +} + +func (r receipt) Origin() uint32 { + return r.origin +} + +func (r receipt) Destination() uint32 { + return r.destination +} + +func (r receipt) MessageHash() [32]byte { + return r.messageHash +} + +func (r receipt) SnapshotRoot() [32]byte { + return r.snapshotRoot +} + +func (r receipt) StateIndex() uint8 { + return r.stateIndex +} + +func (r receipt) AttestationNotary() common.Address { + return r.attestationNotary +} + +func (r receipt) FirstExecutor() common.Address { + return r.firstExecutor +} + +func (r receipt) FinalExecutor() common.Address { + return r.finalExecutor +} + +func (r receipt) SignReceipt(ctx context.Context, signer signer.Signer, valid bool) (signer.Signature, []byte, common.Hash, error) { + var receiptSalt string + if valid { + receiptSalt = ReceiptValidSalt + } else { + receiptSalt = ReceiptInvalidSalt + } + return signEncoder(ctx, signer, r, receiptSalt) +} diff --git a/agents/types/salt.go b/agents/types/salt.go new file mode 100644 index 0000000000..4be9c7009a --- /dev/null +++ b/agents/types/salt.go @@ -0,0 +1,21 @@ +package types + +// Note: these salt values are taken from contracts/libs/Constants.sol. + +// AttestationValidSalt is the salt for ATTESTATION_VALID_SALT. +const AttestationValidSalt = "ATTESTATION_VALID_SALT" + +// AttestationInvalidSalt is the salt for ATTESTATION_INVALID_SALT. +const AttestationInvalidSalt = "ATTESTATION_INVALID_SALT" + +// ReceiptValidSalt is the salt for RECEIPT_VALID_SALT. +const ReceiptValidSalt = "RECEIPT_VALID_SALT" + +// ReceiptInvalidSalt is the salt for RECEIPT_INVALID_SALT. +const ReceiptInvalidSalt = "RECEIPT_INVALID_SALT" + +// SnapshotValidSalt is the salt for SNAPSHOT_VALID_SALT. +const SnapshotValidSalt = "SNAPSHOT_VALID_SALT" + +// StateInvalidSalt is the salt for STATE_INVALID_SALT. +const StateInvalidSalt = "STATE_INVALID_SALT" diff --git a/agents/types/snapshot.go b/agents/types/snapshot.go index 612be277cb..7665b88af5 100644 --- a/agents/types/snapshot.go +++ b/agents/types/snapshot.go @@ -3,17 +3,16 @@ package types import ( "context" "fmt" - "github.com/ethereum/go-ethereum/crypto" "math" "github.com/ethereum/go-ethereum/common" - "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/merkle" "github.com/synapsecns/sanguine/ethergo/signer/signer" ) // Snapshot is the snapshot interface. type Snapshot interface { + Encoder // States are the states of the snapshot. States() []State @@ -76,26 +75,7 @@ func (s snapshot) TreeHeight() uint32 { } func (s snapshot) SignSnapshot(ctx context.Context, signer signer.Signer) (signer.Signature, []byte, common.Hash, error) { - encodedSnapshot, err := EncodeSnapshot(s) - if err != nil { - return nil, nil, common.Hash{}, fmt.Errorf("could not encode snapshot: %w", err) - } - - snapshotSalt := crypto.Keccak256Hash([]byte("SNAPSHOT_VALID_SALT")) - - hashedEncodedSnapshot := crypto.Keccak256Hash(encodedSnapshot).Bytes() - toSign := append(snapshotSalt.Bytes(), hashedEncodedSnapshot...) - - hashedSnapshot, err := HashRawBytes(toSign) - if err != nil { - return nil, nil, common.Hash{}, fmt.Errorf("could not hash snapshot: %w", err) - } - - signature, err := signer.SignMessage(ctx, core.BytesToSlice(hashedSnapshot), false) - if err != nil { - return nil, nil, common.Hash{}, fmt.Errorf("could not sign snapshot: %w", err) - } - return signature, encodedSnapshot, hashedSnapshot, nil + return signEncoder(ctx, signer, s, SnapshotValidSalt) } var _ Snapshot = &snapshot{} diff --git a/agents/types/snapshot_test.go b/agents/types/snapshot_test.go index 368f64bbfd..974b9ad2e8 100644 --- a/agents/types/snapshot_test.go +++ b/agents/types/snapshot_test.go @@ -2,6 +2,10 @@ package types_test import ( "context" + "math/big" + "testing" + "time" + "github.com/brianvoe/gofakeit/v6" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -9,9 +13,6 @@ import ( "github.com/synapsecns/sanguine/agents/testutil" "github.com/synapsecns/sanguine/agents/types" "github.com/synapsecns/sanguine/ethergo/backends/simulated" - "math/big" - "testing" - "time" ) func TestSnapshotRootAndProofs(t *testing.T) { @@ -60,7 +61,7 @@ func TestSnapshotRootAndProofs(t *testing.T) { snapshotRoot, _, err := snapshot.SnapshotRootAndProofs() Nil(t, err) - encodedSnapshot, err := types.EncodeSnapshot(snapshot) + encodedSnapshot, err := snapshot.Encode() Nil(t, err) snapshotContractStatesAmount, err := snapshotContract.StatesAmount(&bind.CallOpts{Context: ctx}, encodedSnapshot) @@ -71,7 +72,7 @@ func TestSnapshotRootAndProofs(t *testing.T) { snapshotContractStateA, err := snapshotContract.State(&bind.CallOpts{Context: ctx}, encodedSnapshot, big.NewInt(0)) Nil(t, err) - stateABytes, err := types.EncodeState(stateA) + stateABytes, err := stateA.Encode() Nil(t, err) Equal(t, stateABytes, snapshotContractStateA) @@ -79,7 +80,7 @@ func TestSnapshotRootAndProofs(t *testing.T) { snapshotContractStateB, err := snapshotContract.State(&bind.CallOpts{Context: ctx}, encodedSnapshot, big.NewInt(1)) Nil(t, err) - stateBBytes, err := types.EncodeState(stateB) + stateBBytes, err := stateB.Encode() Nil(t, err) Equal(t, stateBBytes, snapshotContractStateB) diff --git a/agents/types/state.go b/agents/types/state.go index d9746d64fd..d699255388 100644 --- a/agents/types/state.go +++ b/agents/types/state.go @@ -1,8 +1,12 @@ package types import ( - "github.com/ethereum/go-ethereum/crypto" + "context" "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/synapsecns/sanguine/ethergo/signer/signer" ) const ( @@ -17,6 +21,7 @@ const ( // State is the state interface. type State interface { + Encoder // Root is the root of the Origin Merkle Tree. Root() [32]byte // Origin is the domain where Origin is located. @@ -34,6 +39,8 @@ type State interface { Hash() ([32]byte, error) // SubLeaves returns the left and right sub-leaves of the state. SubLeaves() (leftLeaf, rightLeaf [32]byte, err error) + // SignState returns the signature of the state payload signed by the signer. + SignState(ctx context.Context, signer signer.Signer) (signer.Signature, []byte, common.Hash, error) } type state struct { @@ -93,7 +100,7 @@ func (s state) Hash() ([32]byte, error) { } func (s state) SubLeaves() (leftLeaf, rightLeaf [32]byte, err error) { - encodedState, err := EncodeState(s) + encodedState, err := s.Encode() if err != nil { return } @@ -103,4 +110,8 @@ func (s state) SubLeaves() (leftLeaf, rightLeaf [32]byte, err error) { return } +func (s state) SignState(ctx context.Context, signer signer.Signer) (signer.Signature, []byte, common.Hash, error) { + return signEncoder(ctx, signer, s, StateInvalidSalt) +} + var _ State = state{} diff --git a/agents/types/tips.go b/agents/types/tips.go index 75ec188341..1b0c0625ca 100644 --- a/agents/types/tips.go +++ b/agents/types/tips.go @@ -8,6 +8,12 @@ import ( const ( // TipsSize is the size of the tips in bytes. TipsSize = 8 * 4 + // ShiftSummitTip is the shift for the summit tip. + ShiftSummitTip = 24 * 8 + // ShiftAttestationTip is the shift for the attestation tip. + ShiftAttestationTip = 16 * 8 + // ShiftExecutionTip is the shift for the execution tip. + ShiftExecutionTip = 8 * 8 ) // Tips contain tips used for scientizing different agents. diff --git a/agents/types/utils.go b/agents/types/utils.go new file mode 100644 index 0000000000..0c789f48b6 --- /dev/null +++ b/agents/types/utils.go @@ -0,0 +1,35 @@ +package types + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/synapsecns/sanguine/core" + "github.com/synapsecns/sanguine/ethergo/signer/signer" +) + +// SignEncoder encodes a type, and signs it with the given salt. +func signEncoder(ctx context.Context, signer signer.Signer, encoder Encoder, salt string) (signer.Signature, []byte, common.Hash, error) { + // Encode the given type. + encoded, err := encoder.Encode() + if err != nil { + return nil, nil, common.Hash{}, fmt.Errorf("could not encode: %w", err) + } + + // Hash the encoded type, and concatenate with hashed salt. + hashedEncoded := crypto.Keccak256Hash(encoded).Bytes() + toSign := append(crypto.Keccak256Hash([]byte(salt)).Bytes(), hashedEncoded...) + hashedDigest, err := HashRawBytes(toSign) + if err != nil { + return nil, nil, common.Hash{}, fmt.Errorf("could not hash: %w", err) + } + + // Sign the message. + signature, err := signer.SignMessage(ctx, core.BytesToSlice(hashedDigest), false) + if err != nil { + return nil, nil, common.Hash{}, fmt.Errorf("could not sign: %w", err) + } + return signature, encoded, hashedDigest, nil +} diff --git a/core/dbcommon/consistentname_test.go b/core/dbcommon/consistentname_test.go index 0ca822edda..3e587eaf5d 100644 --- a/core/dbcommon/consistentname_test.go +++ b/core/dbcommon/consistentname_test.go @@ -17,20 +17,20 @@ import ( // of the column as such. E.g.: column:origin_chain_id. type ExplicitColumnTestModel struct { gorm.Model - TestField uint64 `gorm:"column:test_field" ,json:"not_test_field"` + TestField uint64 `,json:"not_test_field" gorm:"column:test_field"` } // ExplicitColumnTestModel tests implicit naming // of the column as such. E.g.: origin_chain_id. type ImplicitColumnTestModel struct { gorm.Model - TestField uint64 `gorm:"test_field" ,json:"not_test_field"` + TestField uint64 `,json:"not_test_field" gorm:"test_field"` } // MultiColumnModel tests multiple tags in gorm. type MultiColumnModel struct { gorm.Model - TestField uint64 `gorm:"column:test_field;uniqueIndex:idx_id" ,json:"not_test_field"` + TestField uint64 `,json:"not_test_field" gorm:"column:test_field;uniqueIndex:idx_id"` } // TestGetGormFieldName tests getting the gorm field by name. @@ -153,12 +153,12 @@ func (t testReplacer) Replace(name string) string { // NonConsistentModelName tests a model with a non-consistent name. type NonConsistentModelName struct { gorm.Model - TestField uint64 `gorm:"column:test_field" ,json:"not_test_field"` + TestField uint64 `,json:"not_test_field" gorm:"column:test_field"` } type OtherNonConsistentModelName struct { gorm.Model - TestField uint64 `gorm:"column:test_field_with_different_name" ,json:"not_test_field"` + TestField uint64 `,json:"not_test_field" gorm:"column:test_field_with_different_name"` } func TestNonConsistentName(t *testing.T) { diff --git a/core/dockerutil/port_util.go b/core/dockerutil/port_util.go new file mode 100644 index 0000000000..b771f59ea8 --- /dev/null +++ b/core/dockerutil/port_util.go @@ -0,0 +1,23 @@ +package dockerutil + +import ( + "github.com/ory/dockertest/v3" + dc "github.com/ory/dockertest/v3/docker" +) + +// GetPort returns the port of a container. +// We intentionally get the last port due to quirky behaviuor of docker-for-mac, +// which does not enforce uniqueness on 0.0.0.0:xxx ports. Not entirely sure why +// docker-for-mac behaves this way. +func GetPort(resource *dockertest.Resource, port string) string { + if resource.Container == nil || resource.Container.NetworkSettings == nil { + return "" + } + + m, ok := resource.Container.NetworkSettings.Ports[dc.Port(port)] + if !ok || len(m) == 0 { + return "" + } + + return m[len(m)-1].HostPort +} diff --git a/core/metrics/localmetrics/jaeger.go b/core/metrics/localmetrics/jaeger.go index 4665d0abf1..42eb1b044b 100644 --- a/core/metrics/localmetrics/jaeger.go +++ b/core/metrics/localmetrics/jaeger.go @@ -3,6 +3,9 @@ package localmetrics import ( "context" "fmt" + "os" + "time" + "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" "github.com/stretchr/testify/assert" @@ -11,8 +14,6 @@ import ( "github.com/synapsecns/sanguine/core/metrics/internal" "github.com/synapsecns/sanguine/core/processlog" "github.com/synapsecns/sanguine/core/retry" - "os" - "time" ) // StartJaegerServer starts a new jaeger instance. @@ -46,9 +47,9 @@ func (j *testJaeger) StartJaegerServer(ctx context.Context) *uiResource { }) assert.Nil(j.tb, err) - j.tb.Setenv(internal.JaegerEndpoint, fmt.Sprintf("http://localhost:%s/api/traces", resource.GetPort("14268/tcp"))) + j.tb.Setenv(internal.JaegerEndpoint, fmt.Sprintf("http://localhost:%s/api/traces", dockerutil.GetPort(resource, "14268/tcp"))) // uiEndpoint is the jaeger endpoint, we want to instead use the pyroscope endpoint - uiEndpoint := fmt.Sprintf("http://localhost:%s", resource.GetPort("16686/tcp")) + uiEndpoint := fmt.Sprintf("http://localhost:%s", dockerutil.GetPort(resource, "16686/tcp")) if !j.cfg.keepContainers { err = resource.Expire(uint(keepAliveOnFailure.Seconds())) @@ -123,7 +124,7 @@ func (j *testJaeger) StartJaegerPyroscopeUI(ctx context.Context) *uiResource { assert.Nil(j.tb, err) // must only be done after the container is started - j.tb.Setenv(internal.JaegerUIEndpoint, fmt.Sprintf("http://localhost:%s", resource.GetPort("80/tcp"))) + j.tb.Setenv(internal.JaegerUIEndpoint, fmt.Sprintf("http://localhost:%s", dockerutil.GetPort(resource, "80/tcp"))) if !j.cfg.keepContainers { err = resource.Expire(uint(keepAliveOnFailure.Seconds())) diff --git a/core/metrics/localmetrics/pyroscope.go b/core/metrics/localmetrics/pyroscope.go index ed36cad02f..ce3243ed79 100644 --- a/core/metrics/localmetrics/pyroscope.go +++ b/core/metrics/localmetrics/pyroscope.go @@ -2,11 +2,15 @@ package localmetrics import ( "context" + "github.com/Flaque/filet" // embeds the pyroscope config file. _ "embed" "fmt" + "os" + "time" + "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" "github.com/ory/dockertest/v3/docker/types/mount" @@ -16,8 +20,6 @@ import ( "github.com/synapsecns/sanguine/core/metrics/internal" "github.com/synapsecns/sanguine/core/processlog" "github.com/synapsecns/sanguine/core/retry" - "os" - "time" ) //go:embed pyroscope.yaml @@ -82,7 +84,7 @@ func (j *testJaeger) StartPyroscopeServer(ctx context.Context) *uiResource { }) assert.Nil(j.tb, err) - j.tb.Setenv(internal.PyroscopeEndpoint, fmt.Sprintf("http://localhost:%s", resource.GetPort("4040/tcp"))) + j.tb.Setenv(internal.PyroscopeEndpoint, fmt.Sprintf("http://localhost:%s", dockerutil.GetPort(resource, "4040/tcp"))) if !j.cfg.keepContainers { err = resource.Expire(uint(keepAliveOnFailure.Seconds())) diff --git a/ethergo/backends/anvil/anvil.go b/ethergo/backends/anvil/anvil.go index cd3fe3d4fc..dbbbb65a3c 100644 --- a/ethergo/backends/anvil/anvil.go +++ b/ethergo/backends/anvil/anvil.go @@ -4,6 +4,13 @@ import ( "context" "errors" "fmt" + "math" + "math/big" + "os" + "strings" + "sync" + "testing" + "github.com/Flaque/filet" "github.com/brianvoe/gofakeit/v6" "github.com/ethereum/go-ethereum/accounts" @@ -28,12 +35,6 @@ import ( "github.com/synapsecns/sanguine/ethergo/chain/client" "github.com/synapsecns/sanguine/ethergo/signer/wallet" "github.com/teivah/onecontext" - "math" - "math/big" - "os" - "strings" - "sync" - "testing" ) const gasLimit = 10000000 @@ -121,7 +122,7 @@ func NewAnvilBackend(ctx context.Context, t *testing.T, args *OptionBuilder) *Ba // to prevent old containers from piling up, we set a timeout to remove the container. require.Nil(t, resource.Expire(args.expirySeconds)) - address := fmt.Sprintf("%s:%s", "http://localhost", resource.GetPort("8545/tcp")) + address := fmt.Sprintf("%s:%s", "http://localhost", dockerutil.GetPort(resource, "8545/tcp")) var chainID *big.Int if err := pool.Retry(func() error { @@ -177,7 +178,8 @@ func NewAnvilBackend(ctx context.Context, t *testing.T, args *OptionBuilder) *Ba t.Cleanup(func() { select { case <-ctx.Done(): - _ = pool.Purge(resource) + err = pool.Purge(resource) + logger.Errorf("error purging anvil container: %w", err) default: // do nothing, we don't want to purge the container if this is just a subtest } @@ -193,7 +195,7 @@ func setupOtterscan(ctx context.Context, tb testing.TB, pool *dockertest.Pool, a Repository: "otterscan/otterscan", Tag: "latest", Env: []string{ - fmt.Sprintf("ERIGON_URL=http://localhost:%s", anvilResource.GetPort("8545/tcp")), + fmt.Sprintf("ERIGON_URL=http://localhost:%s", dockerutil.GetPort(anvilResource, "8545/tcp")), }, Labels: map[string]string{ "test-id": uuid.New().String(), @@ -240,7 +242,7 @@ func setupOtterscan(ctx context.Context, tb testing.TB, pool *dockertest.Pool, a case logInfo := <-logInfoChan: // debug level stuff logger.Debugf("started otterscan for anvil instance %s as container %s. Logs will be stored at %s", anvilResource.Container.Name, strings.TrimPrefix(resource.Container.Name, "/"), logInfo.LogDir()) - return fmt.Sprintf("http://localhost:%s", resource.GetPort("80/tcp")) + return fmt.Sprintf("http://localhost:%s", dockerutil.GetPort(resource, "80/tcp")) } return "" } diff --git a/ethergo/backends/anvil/suite_test.go b/ethergo/backends/anvil/suite_test.go index 4fd3b2b5fe..11ba05f869 100644 --- a/ethergo/backends/anvil/suite_test.go +++ b/ethergo/backends/anvil/suite_test.go @@ -1,6 +1,8 @@ package anvil_test import ( + "testing" + "github.com/ethereum/go-ethereum/common" . "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -10,7 +12,6 @@ import ( "github.com/synapsecns/sanguine/ethergo/example" "github.com/synapsecns/sanguine/ethergo/example/counter" "github.com/synapsecns/sanguine/ethergo/manager" - "testing" ) var vitalik = common.HexToAddress("0xd8da6bf26964af9d7eed9e03e53415d37aa96045") @@ -35,7 +36,7 @@ func NewAnvilSuite(tb testing.TB) *AnvilSuite { func (a *AnvilSuite) SetupSuite() { a.TestSuite.SetupSuite() - a.forkAddress = core.GetEnv("ETHEREUM_RPC_URI", "https://rpc.ankr.com/eth") + a.forkAddress = core.GetEnv("ETHEREUM_RPC_URI", "https://1rpc.io/eth") options := anvil.NewAnvilOptionBuilder() err := options.SetForkURL(a.forkAddress) Nil(a.T(), err) diff --git a/ethergo/backends/base/base.go b/ethergo/backends/base/base.go index c72cb122ab..fc3ca85857 100644 --- a/ethergo/backends/base/base.go +++ b/ethergo/backends/base/base.go @@ -4,8 +4,6 @@ import ( "bytes" "context" "fmt" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" "math/big" "os" "sync" @@ -16,12 +14,15 @@ import ( "github.com/brianvoe/gofakeit/v6" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/hashicorp/go-multierror" "github.com/ipfs/go-log" "github.com/pkg/errors" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/synapsecns/sanguine/ethergo/chain" "github.com/synapsecns/sanguine/ethergo/chain/client" "github.com/synapsecns/sanguine/ethergo/contracts" @@ -150,8 +151,8 @@ func (b *Backend) VerifyContract(contractType contracts.ContractType, contract c go func() { code, err := b.Client().CodeAt(b.ctx, contract.Address(), nil) if !errors.Is(err, context.Canceled) { - assert.Nil(b.T(), err) - assert.NotEmpty(b.T(), code) + require.Nil(b.T(), err) + require.NotEmpty(b.T(), code, "contract of type %s (metadata %s) not found", contractType.ContractName(), contract.String()) } }() var errMux sync.Mutex diff --git a/ethergo/backends/mocks/simulated_test_backend.go b/ethergo/backends/mocks/simulated_test_backend.go index f67c99ddd3..add8fd4f68 100644 --- a/ethergo/backends/mocks/simulated_test_backend.go +++ b/ethergo/backends/mocks/simulated_test_backend.go @@ -240,6 +240,22 @@ func (_m *SimulatedTestBackend) CallContract(ctx context.Context, call ethereum. return r0, r1 } +// ChainConfig provides a mock function with given fields: +func (_m *SimulatedTestBackend) ChainConfig() *params.ChainConfig { + ret := _m.Called() + + var r0 *params.ChainConfig + if rf, ok := ret.Get(0).(func() *params.ChainConfig); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*params.ChainConfig) + } + } + + return r0 +} + // ChainID provides a mock function with given fields: ctx func (_m *SimulatedTestBackend) ChainID(ctx context.Context) (*big.Int, error) { ret := _m.Called(ctx) diff --git a/ethergo/chain/chain.go b/ethergo/chain/chain.go index e447c9da44..cdd48ae474 100644 --- a/ethergo/chain/chain.go +++ b/ethergo/chain/chain.go @@ -39,6 +39,8 @@ type Chain interface { Estimator() gas.PriceEstimator // GasSetter gets the gas setter GasSetter() gas.Setter + // ChainConfig gets the chain config. + ChainConfig() *params.ChainConfig // SetChainConfig sets the config for a chain SetChainConfig(config *params.ChainConfig) // HeaderByTime gets the closest block to the given time. diff --git a/ethergo/chain/mocks/chain.go b/ethergo/chain/mocks/chain.go index 98e9a8fff8..fcaac507ba 100644 --- a/ethergo/chain/mocks/chain.go +++ b/ethergo/chain/mocks/chain.go @@ -212,6 +212,22 @@ func (_m *Chain) CallContract(ctx context.Context, call ethereum.CallMsg, blockN return r0, r1 } +// ChainConfig provides a mock function with given fields: +func (_m *Chain) ChainConfig() *params.ChainConfig { + ret := _m.Called() + + var r0 *params.ChainConfig + if rf, ok := ret.Get(0).(func() *params.ChainConfig); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*params.ChainConfig) + } + } + + return r0 +} + // ChainID provides a mock function with given fields: ctx func (_m *Chain) ChainID(ctx context.Context) (*big.Int, error) { ret := _m.Called(ctx) diff --git a/ethergo/contracts/deployed.go b/ethergo/contracts/deployed.go index ffc2a73243..3e1e103962 100644 --- a/ethergo/contracts/deployed.go +++ b/ethergo/contracts/deployed.go @@ -23,4 +23,6 @@ type DeployedContract interface { ChainID() *big.Int // OwnerPtr is a pointer to the owner OwnerPtr() *common.Address + // String returns the string representation of the contract + String() string } diff --git a/ethergo/contracts/mocks/deployed_contract.go b/ethergo/contracts/mocks/deployed_contract.go index c5f61fee56..31f49a877f 100644 --- a/ethergo/contracts/mocks/deployed_contract.go +++ b/ethergo/contracts/mocks/deployed_contract.go @@ -113,6 +113,20 @@ func (_m *DeployedContract) OwnerPtr() *common.Address { return r0 } +// String provides a mock function with given fields: +func (_m *DeployedContract) String() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + type mockConstructorTestingTNewDeployedContract interface { mock.TestingT Cleanup(func()) diff --git a/ethergo/deployer/deployed_contract.go b/ethergo/deployer/deployed_contract.go index be09128a82..599ffb3b2d 100644 --- a/ethergo/deployer/deployed_contract.go +++ b/ethergo/deployer/deployed_contract.go @@ -73,4 +73,9 @@ func (d DeployedContract) ChainID() *big.Int { return core.CopyBigInt(d.chainID) } +// String returns a string representation of the contract metadata. +func (d DeployedContract) String() string { + return fmt.Sprintf("address: %s, owner: %s, chainID: %s, deployTX: %s", d.address.String(), d.owner.String(), d.chainID.String(), d.deployTx.Hash()) +} + var _ contracts.DeployedContract = DeployedContract{} diff --git a/ethergo/submitter/suite_test.go b/ethergo/submitter/suite_test.go index c207ba43cb..d49f25ba8a 100644 --- a/ethergo/submitter/suite_test.go +++ b/ethergo/submitter/suite_test.go @@ -4,6 +4,12 @@ import ( "context" "database/sql" "fmt" + "math/big" + "os" + "sync" + "testing" + "time" + "github.com/Flaque/filet" "github.com/brianvoe/gofakeit/v6" "github.com/ethereum/go-ethereum/accounts/keystore" @@ -31,11 +37,6 @@ import ( "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/schema" - "math/big" - "os" - "sync" - "testing" - "time" "github.com/stretchr/testify/suite" "github.com/synapsecns/sanguine/core/testsuite" @@ -91,8 +92,16 @@ func (s *SubmitterSuite) SetupSuite() { go func() { defer wg.Done() var err error - localmetrics.SetupTestJaeger(s.GetSuiteContext(), s.T()) - s.metrics, err = metrics.NewByType(s.GetSuiteContext(), buildInfo, metrics.Jaeger) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(s.GetSuiteContext(), s.T()) + metricsHandler = metrics.Jaeger + } + s.metrics, err = metrics.NewByType(s.GetSuiteContext(), buildInfo, metricsHandler) s.Require().NoError(err) }() @@ -180,9 +189,17 @@ func TestTXSubmitterDBSuite(t *testing.T) { func (t *TXSubmitterDBSuite) SetupSuite() { t.TestSuite.SetupSuite() - localmetrics.SetupTestJaeger(t.GetSuiteContext(), t.T()) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(t.GetSuiteContext(), t.T()) + metricsHandler = metrics.Jaeger + } var err error - t.metrics, err = metrics.NewByType(t.GetSuiteContext(), buildInfo, metrics.Jaeger) + t.metrics, err = metrics.NewByType(t.GetSuiteContext(), buildInfo, metricsHandler) t.Require().NoError(err) } diff --git a/services/cctp-relayer/api/suite_test.go b/services/cctp-relayer/api/suite_test.go index e212ff4aef..15c2a1d07e 100644 --- a/services/cctp-relayer/api/suite_test.go +++ b/services/cctp-relayer/api/suite_test.go @@ -5,6 +5,7 @@ import ( "github.com/Flaque/filet" "github.com/stretchr/testify/suite" + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/core/testsuite" @@ -41,7 +42,16 @@ func (s *RelayerAPISuite) SetupTest() { // create the test metrics handler var err error - s.metricsHandler, err = metrics.NewByType(s.GetTestContext(), metadata.BuildInfo(), metrics.Jaeger) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(s.GetSuiteContext(), s.T()) + metricsHandler = metrics.Jaeger + } + s.metricsHandler, err = metrics.NewByType(s.GetTestContext(), metadata.BuildInfo(), metricsHandler) s.Require().NoError(err) // create the test store diff --git a/services/cctp-relayer/db/suite_test.go b/services/cctp-relayer/db/suite_test.go index de431752c6..a379448315 100644 --- a/services/cctp-relayer/db/suite_test.go +++ b/services/cctp-relayer/db/suite_test.go @@ -3,9 +3,14 @@ package db_test import ( dbSQL "database/sql" "fmt" + "os" + "sync" + "testing" + "github.com/Flaque/filet" . "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/dbcommon" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" @@ -15,9 +20,6 @@ import ( "github.com/synapsecns/sanguine/services/cctp-relayer/db/sql/mysql" "github.com/synapsecns/sanguine/services/cctp-relayer/metadata" "gorm.io/gorm/schema" - "os" - "sync" - "testing" ) type DBSuite struct { @@ -37,10 +39,18 @@ func NewDBSuite(tb testing.TB) *DBSuite { func (d *DBSuite) SetupSuite() { d.TestSuite.SetupSuite() - localmetrics.SetupTestJaeger(d.GetSuiteContext(), d.T()) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(d.GetSuiteContext(), d.T()) + metricsHandler = metrics.Jaeger + } var err error - d.metrics, err = metrics.NewByType(d.GetSuiteContext(), metadata.BuildInfo(), metrics.Jaeger) + d.metrics, err = metrics.NewByType(d.GetSuiteContext(), metadata.BuildInfo(), metricsHandler) Nil(d.T(), err) } diff --git a/services/cctp-relayer/relayer/suite_test.go b/services/cctp-relayer/relayer/suite_test.go index 49dc551884..94c28e0b80 100644 --- a/services/cctp-relayer/relayer/suite_test.go +++ b/services/cctp-relayer/relayer/suite_test.go @@ -3,13 +3,15 @@ package relayer_test import ( "fmt" - "github.com/synapsecns/sanguine/services/cctp-relayer/db/sql/base" - "github.com/synapsecns/sanguine/services/cctp-relayer/db/sql/sqlite" "math/big" "net/url" "strconv" "testing" + "github.com/synapsecns/sanguine/core" + "github.com/synapsecns/sanguine/services/cctp-relayer/db/sql/base" + "github.com/synapsecns/sanguine/services/cctp-relayer/db/sql/sqlite" + "github.com/Flaque/filet" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" @@ -164,7 +166,16 @@ func (s *CCTPRelayerSuite) SetupTest() { // create the test metrics handler var err error - s.metricsHandler, err = metrics.NewByType(s.GetTestContext(), metadata.BuildInfo(), metrics.Jaeger) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(s.GetSuiteContext(), s.T()) + metricsHandler = metrics.Jaeger + } + s.metricsHandler, err = metrics.NewByType(s.GetTestContext(), metadata.BuildInfo(), metricsHandler) s.Require().NoError(err) // create the test store diff --git a/services/explorer/api/suite_test.go b/services/explorer/api/suite_test.go index 9d0d9377ea..961061a678 100644 --- a/services/explorer/api/suite_test.go +++ b/services/explorer/api/suite_test.go @@ -11,6 +11,7 @@ import ( "github.com/phayes/freeport" . "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/core/retry" @@ -186,10 +187,18 @@ func NewTestSuite(tb testing.TB) *APISuite { func (g *APISuite) SetupSuite() { g.TestSuite.SetupSuite() - localmetrics.SetupTestJaeger(g.GetSuiteContext(), g.T()) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(g.GetSuiteContext(), g.T()) + metricsHandler = metrics.Jaeger + } var err error - g.scribeMetrics, err = metrics.NewByType(g.GetSuiteContext(), scribeMetadata.BuildInfo(), metrics.Jaeger) + g.scribeMetrics, err = metrics.NewByType(g.GetSuiteContext(), scribeMetadata.BuildInfo(), metricsHandler) g.Require().Nil(err) // TODO: there may be an issue w/ syncer for local test nevs, investigate, but this probably comes from heavy load ending every span of every field synchronously g.explorerMetrics, err = metrics.NewByType(g.GetSuiteContext(), metadata.BuildInfo(), metrics.Null) diff --git a/services/explorer/backfill/suite_test.go b/services/explorer/backfill/suite_test.go index bde9cc418d..f12a255ba5 100644 --- a/services/explorer/backfill/suite_test.go +++ b/services/explorer/backfill/suite_test.go @@ -2,10 +2,12 @@ package backfill_test import ( "fmt" + "github.com/brianvoe/gofakeit/v6" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/suite" + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/core/testsuite" @@ -21,9 +23,10 @@ import ( scribedb "github.com/synapsecns/sanguine/services/scribe/db" "github.com/synapsecns/sanguine/services/scribe/metadata" - "go.uber.org/atomic" "math/big" "testing" + + "go.uber.org/atomic" ) type BackfillSuite struct { @@ -52,10 +55,18 @@ func NewBackfillSuite(tb testing.TB) *BackfillSuite { func (b *BackfillSuite) SetupSuite() { b.TestSuite.SetupSuite() - localmetrics.SetupTestJaeger(b.GetSuiteContext(), b.T()) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(b.GetSuiteContext(), b.T()) + metricsHandler = metrics.Jaeger + } var err error - b.metrics, err = metrics.NewByType(b.GetSuiteContext(), metadata.BuildInfo(), metrics.Jaeger) + b.metrics, err = metrics.NewByType(b.GetSuiteContext(), metadata.BuildInfo(), metricsHandler) b.Require().Nil(err) } diff --git a/services/explorer/consumer/suite_test.go b/services/explorer/consumer/suite_test.go index 46276ea4ea..8552099582 100644 --- a/services/explorer/consumer/suite_test.go +++ b/services/explorer/consumer/suite_test.go @@ -2,11 +2,14 @@ package consumer_test import ( "fmt" + "github.com/brianvoe/gofakeit/v6" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/suite" + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" + "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/core/testsuite" "github.com/synapsecns/sanguine/ethergo/backends" "github.com/synapsecns/sanguine/ethergo/contracts" @@ -15,13 +18,14 @@ import ( "github.com/synapsecns/sanguine/services/explorer/contracts/bridgeconfig" "github.com/synapsecns/sanguine/services/explorer/db" + "math/big" + "testing" + "github.com/synapsecns/sanguine/services/explorer/testutil" "github.com/synapsecns/sanguine/services/explorer/testutil/testcontracts" scribedb "github.com/synapsecns/sanguine/services/scribe/db" "github.com/synapsecns/sanguine/services/scribe/metadata" "go.uber.org/atomic" - "math/big" - "testing" ) // ConsumerSuite is the config test suite. @@ -98,8 +102,17 @@ func (c *ConsumerSuite) SetupTest() { func (c *ConsumerSuite) SetupSuite() { c.TestSuite.SetupSuite() + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(c.GetSuiteContext(), c.T()) + metricsHandler = metrics.Jaeger + } var err error - c.scribeMetrics, err = metrics.NewByType(c.GetSuiteContext(), metadata.BuildInfo(), metrics.Jaeger) + c.scribeMetrics, err = metrics.NewByType(c.GetSuiteContext(), metadata.BuildInfo(), metricsHandler) c.Require().Nil(err) } diff --git a/services/explorer/db/suite_test.go b/services/explorer/db/suite_test.go index 5c623a095e..150b4da98d 100644 --- a/services/explorer/db/suite_test.go +++ b/services/explorer/db/suite_test.go @@ -1,7 +1,10 @@ package db_test import ( + "testing" + "github.com/stretchr/testify/suite" + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/core/testsuite" @@ -12,7 +15,6 @@ import ( scribedb "github.com/synapsecns/sanguine/services/scribe/db" "github.com/synapsecns/sanguine/services/scribe/metadata" "go.uber.org/atomic" - "testing" ) type DBSuite struct { @@ -38,10 +40,18 @@ func NewDBSuite(tb testing.TB) *DBSuite { func (t *DBSuite) SetupTest() { t.TestSuite.SetupTest() - localmetrics.SetupTestJaeger(t.GetSuiteContext(), t.T()) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(t.GetSuiteContext(), t.T()) + metricsHandler = metrics.Jaeger + } var err error - t.scribeMetrics, err = metrics.NewByType(t.GetSuiteContext(), metadata.BuildInfo(), metrics.Jaeger) + t.scribeMetrics, err = metrics.NewByType(t.GetSuiteContext(), metadata.BuildInfo(), metricsHandler) t.Require().Nil(err) t.db, t.eventDB, t.gqlClient, t.logIndex, t.cleanup, t.testBackend, t.deployManager = testutil.NewTestEnvDB(t.GetTestContext(), t.T(), t.scribeMetrics) } diff --git a/services/explorer/node/suite_test.go b/services/explorer/node/suite_test.go index 43eda0e610..e2df6f02e1 100644 --- a/services/explorer/node/suite_test.go +++ b/services/explorer/node/suite_test.go @@ -2,7 +2,11 @@ package node_test import ( "fmt" + "math/big" + "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/ethergo/backends" @@ -12,8 +16,6 @@ import ( "github.com/synapsecns/sanguine/services/explorer/testutil/testcontracts" scribedb "github.com/synapsecns/sanguine/services/scribe/db" scribeMetadata "github.com/synapsecns/sanguine/services/scribe/metadata" - "math/big" - "testing" "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/suite" @@ -86,11 +88,20 @@ func (c *NodeSuite) SetupTest() { func (c *NodeSuite) SetupSuite() { c.TestSuite.SetupSuite() - localmetrics.SetupTestJaeger(c.GetSuiteContext(), c.T()) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(c.GetSuiteContext(), c.T()) + metricsHandler = metrics.Jaeger + } + var err error - c.scribeMetrics, err = metrics.NewByType(c.GetSuiteContext(), scribeMetadata.BuildInfo(), metrics.Jaeger) + c.scribeMetrics, err = metrics.NewByType(c.GetSuiteContext(), scribeMetadata.BuildInfo(), metricsHandler) c.Require().Nil(err) - c.explorerMetrics, err = metrics.NewByType(c.GetSuiteContext(), metadata.BuildInfo(), metrics.Jaeger) + c.explorerMetrics, err = metrics.NewByType(c.GetSuiteContext(), metadata.BuildInfo(), metricsHandler) c.Require().Nil(err) } diff --git a/services/explorer/testutil/clickhouse/clickhouse.go b/services/explorer/testutil/clickhouse/clickhouse.go index 854feb46b7..0a0e036e45 100644 --- a/services/explorer/testutil/clickhouse/clickhouse.go +++ b/services/explorer/testutil/clickhouse/clickhouse.go @@ -3,14 +3,16 @@ package clickhouse import ( "database/sql" "fmt" - "github.com/ClickHouse/clickhouse-go/v2" - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" - "github.com/phayes/freeport" "net" "os" "strconv" "time" + + "github.com/ClickHouse/clickhouse-go/v2" + "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" + "github.com/phayes/freeport" + "github.com/synapsecns/sanguine/core/dockerutil" ) // NewClickhouseStore creates a new clickhouse db hosted at localhost:xxxx with ory/dockertest. @@ -56,7 +58,7 @@ func NewClickhouseStore(src string) (func(), *int, error) { }) // Fetch port assigned to container - address := fmt.Sprintf("%s:%s", "localhost", resource.GetPort("9000/tcp")) + address := fmt.Sprintf("%s:%s", "localhost", dockerutil.GetPort(resource, "9000/tcp")) // Docker will hard kill the container in 360 seconds (this is a test env). // In a continuous integration environment, this is increased to allow for the lower cpu count diff --git a/services/omnirpc/client/suite_test.go b/services/omnirpc/client/suite_test.go index 6e321b47c4..e8821a84e6 100644 --- a/services/omnirpc/client/suite_test.go +++ b/services/omnirpc/client/suite_test.go @@ -3,7 +3,13 @@ package client_test import ( "context" "fmt" + "math/big" + "net/url" + "sync" + "testing" + "github.com/stretchr/testify/suite" + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/core/testsuite" @@ -13,10 +19,6 @@ import ( "github.com/synapsecns/sanguine/services/omnirpc/metadata" "github.com/synapsecns/sanguine/services/omnirpc/testhelper" "golang.org/x/sync/errgroup" - "math/big" - "net/url" - "sync" - "testing" ) type TestClientSuite struct { @@ -66,10 +68,18 @@ func (s *TestClientSuite) SetupTest() { } func (s *TestClientSuite) SetupJaeger() { - localmetrics.SetupTestJaeger(s.GetSuiteContext(), s.T()) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(s.GetSuiteContext(), s.T()) + metricsHandler = metrics.Jaeger + } var err error - s.metrics, err = metrics.NewByType(s.GetSuiteContext(), metadata.BuildInfo(), metrics.Jaeger) + s.metrics, err = metrics.NewByType(s.GetSuiteContext(), metadata.BuildInfo(), metricsHandler) s.Require().Nil(err) } diff --git a/services/omnirpc/proxy/suite_test.go b/services/omnirpc/proxy/suite_test.go index 7aac3584f1..b43395bbcf 100644 --- a/services/omnirpc/proxy/suite_test.go +++ b/services/omnirpc/proxy/suite_test.go @@ -1,13 +1,15 @@ package proxy_test import ( + "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/core/testsuite" "github.com/synapsecns/sanguine/services/omnirpc/metadata" - "testing" ) type ProxySuite struct { @@ -26,10 +28,18 @@ func NewProxySuite(tb testing.TB) *ProxySuite { func (p *ProxySuite) SetupSuite() { p.TestSuite.SetupSuite() - localmetrics.SetupTestJaeger(p.GetSuiteContext(), p.T()) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(p.GetSuiteContext(), p.T()) + metricsHandler = metrics.Jaeger + } var err error - p.metrics, err = metrics.NewByType(p.GetSuiteContext(), metadata.BuildInfo(), metrics.Jaeger) + p.metrics, err = metrics.NewByType(p.GetSuiteContext(), metadata.BuildInfo(), metricsHandler) assert.Nil(p.T(), err) } diff --git a/services/omnirpc/testhelper/server.go b/services/omnirpc/testhelper/server.go index 3e1895f13f..4f60d19359 100644 --- a/services/omnirpc/testhelper/server.go +++ b/services/omnirpc/testhelper/server.go @@ -3,8 +3,12 @@ package testhelper import ( "context" "fmt" + "net/http" + "testing" + "github.com/phayes/freeport" "github.com/stretchr/testify/assert" + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/ginhelper" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" @@ -14,8 +18,6 @@ import ( omniHTTP "github.com/synapsecns/sanguine/services/omnirpc/http" "github.com/synapsecns/sanguine/services/omnirpc/metadata" "github.com/synapsecns/sanguine/services/omnirpc/proxy" - "net/http" - "testing" ) func makeConfig(backends []backends.SimulatedTestBackend, clientType omniHTTP.ClientType) config.Config { @@ -44,9 +46,17 @@ func makeConfig(backends []backends.SimulatedTestBackend, clientType omniHTTP.Cl func NewOmnirpcServer(ctx context.Context, tb testing.TB, backends ...backends.SimulatedTestBackend) string { tb.Helper() - localmetrics.SetupTestJaeger(ctx, tb) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(ctx, tb) + metricsHandler = metrics.Jaeger + } - handler, err := metrics.NewByType(ctx, metadata.BuildInfo(), metrics.Jaeger) + handler, err := metrics.NewByType(ctx, metadata.BuildInfo(), metricsHandler) assert.Nil(tb, err) server := proxy.NewProxy(makeConfig(backends, omniHTTP.FastHTTP), handler) diff --git a/services/scribe/api/suite_test.go b/services/scribe/api/suite_test.go index 1494b3297a..b910f5ad57 100644 --- a/services/scribe/api/suite_test.go +++ b/services/scribe/api/suite_test.go @@ -2,6 +2,11 @@ package api_test import ( "fmt" + "net/http" + "testing" + "time" + + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/services/scribe/api" @@ -10,9 +15,6 @@ import ( "github.com/synapsecns/sanguine/services/scribe/metadata" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - "net/http" - "testing" - "time" "github.com/Flaque/filet" "github.com/phayes/freeport" @@ -51,10 +53,18 @@ func NewTestSuite(tb testing.TB) *APISuite { func (g *APISuite) SetupSuite() { g.TestSuite.SetupSuite() - localmetrics.SetupTestJaeger(g.GetSuiteContext(), g.T()) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(g.GetSuiteContext(), g.T()) + metricsHandler = metrics.Jaeger + } var err error - g.metrics, err = metrics.NewByType(g.GetSuiteContext(), metadata.BuildInfo(), metrics.Jaeger) + g.metrics, err = metrics.NewByType(g.GetSuiteContext(), metadata.BuildInfo(), metricsHandler) g.Require().Nil(err) } diff --git a/services/scribe/backend/suite_test.go b/services/scribe/backend/suite_test.go index 3eb8503c04..df18ab8f9a 100644 --- a/services/scribe/backend/suite_test.go +++ b/services/scribe/backend/suite_test.go @@ -1,11 +1,13 @@ package backend_test import ( + "testing" + "time" + + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/services/scribe/metadata" - "testing" - "time" "github.com/Flaque/filet" . "github.com/stretchr/testify/assert" @@ -50,10 +52,19 @@ func (b *BackendSuite) SetupTest() { func (b *BackendSuite) SetupSuite() { b.TestSuite.SetupSuite() - localmetrics.SetupTestJaeger(b.GetSuiteContext(), b.T()) + + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(b.GetSuiteContext(), b.T()) + metricsHandler = metrics.Jaeger + } var err error - b.metrics, err = metrics.NewByType(b.GetSuiteContext(), metadata.BuildInfo(), metrics.Jaeger) + b.metrics, err = metrics.NewByType(b.GetSuiteContext(), metadata.BuildInfo(), metricsHandler) Nil(b.T(), err) } diff --git a/services/scribe/client/client_test.go b/services/scribe/client/client_test.go index abac091c81..dde48f4be0 100644 --- a/services/scribe/client/client_test.go +++ b/services/scribe/client/client_test.go @@ -2,9 +2,12 @@ package client_test import ( "fmt" + "testing" + "github.com/Flaque/filet" . "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/core/testsuite" @@ -13,7 +16,6 @@ import ( "github.com/synapsecns/sanguine/services/scribe/metadata" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - "testing" ) // ClientSuite defines the basic test suite. @@ -41,10 +43,19 @@ func (g *ClientSuite) SetupTest() { func (g *ClientSuite) SetupSuite() { g.TestSuite.SetupSuite() - localmetrics.SetupTestJaeger(g.GetSuiteContext(), g.T()) + + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(g.GetSuiteContext(), g.T()) + metricsHandler = metrics.Jaeger + } var err error - g.metrics, err = metrics.NewByType(g.GetSuiteContext(), metadata.BuildInfo(), metrics.Jaeger) + g.metrics, err = metrics.NewByType(g.GetSuiteContext(), metadata.BuildInfo(), metricsHandler) g.Require().Nil(err) } diff --git a/services/scribe/db/datastore/sql/base/model.go b/services/scribe/db/datastore/sql/base/model.go index 7901218b3d..7be83440cc 100644 --- a/services/scribe/db/datastore/sql/base/model.go +++ b/services/scribe/db/datastore/sql/base/model.go @@ -145,7 +145,7 @@ type LastIndexedInfo struct { // BlockNumber is the last block number indexed BlockNumber uint64 `gorm:"column:block_number;index:idx_last_indexed,priority:2"` // ChainID is the chain id of the contract - ChainID uint32 `gorm:"column:chain_id;uniqueIndex:idx_contract_chain"` + ChainID uint32 `gorm:"column:chain_id;index:idx_last_indexed;uniqueIndex:idx_contract_chain"` } // LastConfirmedBlockInfo contains information on when a chain last had a block pass the required confirmation diff --git a/services/scribe/db/suite_test.go b/services/scribe/db/suite_test.go index b638ec908f..a71bbb7486 100644 --- a/services/scribe/db/suite_test.go +++ b/services/scribe/db/suite_test.go @@ -3,17 +3,19 @@ package db_test import ( "database/sql" "fmt" + "os" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/dbcommon" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/core/testsuite" "github.com/synapsecns/sanguine/services/scribe/db" "github.com/synapsecns/sanguine/services/scribe/metadata" - "os" - "sync" - "sync/atomic" - "testing" - "time" "github.com/Flaque/filet" . "github.com/stretchr/testify/assert" @@ -54,9 +56,18 @@ func (t *DBSuite) SetupTest() { func (t *DBSuite) SetupSuite() { t.TestSuite.SetupSuite() - localmetrics.SetupTestJaeger(t.GetSuiteContext(), t.T()) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(t.GetSuiteContext(), t.T()) + metricsHandler = metrics.Jaeger + } + var err error - t.scribeMetrics, err = metrics.NewByType(t.GetSuiteContext(), metadata.BuildInfo(), metrics.Jaeger) + t.scribeMetrics, err = metrics.NewByType(t.GetSuiteContext(), metadata.BuildInfo(), metricsHandler) t.Require().Nil(err) } diff --git a/services/scribe/service/indexer/suite_test.go b/services/scribe/service/indexer/suite_test.go index ef6b11c148..aa976bd577 100644 --- a/services/scribe/service/indexer/suite_test.go +++ b/services/scribe/service/indexer/suite_test.go @@ -1,11 +1,13 @@ package indexer_test import ( + "testing" + "time" + + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/services/scribe/metadata" - "testing" - "time" "github.com/Flaque/filet" . "github.com/stretchr/testify/assert" @@ -50,10 +52,19 @@ func (x *IndexerSuite) SetupTest() { func (x *IndexerSuite) SetupSuite() { x.TestSuite.SetupSuite() - localmetrics.SetupTestJaeger(x.GetSuiteContext(), x.T()) + + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(x.GetSuiteContext(), x.T()) + metricsHandler = metrics.Jaeger + } var err error - x.metrics, err = metrics.NewByType(x.GetSuiteContext(), metadata.BuildInfo(), metrics.Jaeger) + x.metrics, err = metrics.NewByType(x.GetSuiteContext(), metadata.BuildInfo(), metricsHandler) Nil(x.T(), err) } diff --git a/services/scribe/service/suite_test.go b/services/scribe/service/suite_test.go index 9fd29145dd..8b09701b82 100644 --- a/services/scribe/service/suite_test.go +++ b/services/scribe/service/suite_test.go @@ -1,11 +1,13 @@ package service_test import ( + "testing" + "time" + + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/services/scribe/metadata" - "testing" - "time" "github.com/Flaque/filet" . "github.com/stretchr/testify/assert" @@ -53,10 +55,19 @@ func (s *ScribeSuite) SetupTest() { func (s *ScribeSuite) SetupSuite() { s.TestSuite.SetupSuite() - localmetrics.SetupTestJaeger(s.GetSuiteContext(), s.T()) + + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(s.GetSuiteContext(), s.T()) + metricsHandler = metrics.Jaeger + } var err error - s.metrics, err = metrics.NewByType(s.GetSuiteContext(), metadata.BuildInfo(), metrics.Jaeger) + s.metrics, err = metrics.NewByType(s.GetSuiteContext(), metadata.BuildInfo(), metricsHandler) Nil(s.T(), err) s.nullMetrics, err = metrics.NewByType(s.GetSuiteContext(), metadata.BuildInfo(), metrics.Null) diff --git a/services/scribe/testhelper/scribe.go b/services/scribe/testhelper/scribe.go index b67af9c3bf..34f128f08e 100644 --- a/services/scribe/testhelper/scribe.go +++ b/services/scribe/testhelper/scribe.go @@ -3,9 +3,12 @@ package testhelper import ( "context" "fmt" + "testing" + "github.com/Flaque/filet" "github.com/ipfs/go-log" "github.com/stretchr/testify/assert" + "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/metrics/localmetrics" "github.com/synapsecns/sanguine/ethergo/backends" @@ -17,7 +20,6 @@ import ( "github.com/synapsecns/sanguine/services/scribe/config" "github.com/synapsecns/sanguine/services/scribe/metadata" "github.com/synapsecns/sanguine/services/scribe/service" - "testing" ) var logger = log.Logger("scribe-testhelper") @@ -32,8 +34,17 @@ func NewTestScribe(ctx context.Context, tb testing.TB, deployedContracts map[uin omnirpcURL := testhelper.NewOmnirpcServer(ctx, tb, backends...) - localmetrics.SetupTestJaeger(ctx, tb) - metricsProvider, err := metrics.NewByType(ctx, metadata.BuildInfo(), metrics.Jaeger) + // don't use metrics on ci for integration tests + isCI := core.GetEnvBool("CI", false) + useMetrics := !isCI + metricsHandler := metrics.Null + + if useMetrics { + localmetrics.SetupTestJaeger(ctx, tb) + metricsHandler = metrics.Jaeger + } + + metricsProvider, err := metrics.NewByType(ctx, metadata.BuildInfo(), metricsHandler) assert.Nil(tb, err) eventDB, err := scribeAPI.InitDB(ctx, "sqlite", dbPath, metricsProvider, false)