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 RFQ Guard #2840

Merged
merged 40 commits into from
Jul 6, 2024
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b69063b
WIP: guard skeleton
dwasse Jul 2, 2024
1c4ef5f
WIP: add guarddb package
dwasse Jul 2, 2024
43d10b7
WIP: db loop
dwasse Jul 2, 2024
49e9466
Feat: add BridgeRequest model
dwasse Jul 2, 2024
85cbf82
Feat: store bridge request
dwasse Jul 2, 2024
35bf205
Feat: add dispute trigger
dwasse Jul 2, 2024
ebb8847
Feat: handle disputed log
dwasse Jul 2, 2024
1aea042
Feat: implement relayMatchesBridgeRequest()
dwasse Jul 2, 2024
9c54eee
Fix: build
dwasse Jul 3, 2024
bdbaa7e
Fix: guarddb models
dwasse Jul 3, 2024
7864840
Feat: check verified status in e2e test
dwasse Jul 3, 2024
ff7d1ca
Feat: check verified status in TestETHtoETH
dwasse Jul 3, 2024
b2d25e9
Feat: add guard cmd
dwasse Jul 3, 2024
76b2e1a
Feat: add embedded guard to relayer
dwasse Jul 3, 2024
278cbb9
Feat: add guard config
dwasse Jul 3, 2024
568acce
Feat: add converter for relayer cfg -> guard cfg
dwasse Jul 3, 2024
24c551e
Feat: add UseEmbeddedGuard flag
dwasse Jul 3, 2024
99834de
Feat: add guard wallet for e2e
dwasse Jul 3, 2024
a9174c3
WIP: add TestDispute
dwasse Jul 3, 2024
11196b4
Feat: start relayer / guard within tests
dwasse Jul 3, 2024
880e044
Fix: return valid=false on 'not found' err
dwasse Jul 3, 2024
32ede77
Fix: pass in raw request
dwasse Jul 3, 2024
6ab95e1
Cleanup: logs
dwasse Jul 3, 2024
c08e4f5
Feat: add tracing
dwasse Jul 3, 2024
b344694
Cleanup: move handlers to handlers.go
dwasse Jul 3, 2024
cdd769d
Cleanup: lint
dwasse Jul 4, 2024
bb677e0
Cleanup: lint
dwasse Jul 5, 2024
ed153b7
[goreleaser]
dwasse Jul 5, 2024
3ab1ce6
Merge branch 'master' into feat/rfq-guard
dwasse Jul 5, 2024
afb427b
[goreleaser]
dwasse Jul 5, 2024
6efd100
Fix: inherit txSubmitter from relayer
dwasse Jul 5, 2024
f5a00e6
[goreleaser]
dwasse Jul 5, 2024
d3011d8
add guard
trajan0x Jul 5, 2024
6982d44
use correct transactor
trajan0x Jul 5, 2024
3544a11
merge master [goreleaser]
trajan0x Jul 6, 2024
9b22148
event parsing
trajan0x Jul 6, 2024
e576f34
Merge branch 'master' into feat/rfq-guard [goreleaser]
trajan0x Jul 6, 2024
7ceb0db
fix tests
trajan0x Jul 6, 2024
71133e6
anvil removal [goreleaser]
trajan0x Jul 6, 2024
63e553c
ci again
trajan0x Jul 6, 2024
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
1 change: 1 addition & 0 deletions docs/bridge/docs/rfq/Relayer/Relayer.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ The relayer is configured with a yaml file. The following is an example configur
- `rebalance_interval` - How often to rebalance, formatted as (s = seconds, m = minutes, h = hours)
- `relayer_api_port` - the relayer api is used to control the relayer. <!--TODO: more info here--> This api should be secured/not public.
- `base_chain_config`: Base chain config is the default config applied for each chain if the other chains do not override it. This is covered in the chains section.
- `enable_embedded_guard` - Run a guard on the same instance.
- `chains` - each chain has a different config that overrides base_chain_config. Here are the parameters for each chain
- `rfq_address` - the address of the rfq contract on this chain. These addresses are available [here](../Contracts.md).

Expand Down
139 changes: 139 additions & 0 deletions services/rfq/e2e/rfq_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package e2e_test

import (
"fmt"
"math/big"
"testing"
"time"
Expand All @@ -19,6 +20,8 @@ import (
omnirpcClient "github.com/synapsecns/sanguine/services/omnirpc/client"
"github.com/synapsecns/sanguine/services/rfq/api/client"
"github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge"
"github.com/synapsecns/sanguine/services/rfq/guard/guarddb"
guardService "github.com/synapsecns/sanguine/services/rfq/guard/service"
"github.com/synapsecns/sanguine/services/rfq/relayer/chain"
"github.com/synapsecns/sanguine/services/rfq/relayer/reldb"
"github.com/synapsecns/sanguine/services/rfq/relayer/service"
Expand All @@ -36,9 +39,12 @@ type IntegrationSuite struct {
omniClient omnirpcClient.RPCClient
metrics metrics.Handler
store reldb.Service
guardStore guarddb.Service
apiServer string
relayer *service.Relayer
guard *guardService.Guard
relayerWallet wallet.Wallet
guardWallet wallet.Wallet
userWallet wallet.Wallet
}

Expand Down Expand Up @@ -82,6 +88,7 @@ func (i *IntegrationSuite) SetupTest() {
// setup the api server
i.setupQuoterAPI()
i.setupRelayer()
i.setupGuard()
}

// getOtherBackend gets the backend that is not the current one. This is a helper
Expand All @@ -100,6 +107,14 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() {
i.T().Skip("skipping until anvil issues are fixed in CI")
}

// start the relayer and guard
go func() {
_ = i.relayer.Start(i.GetTestContext())
}()
go func() {
_ = i.guard.Start(i.GetTestContext())
}()

// load token contracts
const startAmount = 1000
const rfqAmount = 900
Expand Down Expand Up @@ -240,13 +255,29 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() {
i.NoError(err)
return len(originPendingRebals) > 0
})

i.Eventually(func() bool {
// verify that the guard has marked the tx as validated
results, err := i.guardStore.GetPendingProvensByStatus(i.GetTestContext(), guarddb.Validated)
i.NoError(err)
return len(results) == 1
})
}

// nolint: cyclop
func (i *IntegrationSuite) TestETHtoETH() {
if core.GetEnvBool("CI", false) {
i.T().Skip("skipping until anvil issues are fixed in CI")
}

// start the relayer and guard
go func() {
_ = i.relayer.Start(i.GetTestContext())
}()
go func() {
_ = i.guard.Start(i.GetTestContext())
}()

// Send ETH to the relayer on destination
const initialBalance = 10
i.destBackend.FundAccount(i.GetTestContext(), i.relayerWallet.Address(), *big.NewInt(initialBalance))
Expand Down Expand Up @@ -347,4 +378,112 @@ func (i *IntegrationSuite) TestETHtoETH() {
}
return false
})

i.Eventually(func() bool {
// verify that the guard has marked the tx as validated
results, err := i.guardStore.GetPendingProvensByStatus(i.GetTestContext(), guarddb.Validated)
i.NoError(err)
return len(results) == 1
})
}

func (i *IntegrationSuite) TestDispute() {
if core.GetEnvBool("CI", false) {
i.T().Skip("skipping until anvil issues are fixed in CI")
}

// start the guard
go func() {
_ = i.guard.Start(i.GetTestContext())
}()

// load token contracts
const startAmount = 1000
const rfqAmount = 900
opts := i.destBackend.GetTxContext(i.GetTestContext(), nil)
destUSDC, destUSDCHandle := i.cctpDeployManager.GetMockMintBurnTokenType(i.GetTestContext(), i.destBackend)
realStartAmount, err := testutil.AdjustAmount(i.GetTestContext(), big.NewInt(startAmount), destUSDC.ContractHandle())
i.NoError(err)
realRFQAmount, err := testutil.AdjustAmount(i.GetTestContext(), big.NewInt(rfqAmount), destUSDC.ContractHandle())
i.NoError(err)

// add initial usdc to relayer on destination
tx, err := destUSDCHandle.MintPublic(opts.TransactOpts, i.relayerWallet.Address(), realStartAmount)
i.Nil(err)
i.destBackend.WaitForConfirmation(i.GetTestContext(), tx)
i.Approve(i.destBackend, destUSDC, i.relayerWallet)

// add initial USDC to relayer on origin
optsOrigin := i.originBackend.GetTxContext(i.GetTestContext(), nil)
originUSDC, originUSDCHandle := i.cctpDeployManager.GetMockMintBurnTokenType(i.GetTestContext(), i.originBackend)
tx, err = originUSDCHandle.MintPublic(optsOrigin.TransactOpts, i.relayerWallet.Address(), realStartAmount)
i.Nil(err)
i.originBackend.WaitForConfirmation(i.GetTestContext(), tx)
i.Approve(i.originBackend, originUSDC, i.relayerWallet)

// add initial USDC to user on origin
tx, err = originUSDCHandle.MintPublic(optsOrigin.TransactOpts, i.userWallet.Address(), realRFQAmount)
i.Nil(err)
i.originBackend.WaitForConfirmation(i.GetTestContext(), tx)
i.Approve(i.originBackend, originUSDC, i.userWallet)

// now we can send the money
_, originFastBridge := i.manager.GetFastBridge(i.GetTestContext(), i.originBackend)
auth := i.originBackend.GetTxContext(i.GetTestContext(), i.userWallet.AddressPtr())
// we want 499 usdc for 500 requested within a day
tx, err = originFastBridge.Bridge(auth.TransactOpts, fastbridge.IFastBridgeBridgeParams{
DstChainId: uint32(i.destBackend.GetChainID()),
To: i.userWallet.Address(),
OriginToken: originUSDC.Address(),
SendChainGas: true,
DestToken: destUSDC.Address(),
OriginAmount: realRFQAmount,
DestAmount: new(big.Int).Sub(realRFQAmount, big.NewInt(10_000_000)),
Deadline: new(big.Int).SetInt64(time.Now().Add(time.Hour * 24).Unix()),
})
i.NoError(err)
i.originBackend.WaitForConfirmation(i.GetTestContext(), tx)

// fetch the txid and raw request
var txID [32]byte
var rawRequest []byte
parser, err := fastbridge.NewParser(originFastBridge.Address())
i.NoError(err)
i.Eventually(func() bool {
receipt, err := i.originBackend.TransactionReceipt(i.GetTestContext(), tx.Hash())
i.NoError(err)
for _, log := range receipt.Logs {
_, parsedEvent, ok := parser.ParseEvent(*log)
if !ok {
continue
}
event, ok := parsedEvent.(*fastbridge.FastBridgeBridgeRequested)
if ok {
rawRequest = event.Request
txID = event.TransactionId
return true
}
}
return false
})

// call prove() from the relayer wallet before relay actually occurred on dest
relayerAuth := i.originBackend.GetTxContext(i.GetTestContext(), i.relayerWallet.AddressPtr())
fakeHash := common.HexToHash("0xdeadbeef")
tx, err = originFastBridge.Prove(relayerAuth.TransactOpts, rawRequest, fakeHash)
i.NoError(err)
i.originBackend.WaitForConfirmation(i.GetTestContext(), tx)

// verify that the guard calls Dispute()
i.Eventually(func() bool {
results, err := i.guardStore.GetPendingProvensByStatus(i.GetTestContext(), guarddb.Disputed)
i.NoError(err)
if len(results) != 1 {
return false
}
fmt.Printf("GOT RESULTS: %v\n", results)
result, err := i.guardStore.GetPendingProvenByID(i.GetTestContext(), txID)
i.NoError(err)
return result.TxHash == fakeHash && result.Status == guarddb.Disputed && result.TransactionID == txID
})
}
107 changes: 78 additions & 29 deletions services/rfq/e2e/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ import (
"github.com/synapsecns/sanguine/services/rfq/api/db/sql"
"github.com/synapsecns/sanguine/services/rfq/api/rest"
"github.com/synapsecns/sanguine/services/rfq/contracts/ierc20"
"github.com/synapsecns/sanguine/services/rfq/guard/guardconfig"
guardConnect "github.com/synapsecns/sanguine/services/rfq/guard/guarddb/connect"
guardService "github.com/synapsecns/sanguine/services/rfq/guard/service"
"github.com/synapsecns/sanguine/services/rfq/relayer/chain"
"github.com/synapsecns/sanguine/services/rfq/relayer/relconfig"
"github.com/synapsecns/sanguine/services/rfq/relayer/reldb/connect"
Expand Down Expand Up @@ -94,6 +97,9 @@ func (i *IntegrationSuite) setupBackends() {
i.relayerWallet, err = wallet.FromRandom()
i.NoError(err)

i.guardWallet, err = wallet.FromRandom()
i.NoError(err)

i.userWallet, err = wallet.FromRandom()
i.NoError(err)

Expand Down Expand Up @@ -132,10 +138,12 @@ func (i *IntegrationSuite) setupBE(backend backends.SimulatedTestBackend) {

// store the keys
backend.Store(base.WalletToKey(i.T(), i.relayerWallet))
backend.Store(base.WalletToKey(i.T(), i.guardWallet))
backend.Store(base.WalletToKey(i.T(), i.userWallet))

// fund each of the wallets
backend.FundAccount(i.GetTestContext(), i.relayerWallet.Address(), ethAmount)
backend.FundAccount(i.GetTestContext(), i.guardWallet.Address(), ethAmount)
backend.FundAccount(i.GetTestContext(), i.userWallet.Address(), ethAmount)

go func() {
Expand All @@ -144,7 +152,7 @@ func (i *IntegrationSuite) setupBE(backend backends.SimulatedTestBackend) {

// TODO: in the case of relayer this not finishing before the test starts can lead to race conditions since
// nonce may be shared between submitter and relayer. Think about how to deal w/ this.
for _, user := range []wallet.Wallet{i.relayerWallet, i.userWallet} {
for _, user := range []wallet.Wallet{i.relayerWallet, i.guardWallet, i.userWallet} {
go func(userWallet wallet.Wallet) {
for _, token := range predeployTokens {
i.Approve(backend, i.manager.Get(i.GetTestContext(), backend, token), userWallet)
Expand Down Expand Up @@ -217,36 +225,14 @@ func (i *IntegrationSuite) Approve(backend backends.SimulatedTestBackend, token
}
}

func (i *IntegrationSuite) setupRelayer() {
// add myself as a filler
var wg sync.WaitGroup
wg.Add(2)

for _, backend := range core.ToSlice(i.originBackend, i.destBackend) {
go func(backend backends.SimulatedTestBackend) {
defer wg.Done()

metadata, rfqContract := i.manager.GetFastBridge(i.GetTestContext(), backend)

txContext := backend.GetTxContext(i.GetTestContext(), metadata.OwnerPtr())
relayerRole, err := rfqContract.RELAYERROLE(&bind.CallOpts{Context: i.GetTestContext()})
i.NoError(err)

tx, err := rfqContract.GrantRole(txContext.TransactOpts, relayerRole, i.relayerWallet.Address())
i.NoError(err)

backend.WaitForConfirmation(i.GetTestContext(), tx)
}(backend)
}
wg.Wait()

func (i *IntegrationSuite) getRelayerConfig() relconfig.Config {
// construct the config
relayerAPIPort, err := freeport.GetFreePort()
i.NoError(err)
dsn := filet.TmpDir(i.T(), "")
cctpContractOrigin, _ := i.cctpDeployManager.GetSynapseCCTP(i.GetTestContext(), i.originBackend)
cctpContractDest, _ := i.cctpDeployManager.GetSynapseCCTP(i.GetTestContext(), i.destBackend)
cfg := relconfig.Config{
return relconfig.Config{
// generated ex-post facto
Chains: map[int]relconfig.ChainConfig{
originBackendChainID: {
Expand Down Expand Up @@ -300,6 +286,32 @@ func (i *IntegrationSuite) setupRelayer() {
},
RebalanceInterval: 0,
}
}

func (i *IntegrationSuite) setupRelayer() {
// add myself as a filler
var wg sync.WaitGroup
wg.Add(2)

for _, backend := range core.ToSlice(i.originBackend, i.destBackend) {
go func(backend backends.SimulatedTestBackend) {
defer wg.Done()

metadata, rfqContract := i.manager.GetFastBridge(i.GetTestContext(), backend)

txContext := backend.GetTxContext(i.GetTestContext(), metadata.OwnerPtr())
relayerRole, err := rfqContract.RELAYERROLE(&bind.CallOpts{Context: i.GetTestContext()})
i.NoError(err)

tx, err := rfqContract.GrantRole(txContext.TransactOpts, relayerRole, i.relayerWallet.Address())
i.NoError(err)

backend.WaitForConfirmation(i.GetTestContext(), tx)
}(backend)
}
wg.Wait()

cfg := i.getRelayerConfig()

// in the first backend, we want to deploy a bunch of different tokens
// TODO: functionalize me.
Expand Down Expand Up @@ -374,15 +386,52 @@ func (i *IntegrationSuite) setupRelayer() {
fmt.Sprintf("%d-%s", originBackendChainID, chain.EthAddress),
}

// TODO: good chance we wanna leave actually starting this up to the indiividual test.
var err error
i.relayer, err = service.NewRelayer(i.GetTestContext(), i.metrics, cfg)
i.NoError(err)
go func() {
err = i.relayer.Start(i.GetTestContext())
}()

dbType, err := dbcommon.DBTypeFromString(cfg.Database.Type)
i.NoError(err)
i.store, err = connect.Connect(i.GetTestContext(), dbType, cfg.Database.DSN, i.metrics)
i.NoError(err)
}

func (i *IntegrationSuite) setupGuard() {
// add myself as a guard
var wg sync.WaitGroup
wg.Add(2)

for _, backend := range core.ToSlice(i.originBackend, i.destBackend) {
go func(backend backends.SimulatedTestBackend) {
defer wg.Done()

metadata, rfqContract := i.manager.GetFastBridge(i.GetTestContext(), backend)

txContext := backend.GetTxContext(i.GetTestContext(), metadata.OwnerPtr())
guardRole, err := rfqContract.GUARDROLE(&bind.CallOpts{Context: i.GetTestContext()})
i.NoError(err)

tx, err := rfqContract.GrantRole(txContext.TransactOpts, guardRole, i.guardWallet.Address())
i.NoError(err)

backend.WaitForConfirmation(i.GetTestContext(), tx)
}(backend)
}
wg.Wait()

relayerCfg := i.getRelayerConfig()
guardCfg := guardconfig.NewGuardConfigFromRelayer(relayerCfg)
guardCfg.Signer = signerConfig.SignerConfig{
Type: signerConfig.FileType.String(),
File: filet.TmpFile(i.T(), "", i.guardWallet.PrivateKeyHex()).Name(),
}

var err error
i.guard, err = guardService.NewGuard(i.GetTestContext(), i.metrics, guardCfg, nil)
i.NoError(err)

dbType, err := dbcommon.DBTypeFromString(guardCfg.Database.Type)
i.NoError(err)
i.guardStore, err = guardConnect.Connect(i.GetTestContext(), dbType, guardCfg.Database.DSN, i.metrics)
i.NoError(err)
}
Loading
Loading