Skip to content

Commit

Permalink
feat(rfq-guard): v2 guard logic [SLT-387] (#3324)
Browse files Browse the repository at this point in the history
* Feat: add FastBridgeAddress to pending proven model

* Feat: add v2 address to config

* Feat: add fastBridgeHandlers and v1/v2 specific guard logic

* Feat: different listener logic on v1/v2

* WIP: add RelayerAddressV1 to relayer config

* WIP: addr v1 is ptr

* Feat: remove fastBridgeHandler

* Fix: TestDispute

* Cleanup: func sig

* Cleanup: lint

* Fix: e2e test

* proposed mods for feat/guard-v2 [SLT-422] (#3364)

* relabeling & using v2 events/parsing for both versions

* lint

* typo

* addtl nil check on deref

* prove multicalled relays [SLT-422]

* runChainListener tweak. Parse proof relabel

* removing fastbridgev1 BridgeRequested & refs

* test probe - revert guard listener label

* restore legacy listener "guard" label. Establish "guardV2" for new version

* poke to rerun w/o explorer check

* removing dev comments

* [goreleaser]

* Feat: add fastbridgev2 and fastbridge for integration tests

* WIP: add separate dispute tests

* Fix: e2e

---------

Co-authored-by: parodime <[email protected]>
  • Loading branch information
dwasse and parodime authored Nov 26, 2024
1 parent 2132369 commit 57efbd0
Show file tree
Hide file tree
Showing 12 changed files with 575 additions and 144 deletions.
107 changes: 102 additions & 5 deletions services/rfq/e2e/rfq_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() {
})

// now we can send the money
_, originFastBridge := i.manager.GetFastBridge(i.GetTestContext(), i.originBackend)
_, originFastBridge := i.manager.GetFastBridgeV2(i.GetTestContext(), i.originBackend)
auth := i.originBackend.GetTxContext(i.GetTestContext(), i.userWallet.AddressPtr())
tx, err = originFastBridge.Bridge(auth.TransactOpts, fastbridgev2.IFastBridgeBridgeParams{
DstChainId: uint32(i.destBackend.GetChainID()),
Expand Down Expand Up @@ -318,7 +318,7 @@ func (i *IntegrationSuite) TestETHtoETH() {
})

// now we can send the money
_, originFastBridge := i.manager.GetFastBridge(i.GetTestContext(), i.originBackend)
_, originFastBridge := i.manager.GetFastBridgeV2(i.GetTestContext(), i.originBackend)
auth := i.originBackend.GetTxContext(i.GetTestContext(), i.userWallet.AddressPtr())
auth.TransactOpts.Value = realWantAmount
// we want 499 ETH for 500 requested within a day
Expand Down Expand Up @@ -458,7 +458,7 @@ func (i *IntegrationSuite) TestZap() {
})

// now we can send the money
_, originFastBridge := i.manager.GetFastBridge(i.GetTestContext(), i.originBackend)
_, originFastBridge := i.manager.GetFastBridgeV2(i.GetTestContext(), i.originBackend)
_, destRecipient := i.manager.GetRecipientMock(i.GetTestContext(), i.destBackend)
auth := i.originBackend.GetTxContext(i.GetTestContext(), i.userWallet.AddressPtr())
params := fastbridgev2.IFastBridgeBridgeParams{
Expand Down Expand Up @@ -511,7 +511,7 @@ func (i *IntegrationSuite) TestZap() {
})
}

func (i *IntegrationSuite) TestDispute() {
func (i *IntegrationSuite) TestDisputeV1() {
// start the guard
go func() {
_ = i.guard.Start(i.GetTestContext())
Expand Down Expand Up @@ -551,6 +551,103 @@ func (i *IntegrationSuite) TestDispute() {
_, 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()),
Sender: i.userWallet.Address(),
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 {
txID = event.TransactionId
rawRequest = event.Request
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
}
result, err := i.guardStore.GetPendingProvenByID(i.GetTestContext(), txID)
i.NoError(err)
return result.TxHash == fakeHash && result.Status == guarddb.Disputed && result.TransactionID == txID
})
}

func (i *IntegrationSuite) TestDisputeV2() {
// 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.GetFastBridgeV2(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, fastbridgev2.IFastBridgeBridgeParams{
DstChainId: uint32(i.destBackend.GetChainID()),

Check failure on line 652 in services/rfq/e2e/rfq_test.go

View workflow job for this annotation

GitHub Actions / Lint (services/rfq)

G115: integer overflow conversion uint -> uint32 (gosec)
Sender: i.userWallet.Address(),
Expand Down Expand Up @@ -670,7 +767,7 @@ func (i *IntegrationSuite) TestConcurrentBridges() {
return false
})

_, originFastBridge := i.manager.GetFastBridge(i.GetTestContext(), i.originBackend)
_, originFastBridge := i.manager.GetFastBridgeV2(i.GetTestContext(), i.originBackend)
auth := i.originBackend.GetTxContext(i.GetTestContext(), i.userWallet.AddressPtr())
parser, err := fastbridge.NewParser(originFastBridge.Address())
i.NoError(err)
Expand Down
95 changes: 76 additions & 19 deletions services/rfq/e2e/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ func (i *IntegrationSuite) setupQuoterAPI() {
originBackendChainID: i.manager.Get(i.GetTestContext(), i.originBackend, testutil.FastBridgeType).Address().String(),
destBackendChainID: i.manager.Get(i.GetTestContext(), i.destBackend, testutil.FastBridgeType).Address().String(),
},
FastBridgeContractsV2: map[uint32]string{
originBackendChainID: i.manager.Get(i.GetTestContext(), i.originBackend, testutil.FastBridgeV2Type).Address().String(),
destBackendChainID: i.manager.Get(i.GetTestContext(), i.destBackend, testutil.FastBridgeV2Type).Address().String(),
},
Port: strconv.Itoa(apiPort),
}
api, err := rest.NewAPI(i.GetTestContext(), apiCfg, i.metrics, i.omniClient, apiStore)
Expand Down Expand Up @@ -147,7 +151,7 @@ func (i *IntegrationSuite) setupBE(backend backends.SimulatedTestBackend) {
// but this way we can do something while we're waiting for the other backend to startup.
// no need to wait for these to deploy since they can happen in background as soon as the backend is up.
predeployTokens := []contracts.ContractType{testutil.DAIType, testutil.USDTType, testutil.WETH9Type}
predeploys := append(predeployTokens, testutil.FastBridgeType)
predeploys := append(predeployTokens, testutil.FastBridgeV2Type)
slices.Reverse(predeploys) // return fast bridge first

ethAmount := *new(big.Int).Mul(big.NewInt(params.Ether), big.NewInt(10))
Expand Down Expand Up @@ -267,15 +271,41 @@ func (i *IntegrationSuite) Approve(backend backends.SimulatedTestBackend, token

erc20, err := ierc20.NewIERC20(token.Address(), backend)
i.Require().NoError(err, "Failed to get erc20")
_, fastBridge := i.manager.GetFastBridge(i.GetTestContext(), backend)
allowance, err := erc20.Allowance(&bind.CallOpts{Context: i.GetTestContext()}, user.Address(), fastBridge.Address())

// approve fastbridgev1
_, fastBridgeV1 := i.manager.GetFastBridge(i.GetTestContext(), backend)
allowance, err := erc20.Allowance(&bind.CallOpts{Context: i.GetTestContext()}, user.Address(), fastBridgeV1.Address())
i.Require().NoError(err, "Failed to get allowance")

if allowance.Cmp(big.NewInt(0)) == 0 {
err = retry.WithBackoff(i.GetTestContext(), func(ctx context.Context) error {
txOpts := backend.GetTxContext(ctx, user.AddressPtr())
tx, err := erc20.Approve(txOpts.TransactOpts, fastBridgeV1.Address(), core.CopyBigInt(abi.MaxUint256))
if err != nil {
return fmt.Errorf("failed to approve: %w", err)
}
backend.WaitForConfirmation(ctx, tx)
return nil
})
i.Require().NoError(err, "Failed to approve")
}

// approve fastbridgev2
_, fastBridgeV2 := i.manager.GetFastBridgeV2(i.GetTestContext(), backend)
allowance, err = erc20.Allowance(&bind.CallOpts{Context: i.GetTestContext()}, user.Address(), fastBridgeV2.Address())
i.Require().NoError(err, "Failed to get allowance")

if allowance.Cmp(big.NewInt(0)) == 0 {
txOpts := backend.GetTxContext(i.GetTestContext(), user.AddressPtr())
tx, err := erc20.Approve(txOpts.TransactOpts, fastBridge.Address(), core.CopyBigInt(abi.MaxUint256))
err = retry.WithBackoff(i.GetTestContext(), func(ctx context.Context) error {
txOpts := backend.GetTxContext(ctx, user.AddressPtr())
tx, err := erc20.Approve(txOpts.TransactOpts, fastBridgeV2.Address(), core.CopyBigInt(abi.MaxUint256))
if err != nil {
return fmt.Errorf("failed to approve: %w", err)
}
backend.WaitForConfirmation(ctx, tx)
return nil
})
i.Require().NoError(err, "Failed to approve")
backend.WaitForConfirmation(i.GetTestContext(), tx)
}
}

Expand All @@ -286,11 +316,14 @@ func (i *IntegrationSuite) getRelayerConfig() relconfig.Config {
dsn := filet.TmpDir(i.T(), "")
cctpContractOrigin, _ := i.cctpDeployManager.GetSynapseCCTP(i.GetTestContext(), i.originBackend)
cctpContractDest, _ := i.cctpDeployManager.GetSynapseCCTP(i.GetTestContext(), i.destBackend)
rfqAddressV1Origin := i.manager.Get(i.GetTestContext(), i.originBackend, testutil.FastBridgeType).Address().String()
rfqAddressV1Dest := i.manager.Get(i.GetTestContext(), i.destBackend, testutil.FastBridgeType).Address().String()
return relconfig.Config{
// generated ex-post facto
Chains: map[int]relconfig.ChainConfig{
originBackendChainID: {
RFQAddress: i.manager.Get(i.GetTestContext(), i.originBackend, testutil.FastBridgeType).Address().String(),
RFQAddress: i.manager.Get(i.GetTestContext(), i.originBackend, testutil.FastBridgeV2Type).Address().String(),
RFQAddressV1: &rfqAddressV1Origin,
RebalanceConfigs: relconfig.RebalanceConfigs{
Synapse: &relconfig.SynapseCCTPRebalanceConfig{
SynapseCCTPAddress: cctpContractOrigin.Address().Hex(),
Expand All @@ -307,7 +340,8 @@ func (i *IntegrationSuite) getRelayerConfig() relconfig.Config {
NativeToken: "ETH",
},
destBackendChainID: {
RFQAddress: i.manager.Get(i.GetTestContext(), i.destBackend, testutil.FastBridgeType).Address().String(),
RFQAddress: i.manager.Get(i.GetTestContext(), i.destBackend, testutil.FastBridgeV2Type).Address().String(),
RFQAddressV1: &rfqAddressV1Dest,
RebalanceConfigs: relconfig.RebalanceConfigs{
Synapse: &relconfig.SynapseCCTPRebalanceConfig{
SynapseCCTPAddress: cctpContractDest.Address().Hex(),
Expand Down Expand Up @@ -362,26 +396,38 @@ func (i *IntegrationSuite) setupRelayer() {
defer wg.Done()

err := retry.WithBackoff(i.GetTestContext(), func(ctx context.Context) error {
metadata, rfqContract := i.manager.GetFastBridge(i.GetTestContext(), backend)
metadataV1, rfqContractV1 := i.manager.GetFastBridge(i.GetTestContext(), backend)
txContextV1 := backend.GetTxContext(i.GetTestContext(), metadataV1.OwnerPtr())

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

Check failure on line 402 in services/rfq/e2e/setup_test.go

View workflow job for this annotation

GitHub Actions / Lint (services/rfq)

ineffectual assignment to err (ineffassign)
proverRole, err := rfqContractV1.RELAYERROLE(&bind.CallOpts{Context: i.GetTestContext()})

Check failure on line 403 in services/rfq/e2e/setup_test.go

View workflow job for this annotation

GitHub Actions / Lint (services/rfq)

ineffectual assignment to proverRole (ineffassign)
if err != nil {
return fmt.Errorf("could not get prover role: %w", err)
}
tx, err := rfqContractV1.GrantRole(txContextV1.TransactOpts, relayerRole, i.relayerWallet.Address())
if err != nil {
return fmt.Errorf("could not grant relayer role: %w", err)
}
backend.WaitForConfirmation(i.GetTestContext(), tx)

metadataV2, rfqContractV2 := i.manager.GetFastBridgeV2(i.GetTestContext(), backend)
txContextV2 := backend.GetTxContext(i.GetTestContext(), metadataV2.OwnerPtr())

tx, err := rfqContract.GrantRole(txContext.TransactOpts, proverRole, i.relayerWallet.Address())
proverRole, err = rfqContractV2.PROVERROLE(&bind.CallOpts{Context: i.GetTestContext()})
if err != nil {
return fmt.Errorf("could not get prover role: %w", err)
}
tx, err = rfqContractV2.GrantRole(txContextV2.TransactOpts, proverRole, i.relayerWallet.Address())
if err != nil {
return fmt.Errorf("could not grant prover role: %w", err)
}
backend.WaitForConfirmation(i.GetTestContext(), tx)

quoterRole, err := rfqContract.QUOTERROLE(&bind.CallOpts{Context: i.GetTestContext()})
quoterRole, err := rfqContractV2.QUOTERROLE(&bind.CallOpts{Context: i.GetTestContext()})
if err != nil {
return fmt.Errorf("could not get quoter role: %w", err)
}

tx, err = rfqContract.GrantRole(txContext.TransactOpts, quoterRole, i.relayerWallet.Address())
tx, err = rfqContractV2.GrantRole(txContextV2.TransactOpts, quoterRole, i.relayerWallet.Address())
if err != nil {
return fmt.Errorf("could not grant quoter role: %w", err)
}
Expand Down Expand Up @@ -488,13 +534,24 @@ func (i *IntegrationSuite) setupGuard() {
go func(backend backends.SimulatedTestBackend) {
defer wg.Done()

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

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

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

backend.WaitForConfirmation(i.GetTestContext(), tx)

metadataV2, rfqContractV2 := i.manager.GetFastBridgeV2(i.GetTestContext(), backend)

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

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

backend.WaitForConfirmation(i.GetTestContext(), tx)
Expand Down
35 changes: 27 additions & 8 deletions services/rfq/guard/guardconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ type Config struct {

// ChainConfig represents the configuration for a chain.
type ChainConfig struct {
// Bridge is the rfq bridge contract address.
// RFQAddressV1 is the legacy V1 rfq bridge contract address. OPTIONAL. Only populate if also guarding a deprecated V1 contract.
RFQAddressV1 *string `yaml:"rfq_address_v1"`
// RFQAddress is the current/latest rfq bridge contract address. REQUIRED.
RFQAddress string `yaml:"rfq_address"`
// Confirmations is the number of required confirmations.
Confirmations uint64 `yaml:"confirmations"`
Expand Down Expand Up @@ -66,12 +68,19 @@ func LoadConfig(path string) (config Config, err error) {
// Validate validates the config.
func (c Config) Validate() (err error) {
for chainID := range c.Chains {
addr, err := c.GetRFQAddress(chainID)
addrV1, err := c.GetRFQAddressV1(chainID)
if err != nil {
return fmt.Errorf("could not get rfq address: %w", err)
return fmt.Errorf("could not get v1 rfq address: %w", err)
}
if !common.IsHexAddress(addr) {
return fmt.Errorf("invalid rfq address: %s", addr)
if addrV1 != nil && !common.IsHexAddress(*addrV1) {
return fmt.Errorf("invalid rfq v1 address: %s", *addrV1)
}
addrV2, err := c.GetRFQAddressV2(chainID)
if err != nil {
return fmt.Errorf("could not get v2 rfq address: %w", err)
}
if !common.IsHexAddress(addrV2) {
return fmt.Errorf("invalid rfq v2 address: %s", addrV2)
}
}

Expand All @@ -83,11 +92,20 @@ func (c Config) GetChains() map[int]ChainConfig {
return c.Chains
}

// GetRFQAddress returns the RFQ address for the given chain.
func (c Config) GetRFQAddress(chainID int) (string, error) {
// GetRFQAddressV1 returns the RFQ address for the given chain.
func (c Config) GetRFQAddressV1(chainID int) (*string, error) {
chainCfg, ok := c.Chains[chainID]
if !ok {
return nil, fmt.Errorf("v1 chain config not found for chain %d", chainID)
}
return chainCfg.RFQAddressV1, nil
}

// GetRFQAddressV2 returns the RFQ address for the given chain.
func (c Config) GetRFQAddressV2(chainID int) (string, error) {
chainCfg, ok := c.Chains[chainID]
if !ok {
return "", fmt.Errorf("chain config not found for chain %d", chainID)
return "", fmt.Errorf("v2 chain config not found for chain %d", chainID)
}
return chainCfg.RFQAddress, nil
}
Expand Down Expand Up @@ -115,6 +133,7 @@ func NewGuardConfigFromRelayer(relayerCfg relconfig.Config) Config {
}
for chainID, chainCfg := range relayerCfg.GetChains() {
cfg.Chains[chainID] = ChainConfig{
RFQAddressV1: chainCfg.RFQAddressV1,
RFQAddress: chainCfg.RFQAddress,
Confirmations: chainCfg.FinalityConfirmations,
}
Expand Down
Loading

0 comments on commit 57efbd0

Please sign in to comment.