Skip to content

Commit

Permalink
feat(rfq-relayer): fee pricer considers v2 CallValue and CallParams […
Browse files Browse the repository at this point in the history
…SLT-320] (#3299)

* WIP: incorporate call params into fee

* WIP: decompose getFee()

* Feat: account for CallValue in getDestinationFee()

* Fix: build

* Feat: test call value and call param calculation in fee pricer

* Feat: add context on request body in rpc fwd errs

* Fix: zap estimate gas

* Cleanup: move gas estimate into own func

* Fix: quoter tests

* Cleanup: lint

* Cleanup: lint

* Fix: tests

* Cleanup: decompose func

* Cleanup: lint

* Fix: tests

* Cleanup: lint

* Feat: always use quote fee multiplier

* WIP: abi encode pack relay()

* Feat: pass full RawRequest for gas estimation

* Cleanup: lint

* Fix: pricer tests

* Feat: ignore static l2 fee when incorporating call params

* Fix: tests

* Clarifying comment

* Feat: add extra check for call param len

* Attempt to fix flake

* Cleanup: lint

* Fix: build

* feat(rfq-relayer): apply zap fee to dest amount for active quotes [SLT-465] (#3395)

* Feat: set zap params in supply to fee pricer

* Feat: adjust dest amount by fee for active quote

* Feat: add TestGenerateActiveRFQ

* Feat: add nonzero case

* Cleanup: lint

* fix(rfq-relayer): gas estimation for zaps (#3413)

* WIP: use QuoteData instead of QuoteRequest

* WIP: impl conversion

* Feat: use QuoteRequest instead of QuoteData for encoding

* WIP: another encoding impl

* WIP: add bridgetransactionv2

* WIP: regenerate with running test

* Working zap e2e

* Cleanup: encoding

* Cleanup: move to new encoding file

* ABIgen from harness instead of lib

* Cleanup: lint

* Fix: CI build

* Fix: deploy harness

* Fix: rfq test

* Feat: use gofakeit to mock

* Cleanup: test structure

* Replace: EncodeQuoteRequest -> EncodeBridgeTx

* Feat: add Decode func and add to parity test

* Fix: eth address mocks

* Cleanup: lint

* Cleanup: lint

---------

Co-authored-by: parodime <[email protected]>
  • Loading branch information
dwasse and parodime authored Dec 6, 2024
1 parent fab0b9e commit c1f57ef
Show file tree
Hide file tree
Showing 23 changed files with 4,747 additions and 72 deletions.
13 changes: 10 additions & 3 deletions ethergo/backends/anvil/anvil.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/synapsecns/sanguine/core/dockerutil"
"github.com/synapsecns/sanguine/core/mapmutex"
"github.com/synapsecns/sanguine/core/processlog"
"github.com/synapsecns/sanguine/core/retry"
"github.com/synapsecns/sanguine/ethergo/backends"
"github.com/synapsecns/sanguine/ethergo/backends/base"
"github.com/synapsecns/sanguine/ethergo/chain"
Expand Down Expand Up @@ -386,9 +387,15 @@ func (f *Backend) GetTxContext(ctx context.Context, address *common.Address) (re
var acct *keystore.Key
// TODO handle storing accounts to conform to get tx context
if address != nil {
acct = f.GetAccount(*address)
if acct == nil {
f.T().Errorf("could not get account %s", address.String())
err := retry.WithBackoff(ctx, func(_ context.Context) error {
acct = f.GetAccount(*address)
if acct == nil {
return fmt.Errorf("could not get account %s", address.String())
}
return nil
}, retry.WithMaxTotalTime(10*time.Second))
if err != nil {
f.T().Errorf("could not get account %s: %v", address.String(), err)
return res
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts-rfq/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"lint": "forge fmt && npm run solhint",
"lint:check": "forge fmt --check && npm run solhint:check",
"ci:lint": "npm run lint:check",
"build:go": "./flatten.sh contracts/*.sol test/*.sol test/mocks/*.sol",
"build:go": "./flatten.sh contracts/*.sol test/*.sol test/mocks/*.sol test/harnesses/*.sol",
"solhint": "solhint '{contracts,script,test}/**/*.sol' --fix --noPrompt --max-warnings 3",
"solhint:check": "solhint '{contracts,script,test}/**/*.sol' --max-warnings 3"
}
Expand Down
7 changes: 4 additions & 3 deletions services/omnirpc/proxy/forward.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import (
"context"
"crypto/sha256"
"fmt"
goHTTP "net/http"
"strings"

"github.com/ImVexed/fasturl"
"github.com/goccy/go-json"
"github.com/jftuga/ellipsis"
Expand All @@ -14,8 +17,6 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"golang.org/x/exp/slices"
goHTTP "net/http"
"strings"
)

type rawResponse struct {
Expand Down Expand Up @@ -56,7 +57,7 @@ func (f *Forwarder) newRawResponse(ctx context.Context, body []byte, url string)

standardizedResponse, err = standardizeResponse(ctx, &f.rpcRequest[0], rpcMessage)
if err != nil {
return nil, fmt.Errorf("could not standardize response: %w", err)
return nil, fmt.Errorf("could not standardize response from body %s: %w", ellipsis.Shorten(string(body), 200), err)
}
}

Expand Down
3 changes: 2 additions & 1 deletion services/rfq/api/model/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,12 @@ type QuoteData struct {
DestChainID int `json:"dest_chain_id"`
OriginTokenAddr string `json:"origin_token_addr"`
DestTokenAddr string `json:"dest_token_addr"`
OriginAmount string `json:"origin_amount"`
ExpirationWindow int64 `json:"expiration_window"`
ZapData string `json:"zap_data"`
ZapNative string `json:"zap_native"`
OriginAmountExact string `json:"origin_amount_exact"`
OriginSender string `json:"origin_sender"`
DestRecipient string `json:"dest_recipient"`
DestAmount *string `json:"dest_amount"`
RelayerAddress *string `json:"relayer_address"`
QuoteID *string `json:"quote_id"`
Expand Down
4,004 changes: 4,004 additions & 0 deletions services/rfq/contracts/bridgetransactionv2/bridgetransactionv2.abigen.go

Large diffs are not rendered by default.

Large diffs are not rendered by default.

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

4 changes: 4 additions & 0 deletions services/rfq/contracts/bridgetransactionv2/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Package bridgetransactionv2 is the bridge transaction contract.
package bridgetransactionv2

//go:generate go run github.com/synapsecns/sanguine/tools/abigen generate --sol ../../../../packages/contracts-rfq/flattened/BridgeTransactionV2Harness.sol --pkg bridgetransactionv2 --sol-version 0.8.24 --filename bridgetransactionv2 --evm-version istanbul
35 changes: 35 additions & 0 deletions services/rfq/contracts/bridgetransactionv2/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package bridgetransactionv2

import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
)

// BridgeTransactionV2Ref is a bound fast bridge contract that returns the address of the contract.
//
//nolint:golint
type BridgeTransactionV2Ref struct {
*BridgeTransactionV2Harness
address common.Address
}

// Address gets the ocntract address.
func (f *BridgeTransactionV2Ref) Address() common.Address {
return f.address
}

// NewBridgeTransactionV2Ref creates a new fast bridge mock contract with a ref.
func NewBridgeTransactionV2Ref(address common.Address, backend bind.ContractBackend) (*BridgeTransactionV2Ref, error) {
bridgetransactionv2, err := NewBridgeTransactionV2Harness(address, backend)
if err != nil {
return nil, err
}

return &BridgeTransactionV2Ref{
BridgeTransactionV2Harness: bridgetransactionv2,
address: address,
}, nil
}

var _ vm.ContractRef = &BridgeTransactionV2Ref{}
110 changes: 110 additions & 0 deletions services/rfq/e2e/rfq_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,19 @@ import (
cctpTest "github.com/synapsecns/sanguine/services/cctp-relayer/testutil"
omnirpcClient "github.com/synapsecns/sanguine/services/omnirpc/client"
"github.com/synapsecns/sanguine/services/rfq/api/client"
"github.com/synapsecns/sanguine/services/rfq/contracts/bridgetransactionv2"
"github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge"
"github.com/synapsecns/sanguine/services/rfq/contracts/fastbridgev2"
"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"
"github.com/synapsecns/sanguine/services/rfq/testutil"
"github.com/synapsecns/sanguine/services/rfq/util"
"golang.org/x/sync/errgroup"

"github.com/brianvoe/gofakeit/v6"
)

type IntegrationSuite struct {
Expand Down Expand Up @@ -403,6 +407,8 @@ func (i *IntegrationSuite) TestZap() {
_ = i.guard.Start(i.GetTestContext())
}()

fmt.Printf("omnirpc url: %s\n", i.destBackend.RPCAddress())

// load token contracts
const startAmount = 1000
const rfqAmount = 900
Expand Down Expand Up @@ -868,3 +874,107 @@ func (i *IntegrationSuite) TestConcurrentBridges() {
return true
})
}

//nolint:gosec
func (i *IntegrationSuite) TestEncodeBridgeTransactionParity() {
_, handle := i.manager.GetBridgeTransactionV2(i.GetTestContext(), i.originBackend)

mockAddress := func() common.Address {
// Generate 20 random bytes for the address
b := make([]byte, 20)
for i := range b {
b[i] = byte(gofakeit.Number(0, 255))
}
return common.BytesToAddress(b)
}

// Generate random values that will be used for both transactions
originChainId := uint32(gofakeit.Number(1, 1000000))
destChainId := uint32(gofakeit.Number(1, 1000000))
originSender := mockAddress()
destRecipient := mockAddress()
originToken := mockAddress()
destToken := mockAddress()
originAmount := new(big.Int).SetUint64(gofakeit.Uint64())
destAmount := new(big.Int).SetUint64(gofakeit.Uint64())
originFeeAmount := new(big.Int).SetUint64(gofakeit.Uint64())
deadline := new(big.Int).SetUint64(gofakeit.Uint64())
nonce := new(big.Int).SetUint64(gofakeit.Uint64())
exclusivityRelayer := mockAddress()
exclusivityEndTime := new(big.Int).SetUint64(gofakeit.Uint64())
zapNative := new(big.Int).SetUint64(gofakeit.Uint64())

// Random size and values for zapData
zapDataSize := gofakeit.Number(0, 1000)
zapData := make([]byte, zapDataSize)
for i := range zapDataSize {
zapData[i] = gofakeit.Uint8()
}

// Create first transaction
bridgeTx := bridgetransactionv2.IFastBridgeV2BridgeTransactionV2{
OriginChainId: originChainId,
DestChainId: destChainId,
OriginSender: originSender,
DestRecipient: destRecipient,
OriginToken: originToken,
DestToken: destToken,
OriginAmount: originAmount,
DestAmount: destAmount,
OriginFeeAmount: originFeeAmount,
Deadline: deadline,
Nonce: nonce,
ExclusivityRelayer: exclusivityRelayer,
ExclusivityEndTime: exclusivityEndTime,
ZapNative: zapNative,
ZapData: zapData,
}

// Create second transaction with same values
tx := fastbridgev2.IFastBridgeV2BridgeTransactionV2{
OriginChainId: originChainId,
DestChainId: destChainId,
OriginSender: originSender,
DestRecipient: destRecipient,
OriginToken: originToken,
DestToken: destToken,
OriginAmount: originAmount,
DestAmount: destAmount,
OriginFeeAmount: originFeeAmount,
Deadline: deadline,
Nonce: nonce,
ExclusivityRelayer: exclusivityRelayer,
ExclusivityEndTime: exclusivityEndTime,
ZapNative: zapNative,
ZapData: zapData,
}

expectedEncoded, err := handle.EncodeV2(&bind.CallOpts{Context: i.GetTestContext()}, bridgeTx)
i.NoError(err)

encoded, err := chain.EncodeBridgeTx(tx)
i.NoError(err)

i.Equal(expectedEncoded, encoded)

// Test decoding
decodedTx, err := chain.DecodeBridgeTx(encoded)
i.NoError(err)

// Verify all fields match the original transaction
i.Equal(tx.OriginChainId, decodedTx.OriginChainId)
i.Equal(tx.DestChainId, decodedTx.DestChainId)
i.Equal(tx.OriginSender, decodedTx.OriginSender)
i.Equal(tx.DestRecipient, decodedTx.DestRecipient)
i.Equal(tx.OriginToken, decodedTx.OriginToken)
i.Equal(tx.DestToken, decodedTx.DestToken)
i.Equal(tx.OriginAmount.String(), decodedTx.OriginAmount.String())
i.Equal(tx.DestAmount.String(), decodedTx.DestAmount.String())
i.Equal(tx.OriginFeeAmount.String(), decodedTx.OriginFeeAmount.String())
i.Equal(tx.Deadline.String(), decodedTx.Deadline.String())
i.Equal(tx.Nonce.String(), decodedTx.Nonce.String())
i.Equal(tx.ExclusivityRelayer, decodedTx.ExclusivityRelayer)
i.Equal(tx.ExclusivityEndTime.String(), decodedTx.ExclusivityEndTime.String())
i.Equal(tx.ZapNative.String(), decodedTx.ZapNative.String())
i.Equal(tx.ZapData, decodedTx.ZapData)
}
113 changes: 113 additions & 0 deletions services/rfq/relayer/chain/encoding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package chain

import (
"encoding/binary"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/synapsecns/sanguine/services/rfq/contracts/fastbridgev2"
)

const (
// Field sizes in bytes.
sizeVersion = 2
sizeChainID = 4
sizeAddress = 20
sizeUint256 = 32

// Field offsets in bytes.
offsetVersion = 0
offsetOriginChainID = offsetVersion + sizeVersion
offsetDestChainID = offsetOriginChainID + sizeChainID
offsetOriginSender = offsetDestChainID + sizeChainID
offsetDestRecipient = offsetOriginSender + sizeAddress
offsetOriginToken = offsetDestRecipient + sizeAddress
offsetDestToken = offsetOriginToken + sizeAddress
offsetOriginAmount = offsetDestToken + sizeAddress
offsetDestAmount = offsetOriginAmount + sizeUint256
offsetOriginFeeAmount = offsetDestAmount + sizeUint256
offsetDeadline = offsetOriginFeeAmount + sizeUint256
offsetNonce = offsetDeadline + sizeUint256
offsetExclusivityRelayer = offsetNonce + sizeUint256
offsetExclusivityEndTime = offsetExclusivityRelayer + sizeAddress
offsetZapNative = offsetExclusivityEndTime + sizeUint256
offsetZapData = offsetZapNative + sizeUint256
)

// Helper function to properly encode uint256.
func padUint256(b *big.Int) []byte {
// Convert big.Int to bytes
bytes := b.Bytes()
// Create 32-byte array (initialized to zeros)
result := make([]byte, 32)
// Copy bytes to right side of array (left-pad with zeros)
copy(result[32-len(bytes):], bytes)
return result
}

// EncodeBridgeTx encodes a bridge transaction into a byte array.
func EncodeBridgeTx(tx fastbridgev2.IFastBridgeV2BridgeTransactionV2) ([]byte, error) {
// Initialize with total size including ZapData
result := make([]byte, offsetZapData+len(tx.ZapData))

// Version
result[offsetVersion] = 0
result[offsetVersion+1] = 2

// Chain IDs
binary.BigEndian.PutUint32(result[offsetOriginChainID:offsetOriginChainID+sizeChainID], tx.OriginChainId)
binary.BigEndian.PutUint32(result[offsetDestChainID:offsetDestChainID+sizeChainID], tx.DestChainId)

// Addresses
copy(result[offsetOriginSender:offsetOriginSender+sizeAddress], tx.OriginSender.Bytes())
copy(result[offsetDestRecipient:offsetDestRecipient+sizeAddress], tx.DestRecipient.Bytes())
copy(result[offsetOriginToken:offsetOriginToken+sizeAddress], tx.OriginToken.Bytes())
copy(result[offsetDestToken:offsetDestToken+sizeAddress], tx.DestToken.Bytes())

// uint256 values
copy(result[offsetOriginAmount:offsetOriginAmount+sizeUint256], padUint256(tx.OriginAmount))
copy(result[offsetDestAmount:offsetDestAmount+sizeUint256], padUint256(tx.DestAmount))
copy(result[offsetOriginFeeAmount:offsetOriginFeeAmount+sizeUint256], padUint256(tx.OriginFeeAmount))
copy(result[offsetDeadline:offsetDeadline+sizeUint256], padUint256(tx.Deadline))
copy(result[offsetNonce:offsetNonce+sizeUint256], padUint256(tx.Nonce))

// Exclusivity address
copy(result[offsetExclusivityRelayer:offsetExclusivityRelayer+sizeAddress], tx.ExclusivityRelayer.Bytes())

// More uint256 values
copy(result[offsetExclusivityEndTime:offsetExclusivityEndTime+sizeUint256], padUint256(tx.ExclusivityEndTime))
copy(result[offsetZapNative:offsetZapNative+sizeUint256], padUint256(tx.ZapNative))

// Replace append with copy for ZapData
copy(result[offsetZapData:], tx.ZapData)

return result, nil
}

// DecodeBridgeTx decodes a byte array into a bridge transaction.
func DecodeBridgeTx(data []byte) (fastbridgev2.IFastBridgeV2BridgeTransactionV2, error) {
if len(data) < offsetZapData {
return fastbridgev2.IFastBridgeV2BridgeTransactionV2{}, fmt.Errorf("data too short: got %d bytes, need at least %d", len(data), offsetZapData)
}

tx := fastbridgev2.IFastBridgeV2BridgeTransactionV2{
OriginChainId: binary.BigEndian.Uint32(data[offsetOriginChainID:offsetDestChainID]),
DestChainId: binary.BigEndian.Uint32(data[offsetDestChainID:offsetOriginSender]),
OriginSender: common.BytesToAddress(data[offsetOriginSender:offsetDestRecipient]),
DestRecipient: common.BytesToAddress(data[offsetDestRecipient:offsetOriginToken]),
OriginToken: common.BytesToAddress(data[offsetOriginToken:offsetDestToken]),
DestToken: common.BytesToAddress(data[offsetDestToken:offsetOriginAmount]),
OriginAmount: new(big.Int).SetBytes(data[offsetOriginAmount:offsetDestAmount]),
DestAmount: new(big.Int).SetBytes(data[offsetDestAmount:offsetOriginFeeAmount]),
OriginFeeAmount: new(big.Int).SetBytes(data[offsetOriginFeeAmount:offsetDeadline]),
Deadline: new(big.Int).SetBytes(data[offsetDeadline:offsetNonce]),
Nonce: new(big.Int).SetBytes(data[offsetNonce:offsetExclusivityRelayer]),
ExclusivityRelayer: common.BytesToAddress(data[offsetExclusivityRelayer:offsetExclusivityEndTime]),
ExclusivityEndTime: new(big.Int).SetBytes(data[offsetExclusivityEndTime:offsetZapNative]),
ZapNative: new(big.Int).SetBytes(data[offsetZapNative:offsetZapData]),
ZapData: data[offsetZapData:],
}

return tx, nil
}
Loading

0 comments on commit c1f57ef

Please sign in to comment.