Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Parser support for Destination and add DB models for monitoring #102

Merged
merged 23 commits into from
Aug 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions core/contracts/attestationcollector/attestationcollector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package attestationcollector_test

import (
"context"
"fmt"
"math/big"
"time"

"github.com/brianvoe/gofakeit/v6"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
. "github.com/stretchr/testify/assert"
"github.com/synapsecns/sanguine/core/agents/notary"
"github.com/synapsecns/sanguine/core/contracts/attestationcollector"
"github.com/synapsecns/sanguine/core/types"
"github.com/synapsecns/synapse-node/contracts/bridge"
)

func (a AttestationCollectorSuite) TestAttestationCollectorSuite() {
// Set up the contexts for all contracts, including the destination and attestation collector to get owner.
txContextOrigin := a.testBackendOrigin.GetTxContext(a.GetTestContext(), nil)
txContextDestination := a.testBackendDestination.GetTxContext(a.GetTestContext(), a.destinationContractMetadata.OwnerPtr())
txContextAttestationCollector := a.testBackendDestination.GetTxContext(a.GetTestContext(), a.attestationContractMetadata.OwnerPtr())

// Create a channel and subscription to receive AttestationSubmitted events as they are emitted.
attestationSink := make(chan *attestationcollector.AttestationCollectorAttestationSubmitted)
subAttestation, err := a.attestationContract.WatchAttestationSubmitted(&bind.WatchOpts{Context: a.GetTestContext()}, attestationSink, []common.Address{})
Nil(a.T(), err)

encodedTips, err := types.EncodeTips(types.NewTips(big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)))
Nil(a.T(), err)

// Dispatch an event from the Origin contract to be accepted on the Destination contract.
tx, err := a.originContract.Dispatch(txContextOrigin.TransactOpts, 1, [32]byte{}, 1, encodedTips, nil)
Nil(a.T(), err)
a.testBackendOrigin.WaitForConfirmation(a.GetTestContext(), tx)

// Create an attestation
localDomain := uint32(a.testBackendOrigin.Config().ChainID)
nonce := gofakeit.Uint32()
root := common.BigToHash(new(big.Int).SetUint64(gofakeit.Uint64()))
unsignedAttestation := types.NewAttestation(localDomain, nonce, root)
hashedAttestation, err := notary.HashAttestation(unsignedAttestation)
Nil(a.T(), err)

signature, err := a.signer.SignMessage(a.GetTestContext(), bridge.KappaToSlice(hashedAttestation), false)
Nil(a.T(), err)

signedAttestation := types.NewSignedAttestation(unsignedAttestation, signature)
encodedSig, err := types.EncodeSignature(signedAttestation.Signature())
Nil(a.T(), err)

attestation, err := a.attestationHarness.FormatAttestation(
&bind.CallOpts{Context: a.GetTestContext()},
signedAttestation.Attestation().Domain(),
signedAttestation.Attestation().Nonce(),
signedAttestation.Attestation().Root(),
encodedSig,
)
Nil(a.T(), err)

// Set notary to the testing address so we can submit attestations.
tx, err = a.destinationContract.SetNotary(txContextDestination.TransactOpts, uint32(a.testBackendOrigin.GetChainID()), a.signer.Address())
Nil(a.T(), err)
a.testBackendDestination.WaitForConfirmation(a.GetTestContext(), tx)

// Submit the attestation to get an AttestationAccepted event.
tx, err = a.destinationContract.SubmitAttestation(txContextDestination.TransactOpts, attestation)
Nil(a.T(), err)
a.testBackendDestination.WaitForConfirmation(a.GetTestContext(), tx)

// Set notary to the testing address so we can submit attestations.
tx, err = a.attestationContract.AddNotary(txContextAttestationCollector.TransactOpts, uint32(a.testBackendOrigin.GetChainID()), a.signer.Address())
Nil(a.T(), err)
a.testBackendDestination.WaitForConfirmation(a.GetTestContext(), tx)

// Submit the attestation to get an AttestationSubmitted event.
tx, err = a.attestationContract.SubmitAttestation(txContextAttestationCollector.TransactOpts, attestation)
Nil(a.T(), err)
a.testBackendDestination.WaitForConfirmation(a.GetTestContext(), tx)

watchCtx, cancel := context.WithTimeout(a.GetTestContext(), time.Second*10)
defer cancel()

select {
// check for errors and fail
case <-watchCtx.Done():
a.T().Error(a.T(), fmt.Errorf("test context completed %w", a.GetTestContext().Err()))
case <-subAttestation.Err():
a.T().Error(a.T(), subAttestation.Err())
// get AttestationSubmitted event
case item := <-attestationSink:
parser, err := attestationcollector.NewParser(a.attestationContract.Address())
Nil(a.T(), err)

// Check to see if the event was an AttestationSubmitted event.
eventType, ok := parser.EventType(item.Raw)
True(a.T(), ok)
Equal(a.T(), eventType, attestationcollector.AttestationSubmittedEvent)

break
}
}
23 changes: 23 additions & 0 deletions core/contracts/attestationcollector/eventtype_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 83 additions & 0 deletions core/contracts/attestationcollector/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package attestationcollector

import (
"fmt"
"github.com/ethereum/go-ethereum/common"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/synapsecns/sanguine/core/types"
)

// init serves as a static assertion that AllEventTypes are accounted for.
func init() {
if len(_EventType_index)-1 != len(AllEventTypes) {
panic("add new events to alle vent types")
}
}

// Parser parses events from the attestation collector contract.
type Parser interface {
// EventType determines if an event was initiated by the bridge or the user.
EventType(log ethTypes.Log) (_ EventType, ok bool)
// ParseAttestationSubmitted parses an AttestationSubmitted event
ParseAttestationSubmitted(log ethTypes.Log) (_ types.AttestationSubmitted, ok bool)
}

type parserImpl struct {
// filterer is the parser filterer we use to parse events
filterer *AttestationCollectorFilterer
}

// NewParser creates a new parser for the attestation collector contract.
func NewParser(attestationCollectorAddress common.Address) (Parser, error) {
parser, err := NewAttestationCollectorFilterer(attestationCollectorAddress, nil)
if err != nil {
return nil, fmt.Errorf("could not create %T: %w", AttestationCollectorFilterer{}, err)
}

return &parserImpl{filterer: parser}, 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(AllEventTypes) + 2), false
}

// ParseAttestationSubmitted parses an AttestationSubmitted event.
func (p parserImpl) ParseAttestationSubmitted(log ethTypes.Log) (_ types.AttestationSubmitted, ok bool) {
attestation, err := p.filterer.ParseAttestationSubmitted(log)
if err != nil {
return nil, false
}

attestationSubmitted := types.NewAttestationSubmitted(
attestation.Notary.Hash(),
attestation.Attestation,
)

return attestationSubmitted, true
}

// EventType is the type of the attestation collector events
//go:generate go run golang.org/x/tools/cmd/stringer -type=EventType
type EventType uint

const (
// AttestationSubmittedEvent is a AttestationSubmitted event.
AttestationSubmittedEvent EventType = 0
)

// Int gets the int for an event type.
func (i EventType) Int() uint8 {
return uint8(i)
}

// AllEventTypes contains all event types.
var AllEventTypes = []EventType{AttestationSubmittedEvent}
87 changes: 87 additions & 0 deletions core/contracts/attestationcollector/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package attestationcollector_test

import (
"math/big"
"testing"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/suite"
"github.com/synapsecns/sanguine/core/contracts/attestationcollector"
"github.com/synapsecns/sanguine/core/contracts/destination"
"github.com/synapsecns/sanguine/core/contracts/origin"
"github.com/synapsecns/sanguine/core/contracts/test/attestationharness"
"github.com/synapsecns/sanguine/core/testutil"
"github.com/synapsecns/sanguine/ethergo/signer/signer"
"github.com/synapsecns/sanguine/ethergo/signer/signer/localsigner"
"github.com/synapsecns/sanguine/ethergo/signer/wallet"
"github.com/synapsecns/synapse-node/testutils"
"github.com/synapsecns/synapse-node/testutils/backends"
"github.com/synapsecns/synapse-node/testutils/backends/preset"
)

// AttestationCollectorSuite is the attestation collector test suite.
type AttestationCollectorSuite struct {
*testutils.TestSuite
originContract *origin.OriginRef
destinationContract *destination.DestinationRef
destinationContractMetadata backends.DeployedContract
attestationHarness *attestationharness.AttestationHarnessRef
attestationContract *attestationcollector.AttestationCollectorRef
attestationContractMetadata backends.DeployedContract
testBackendOrigin backends.SimulatedTestBackend
testBackendDestination backends.SimulatedTestBackend
wallet wallet.Wallet
signer signer.Signer
}

// NewAttestationCollectorSuite creates an end-to-end test suite.
func NewAttestationCollectorSuite(tb testing.TB) *AttestationCollectorSuite {
tb.Helper()
return &AttestationCollectorSuite{
TestSuite: testutils.NewTestSuite(tb),
}
}

func (a *AttestationCollectorSuite) SetupTest() {
a.TestSuite.SetupTest()

a.testBackendOrigin = preset.GetRinkeby().Geth(a.GetTestContext(), a.T())
a.testBackendDestination = preset.GetBSCTestnet().Geth(a.GetTestContext(), a.T())
deployManager := testutil.NewDeployManager(a.T())

_, a.originContract = deployManager.GetOrigin(a.GetTestContext(), a.testBackendOrigin)
_, a.attestationHarness = deployManager.GetAttestationHarness(a.GetTestContext(), a.testBackendOrigin)
a.attestationContractMetadata, a.attestationContract = deployManager.GetAttestationCollector(a.GetTestContext(), a.testBackendDestination)
a.destinationContractMetadata, a.destinationContract = deployManager.GetDestination(a.GetTestContext(), a.testBackendDestination)

var err error
a.wallet, err = wallet.FromRandom()
if err != nil {
a.T().Fatal(err)
}

_, notaryManager := deployManager.GetNotaryManager(a.GetTestContext(), a.testBackendOrigin)
owner, err := notaryManager.Owner(&bind.CallOpts{Context: a.GetTestContext()})
if err != nil {
a.T().Fatal(err)
}

a.signer = localsigner.NewSigner(a.wallet.PrivateKey())
a.testBackendOrigin.FundAccount(a.GetTestContext(), a.signer.Address(), *big.NewInt(params.Ether))
a.testBackendDestination.FundAccount(a.GetTestContext(), a.signer.Address(), *big.NewInt(params.Ether))

transactOpts := a.testBackendOrigin.GetTxContext(a.GetTestContext(), &owner)

tx, err := notaryManager.SetNotary(transactOpts.TransactOpts, a.signer.Address())
if err != nil {
a.T().Fatal(err)
}

a.testBackendOrigin.WaitForConfirmation(a.GetTestContext(), tx)
}

// TestAttestationCollectorSuite runs the integration test suite.
func TestAttestationCollectorSuite(t *testing.T) {
suite.Run(t, NewAttestationCollectorSuite(t))
}
44 changes: 44 additions & 0 deletions core/contracts/attestationcollector/topics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package attestationcollector

import (
"bytes"
"strings"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
)

func init() {
// set topics
var err error

parsedAttestationCollector, err := abi.JSON(strings.NewReader(AttestationCollectorABI))
if err != nil {
panic(err)
}

AttestationSubmittedTopic = parsedAttestationCollector.Events["AttestationSubmitted"].ID
}

// AttestationSubmittedTopic is the topic that gets emitted
// when the AttestationSubmitted event is called.
var AttestationSubmittedTopic 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{
AttestationSubmittedEvent: AttestationSubmittedTopic,
}
}

// 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
}
Loading