-
Notifications
You must be signed in to change notification settings - Fork 33
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
fix(rfq-relayer): gas estimation for zaps #3413
Changes from 9 commits
603e42f
cd70e74
d1796a1
f557248
fdeff59
1589b83
6ae2731
3858f68
cc1cd2e
5c5c557
e0f3e55
78a3293
bde24d6
20a2e82
c59425c
e8db2fe
3783b2c
a65d861
89e8bb7
1e05972
8d8491a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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.
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/BridgeTransactionV2.t.sol --pkg bridgetransactionv2 --sol-version 0.8.24 --filename bridgetransactionv2 --evm-version istanbul |
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{} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package chain | ||
|
||
import ( | ||
"encoding/binary" | ||
"math/big" | ||
|
||
"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 | ||
} | ||
|
||
// EncodeQuoteRequest encodes a quote request into a byte array. | ||
func EncodeQuoteRequest(tx fastbridgev2.IFastBridgeV2BridgeTransactionV2) ([]byte, error) { | ||
result := make([]byte, offsetZapData) | ||
|
||
// 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)) | ||
|
||
// Append ZapData | ||
result = append(result, tx.ZapData...) | ||
|
||
return result, nil | ||
} | ||
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -381,16 +381,11 @@ | |||||||||||||||||||||
OriginAmountExact: originAmountExact, | ||||||||||||||||||||||
} | ||||||||||||||||||||||
if rfqRequest.Data.ZapNative != "" || rfqRequest.Data.ZapData != "" { | ||||||||||||||||||||||
zapNative, ok := new(big.Int).SetString(rfqRequest.Data.ZapNative, 10) | ||||||||||||||||||||||
if !ok { | ||||||||||||||||||||||
return nil, fmt.Errorf("invalid zap native amount: %s", rfqRequest.Data.ZapNative) | ||||||||||||||||||||||
} | ||||||||||||||||||||||
quoteInput.QuoteRequest = &reldb.QuoteRequest{ | ||||||||||||||||||||||
Transaction: fastbridgev2.IFastBridgeV2BridgeTransactionV2{ | ||||||||||||||||||||||
ZapNative: zapNative, | ||||||||||||||||||||||
ZapData: []byte(rfqRequest.Data.ZapData), | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
quoteRequest, err := quoteDataToQuoteRequestV2(&rfqRequest.Data) | ||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||
return nil, fmt.Errorf("error converting quote data to quote request: %w", err) | ||||||||||||||||||||||
} | ||||||||||||||||||||||
quoteInput.QuoteRequest = quoteRequest | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
rawQuote, err := m.generateQuote(ctx, quoteInput) | ||||||||||||||||||||||
|
@@ -432,6 +427,49 @@ | |||||||||||||||||||||
return resp, nil | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
func quoteDataToQuoteRequestV2(quoteData *model.QuoteData) (*reldb.QuoteRequest, error) { | ||||||||||||||||||||||
if quoteData == nil { | ||||||||||||||||||||||
return nil, errors.New("quote data is nil") | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
originAmount, ok := new(big.Int).SetString(quoteData.OriginAmountExact, 10) | ||||||||||||||||||||||
if !ok { | ||||||||||||||||||||||
return nil, errors.New("invalid origin amount") | ||||||||||||||||||||||
} | ||||||||||||||||||||||
destAmount := originAmount // assume dest amount same as origin amount for estimation purposes | ||||||||||||||||||||||
originFeeAmount := big.NewInt(0) | ||||||||||||||||||||||
nonce := big.NewInt(0) | ||||||||||||||||||||||
exclusivityEndTime := big.NewInt(0) | ||||||||||||||||||||||
zapNative, ok := new(big.Int).SetString(quoteData.ZapNative, 10) | ||||||||||||||||||||||
if !ok { | ||||||||||||||||||||||
return nil, errors.New("invalid zap native") | ||||||||||||||||||||||
} | ||||||||||||||||||||||
deadline := new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil), big.NewInt(1)) | ||||||||||||||||||||||
exclusivityRelayer := common.HexToAddress("") | ||||||||||||||||||||||
|
||||||||||||||||||||||
quoteRequest := &reldb.QuoteRequest{ | ||||||||||||||||||||||
Transaction: fastbridgev2.IFastBridgeV2BridgeTransactionV2{ | ||||||||||||||||||||||
OriginChainId: uint32(quoteData.OriginChainID), | ||||||||||||||||||||||
DestChainId: uint32(quoteData.DestChainID), | ||||||||||||||||||||||
Comment on lines
+453
to
+454
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential integer overflow when converting Casting Apply this diff to add validation: + if quoteData.OriginChainID < 0 || quoteData.OriginChainID > math.MaxUint32 {
+ return nil, fmt.Errorf("OriginChainID out of range: %d", quoteData.OriginChainID)
+ }
+ if quoteData.DestChainID < 0 || quoteData.DestChainID > math.MaxUint32 {
+ return nil, fmt.Errorf("DestChainID out of range: %d", quoteData.DestChainID)
+ }
quoteRequest := &reldb.QuoteRequest{
Transaction: fastbridgev2.IFastBridgeV2BridgeTransactionV2{
OriginChainId: uint32(quoteData.OriginChainID),
DestChainId: uint32(quoteData.DestChainID), Alternatively, if
🧰 Tools🪛 GitHub Check: Lint (services/rfq)[failure] 452-452: [failure] 453-453: |
||||||||||||||||||||||
OriginSender: common.HexToAddress(quoteData.OriginSender), | ||||||||||||||||||||||
DestRecipient: common.HexToAddress(quoteData.DestRecipient), | ||||||||||||||||||||||
Comment on lines
+455
to
+456
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate sender and recipient addresses. The function should validate that the sender and recipient addresses are not zero addresses to prevent potential issues with transactions. + if quoteData.OriginSender == "" || common.HexToAddress(quoteData.OriginSender) == (common.Address{}) {
+ return nil, errors.New("invalid origin sender address")
+ }
+ if quoteData.DestRecipient == "" || common.HexToAddress(quoteData.DestRecipient) == (common.Address{}) {
+ return nil, errors.New("invalid destination recipient address")
+ } 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||
OriginToken: common.HexToAddress(quoteData.OriginTokenAddr), | ||||||||||||||||||||||
DestToken: common.HexToAddress(quoteData.DestTokenAddr), | ||||||||||||||||||||||
OriginAmount: originAmount, | ||||||||||||||||||||||
DestAmount: destAmount, | ||||||||||||||||||||||
OriginFeeAmount: originFeeAmount, | ||||||||||||||||||||||
Deadline: deadline, | ||||||||||||||||||||||
Nonce: nonce, | ||||||||||||||||||||||
ExclusivityRelayer: exclusivityRelayer, | ||||||||||||||||||||||
ExclusivityEndTime: exclusivityEndTime, | ||||||||||||||||||||||
ZapNative: zapNative, | ||||||||||||||||||||||
ZapData: []byte(quoteData.ZapData), | ||||||||||||||||||||||
}, | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
return quoteRequest, nil | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
// GetPrice gets the price of a token. | ||||||||||||||||||||||
func (m *Manager) GetPrice(parentCtx context.Context, tokenName string) (_ float64, err error) { | ||||||||||||||||||||||
ctx, span := m.metricsHandler.Tracer().Start(parentCtx, "GetPrice") | ||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import ( | |
"github.com/ethereum/go-ethereum/common/compiler" | ||
"github.com/synapsecns/sanguine/ethergo/backends/base" | ||
"github.com/synapsecns/sanguine/ethergo/contracts" | ||
"github.com/synapsecns/sanguine/services/rfq/contracts/bridgetransactionv2" | ||
"github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" | ||
"github.com/synapsecns/sanguine/services/rfq/contracts/testcontracts/dai" | ||
"github.com/synapsecns/sanguine/services/rfq/contracts/testcontracts/fastbridgemockv2" | ||
|
@@ -58,6 +59,8 @@ const ( | |
FastBridgeMockType // FastBridgeMock | ||
// RecipientMockType is a mock contract for testing fast bridge interactions. | ||
RecipientMockType // RecipientMock | ||
// BridgeTransactionV2Type is a bridge transaction contract for testing fast bridge interactions. | ||
BridgeTransactionV2Type // BridgeTransactionV2 | ||
Comment on lines
+62
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Update stringer for the new BridgeTransactionV2Type contract type The stringer generation is not up to date with the newly added contract type. Please run: go generate ./... to update the stringer implementation and include the BridgeTransactionV2Type in the String() method. 🔗 Analysis chainVerify stringer generation for the new contract type The new contract type is properly defined and documented. Since this file uses stringer generation, ensure you run: go generate ./... 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Verify if stringer has been updated for the new type
rg -A 1 "func.*String.*contractTypeImpl" | grep -q "BridgeTransactionV2"
if [ $? -eq 0 ]; then
echo "Stringer is up to date"
else
echo "Stringer needs to be updated"
fi
Length of output: 157 |
||
// WETH9Type is the weth 9 contract. | ||
WETH9Type // WETH9 | ||
// USDTType is the tether type. | ||
|
@@ -100,6 +103,8 @@ func (c contractTypeImpl) ContractInfo() *compiler.Contract { | |
return fastbridgemockv2.Contracts["solidity/FastBridgeMock.sol:FastBridgeMock"] | ||
case RecipientMockType: | ||
return recipientmock.Contracts["solidity/RecipientMock.sol:RecipientMock"] | ||
case BridgeTransactionV2Type: | ||
return bridgetransactionv2.Contracts["solidity/BridgeTransactionV2.t.sol:BridgeTransactionV2Lib"] | ||
case WETH9Type: | ||
return weth9.Contracts["/solidity/WETH9.sol:WETH9"] | ||
case USDTType: | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add security validations for critical transaction fields.
The function should validate critical transaction fields to prevent potential security issues.
Consider adding these validations:
Example implementation:
🧰 Tools
🪛 golangci-lint (1.62.2)
80-80: append to slice
result
with non-zero initialized length(makezero)
🪛 GitHub Check: Lint (services/rfq)
[failure] 80-80:
append to slice
result
with non-zero initialized length (makezero)🛠️ Refactor suggestion
Add input validation and optimize slice operations.
The encoding logic is well-structured, but there are a few improvements that could be made:
Apply these changes:
📝 Committable suggestion
🧰 Tools
🪛 golangci-lint (1.62.2)
80-80: append to slice
result
with non-zero initialized length(makezero)
🪛 GitHub Check: Lint (services/rfq)
[failure] 80-80:
append to slice
result
with non-zero initialized length (makezero)