diff --git a/core/ginhelper/server.go b/core/ginhelper/server.go index 3658296298..bdad5a10cf 100644 --- a/core/ginhelper/server.go +++ b/core/ginhelper/server.go @@ -85,18 +85,24 @@ func NewWithExperimentalLogger(ctx context.Context, logger logger.ExperimentalLo return server } +// TODO: this is an anti-pattern and needs to be replaced by an option asap. +var CorsEnabled = true + func newBase() *gin.Engine { server := gin.New() // required for opentracing. server.ContextWithFallback = true - server.Use(helmet.Default()) + server.Use(gin.Recovery()) - server.Use(cors.New(cors.Config{ - AllowAllOrigins: true, - AllowHeaders: []string{"*"}, - AllowMethods: []string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodPatch, http.MethodDelete, http.MethodOptions}, - MaxAge: 12 * time.Hour, - })) + if CorsEnabled { + server.Use(helmet.Default()) + server.Use(cors.New(cors.Config{ + AllowAllOrigins: true, + AllowHeaders: []string{"*"}, + AllowMethods: []string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodPatch, http.MethodDelete, http.MethodOptions}, + MaxAge: 12 * time.Hour, + })) + } // configure the request id server.Use(requestid.New( diff --git a/core/metrics/README.md b/core/metrics/README.md index 5f60dfbfe6..19582c21c5 100644 --- a/core/metrics/README.md +++ b/core/metrics/README.md @@ -61,6 +61,14 @@ The metrics endpoint is exposed on `/metrics` on port `8080` by default and is c **Note: this server failing to bind to `METRICS_PORT` will not cause the application to fail to start. The error will be logged.** +Most metrics come with a `# HELP` explanation that explains them, for example: + +```promql +# HELP process_uptime_seconds The uptime of the process in seconds +# TYPE process_uptime_seconds gauge +process_uptime_seconds{otel_scope_name="standard_metrics",otel_scope_version=""} 24.241680459 +``` + ## Logger Currently, the entire sanguine codebase uses [ipfs go-log]("https://github.com/ipfs/go-log"). As pointed out in [#1521](https://github.com/synapsecns/sanguine/issues/1521), this is not a good long term solution since the logs are not currently appended to opentelemetry, and so new traces require telemtry. @@ -80,3 +88,4 @@ Note: because both [ipfs go-log]("https://github.com/ipfs/go-log") and [otelzap ### Using the logger Since the logger is dependent on the `context` to derive the current span, you need to always use `logger.Ctx(ctx)` or `logger.InfoCtx`. One thing under consideration is removing the non-ctx methods + diff --git a/core/metrics/base.go b/core/metrics/base.go index 7609b2d9d8..b2b2210b24 100644 --- a/core/metrics/base.go +++ b/core/metrics/base.go @@ -3,8 +3,6 @@ package metrics import ( "context" "fmt" - "net/http" - "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/synapsecns/sanguine/core" @@ -30,6 +28,7 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.26.0" "go.opentelemetry.io/otel/trace" "gorm.io/gorm" + "net/http" ) const pyroscopeEndpoint = internal.PyroscopeEndpoint @@ -44,7 +43,8 @@ type baseHandler struct { tracer trace.Tracer name string propagator propagation.TextMapPropagator - meter MeterProvider + // Deprecated: will be removed in a future version + meter MeterProvider // handler is an integrated handler for everything exported over http. This includes prometheus // or http-based sampling methods for other providers. handler http.Handler @@ -78,6 +78,8 @@ func (b *baseHandler) Start(ctx context.Context) error { otel.SetMeterProvider(b.meter) b.handler = promhttp.Handler() + newStandardMetrics(ctx, b) + go func() { <-ctx.Done() // shutting down this way will not flush. diff --git a/core/metrics/standard.go b/core/metrics/standard.go new file mode 100644 index 0000000000..12e2477d9a --- /dev/null +++ b/core/metrics/standard.go @@ -0,0 +1,41 @@ +package metrics + +import ( + "context" + "go.opentelemetry.io/otel/metric" + "time" +) + +// standardMetrics records metrics across any service using the metrics handler. +type standardMetrics struct { + metrics Handler + meter metric.Meter + uptimeGauge metric.Float64ObservableGauge + startTime time.Time +} + +const processUptimeSecondsMetric = "process_uptime_seconds" + +func newStandardMetrics(ctx context.Context, handler Handler) { + str := standardMetrics{ + metrics: handler, + meter: handler.Meter("standard_metrics"), + startTime: time.Now(), + } + + var err error + if str.uptimeGauge, err = str.meter.Float64ObservableGauge(processUptimeSecondsMetric, metric.WithDescription("The uptime of the process in seconds"), metric.WithUnit("seconds")); err != nil { + handler.ExperimentalLogger().Errorf(ctx, "failed to create %s gauge: %v", processUptimeSecondsMetric, err) + } + + // Register callback + if _, err = str.meter.RegisterCallback(str.uptimeCallback, str.uptimeGauge); err != nil { + handler.ExperimentalLogger().Warnf(ctx, "failed to register callback: %v", err) + } +} + +func (str *standardMetrics) uptimeCallback(_ context.Context, observer metric.Observer) error { + uptimeDuration := time.Since(str.startTime).Seconds() + observer.ObserveFloat64(str.uptimeGauge, uptimeDuration) + return nil +} diff --git a/docs/bridge/CHANGELOG.md b/docs/bridge/CHANGELOG.md index c692d12f9d..6cf3e196cd 100644 --- a/docs/bridge/CHANGELOG.md +++ b/docs/bridge/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.4.5](https://github.com/synapsecns/sanguine/compare/@synapsecns/bridge-docs@0.4.4...@synapsecns/bridge-docs@0.4.5) (2024-10-21) + +**Note:** Version bump only for package @synapsecns/bridge-docs + + + + + ## [0.4.4](https://github.com/synapsecns/sanguine/compare/@synapsecns/bridge-docs@0.4.3...@synapsecns/bridge-docs@0.4.4) (2024-10-15) **Note:** Version bump only for package @synapsecns/bridge-docs diff --git a/docs/bridge/package.json b/docs/bridge/package.json index 7b01af0b92..dee8a12c74 100644 --- a/docs/bridge/package.json +++ b/docs/bridge/package.json @@ -1,6 +1,6 @@ { "name": "@synapsecns/bridge-docs", - "version": "0.4.4", + "version": "0.4.5", "private": true, "scripts": { "docusaurus": "docusaurus", @@ -34,7 +34,7 @@ "@docusaurus/utils-validation": "3.5.2", "@easyops-cn/docusaurus-search-local": "^0.44.5", "@mdx-js/react": "^3.0.0", - "@synapsecns/synapse-constants": "^1.7.0", + "@synapsecns/synapse-constants": "^1.8.0", "clsx": "^2.0.0", "docusaurus-plugin-openapi-docs": "^4.0.1", "docusaurus-theme-openapi-docs": "^4.0.1", diff --git a/packages/contracts-rfq/.env.example b/packages/contracts-rfq/.env.example index ac82d33df1..84cdc83bab 100644 --- a/packages/contracts-rfq/.env.example +++ b/packages/contracts-rfq/.env.example @@ -57,6 +57,11 @@ SCROLL_RPC=https://rpc.scroll.io SCROLL_VERIFIER=etherscan SCROLL_VERIFIER_URL=https://api.scrollscan.com/api SCROLL_VERIFIER_KEY=YourScrollScanKey +# World Chain +WORLDCHAIN_RPC=https://worldchain-mainnet.g.alchemy.com/public +WORLDCHAIN_VERIFIER=etherscan +WORLDCHAIN_VERIFIER_URL=https://api.worldscan.org/api +WORLDCHAIN_VERIFIER_KEY=YourWorldScanKey # TESTNET CHAINS # Arbitrum Sepolia diff --git a/packages/contracts-rfq/CHANGELOG.md b/packages/contracts-rfq/CHANGELOG.md index 1aac1ff866..f6c4b0a6b9 100644 --- a/packages/contracts-rfq/CHANGELOG.md +++ b/packages/contracts-rfq/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.9.3](https://github.com/synapsecns/sanguine/compare/@synapsecns/contracts-rfq@0.9.2...@synapsecns/contracts-rfq@0.9.3) (2024-10-24) + +**Note:** Version bump only for package @synapsecns/contracts-rfq + + + + + +## [0.9.2](https://github.com/synapsecns/sanguine/compare/@synapsecns/contracts-rfq@0.9.1...@synapsecns/contracts-rfq@0.9.2) (2024-10-22) + +**Note:** Version bump only for package @synapsecns/contracts-rfq + + + + + ## [0.9.1](https://github.com/synapsecns/sanguine/compare/@synapsecns/contracts-rfq@0.9.0...@synapsecns/contracts-rfq@0.9.1) (2024-10-18) **Note:** Version bump only for package @synapsecns/contracts-rfq diff --git a/packages/contracts-rfq/contracts/FastBridgeV2.sol b/packages/contracts-rfq/contracts/FastBridgeV2.sol index daa243ac7c..9f54c87f42 100644 --- a/packages/contracts-rfq/contracts/FastBridgeV2.sol +++ b/packages/contracts-rfq/contracts/FastBridgeV2.sol @@ -83,16 +83,16 @@ contract FastBridgeV2 is Admin, IFastBridgeV2, IFastBridgeV2Errors { /// @inheritdoc IFastBridge function dispute(bytes32 transactionId) external onlyRole(GUARD_ROLE) { BridgeTxDetails storage $ = bridgeTxDetails[transactionId]; - + // Aggregate the read operations from the same storage slot address disputedRelayer = $.proofRelayer; BridgeStatus status = $.status; uint40 proofBlockTimestamp = $.proofBlockTimestamp; - + // Can only dispute a RELAYER_PROVED transaction within the dispute period if (status != BridgeStatus.RELAYER_PROVED) revert StatusIncorrect(); if (_timeSince(proofBlockTimestamp) > DISPUTE_PERIOD) { revert DisputePeriodPassed(); } - + // Update status to REQUESTED and delete the disputed proof details // Note: these are storage writes $.status = BridgeStatus.REQUESTED; $.proofRelayer = address(0); @@ -107,33 +107,34 @@ contract FastBridgeV2 is Admin, IFastBridgeV2, IFastBridgeV2Errors { request.validateV2(); bytes32 transactionId = keccak256(request); BridgeTxDetails storage $ = bridgeTxDetails[transactionId]; - + // Can only refund a REQUESTED transaction after its deadline expires if ($.status != BridgeStatus.REQUESTED) revert StatusIncorrect(); - uint256 deadline = request.deadline(); - // Permissionless refund is allowed after REFUND_DELAY + // Permissionless refund is only allowed after REFUND_DELAY on top of the deadline if (!hasRole(REFUNDER_ROLE, msg.sender)) deadline += REFUND_DELAY; if (block.timestamp <= deadline) revert DeadlineNotExceeded(); + // Update status to REFUNDED and return the full amount (collateral + protocol fees) to the original sender. + // The protocol fees are only updated when the transaction is claimed, so we don't need to update them here. // Note: this is a storage write $.status = BridgeStatus.REFUNDED; - // transfer origin collateral back to original sender address to = request.originSender(); address token = request.originToken(); uint256 amount = request.originAmount() + request.originFeeAmount(); + // Emit the event before any external calls + emit BridgeDepositRefunded(transactionId, to, token, amount); + // Complete the user refund as the last transaction action if (token == UniversalTokenLib.ETH_ADDRESS) { Address.sendValue(payable(to), amount); } else { IERC20(token).safeTransfer(to, amount); } - - emit BridgeDepositRefunded(transactionId, to, token, amount); } /// @inheritdoc IFastBridge function canClaim(bytes32 transactionId, address relayer) external view returns (bool) { BridgeTxDetails storage $ = bridgeTxDetails[transactionId]; - + // The correct relayer can only claim a RELAYER_PROVED transaction after the dispute period if ($.status != BridgeStatus.RELAYER_PROVED) revert StatusIncorrect(); if ($.proofRelayer != relayer) revert SenderIncorrect(); return _timeSince($.proofBlockTimestamp) > DISPUTE_PERIOD; @@ -288,8 +289,9 @@ contract FastBridgeV2 is Admin, IFastBridgeV2, IFastBridgeV2Errors { function prove(bytes32 transactionId, bytes32 destTxHash, address relayer) public onlyRole(RELAYER_ROLE) { BridgeTxDetails storage $ = bridgeTxDetails[transactionId]; - // update bridge tx status given proof provided + // Can only prove a REQUESTED transaction if ($.status != BridgeStatus.REQUESTED) revert StatusIncorrect(); + // Update status to RELAYER_PROVED and store the proof details // Note: these are storage writes $.status = BridgeStatus.RELAYER_PROVED; $.proofBlockTimestamp = uint40(block.timestamp); @@ -304,47 +306,50 @@ contract FastBridgeV2 is Admin, IFastBridgeV2, IFastBridgeV2Errors { request.validateV2(); bytes32 transactionId = keccak256(request); BridgeTxDetails storage $ = bridgeTxDetails[transactionId]; - + // Aggregate the read operations from the same storage slot address proofRelayer = $.proofRelayer; BridgeStatus status = $.status; uint40 proofBlockTimestamp = $.proofBlockTimestamp; - // update bridge tx status if able to claim origin collateral + // Can only claim a RELAYER_PROVED transaction after the dispute period if (status != BridgeStatus.RELAYER_PROVED) revert StatusIncorrect(); + if (_timeSince(proofBlockTimestamp) <= DISPUTE_PERIOD) { + revert DisputePeriodNotPassed(); + } - // if "to" is zero addr, permissionlessly send funds to proven relayer if (to == address(0)) { + // Anyone could claim the funds to the proven relayer on their behalf to = proofRelayer; } else if (proofRelayer != msg.sender) { + // Only the proven relayer could specify an address to claim the funds to revert SenderIncorrect(); } - if (_timeSince(proofBlockTimestamp) <= DISPUTE_PERIOD) { - revert DisputePeriodNotPassed(); - } + // Update status to RELAYER_CLAIMED and transfer the origin collateral to the specified claim address // Note: this is a storage write $.status = BridgeStatus.RELAYER_CLAIMED; - // update protocol fees if origin fee amount exists address token = request.originToken(); uint256 amount = request.originAmount(); + // Update protocol fees if origin fee amount exists uint256 originFeeAmount = request.originFeeAmount(); if (originFeeAmount > 0) protocolFees[token] += originFeeAmount; - - // transfer origin collateral to specified address (protocol fee was pre-deducted at deposit) + // Emit the event before any external calls + emit BridgeDepositClaimed(transactionId, proofRelayer, to, token, amount); + // Complete the relayer claim as the last transaction action if (token == UniversalTokenLib.ETH_ADDRESS) { Address.sendValue(payable(to), amount); } else { IERC20(token).safeTransfer(to, amount); } - - emit BridgeDepositClaimed(transactionId, proofRelayer, to, token, amount); } + /// @inheritdoc IFastBridgeV2 function bridgeStatuses(bytes32 transactionId) public view returns (BridgeStatus status) { return bridgeTxDetails[transactionId].status; } + /// @inheritdoc IFastBridgeV2 function bridgeProofs(bytes32 transactionId) public view returns (uint96 timestamp, address relayer) { BridgeTxDetails storage $ = bridgeTxDetails[transactionId]; diff --git a/packages/contracts-rfq/deployments/worldchain/.chainId b/packages/contracts-rfq/deployments/worldchain/.chainId new file mode 100644 index 0000000000..7ad8022502 --- /dev/null +++ b/packages/contracts-rfq/deployments/worldchain/.chainId @@ -0,0 +1 @@ +480 \ No newline at end of file diff --git a/packages/contracts-rfq/deployments/worldchain/FastBridge.json b/packages/contracts-rfq/deployments/worldchain/FastBridge.json new file mode 100644 index 0000000000..455272aada --- /dev/null +++ b/packages/contracts-rfq/deployments/worldchain/FastBridge.json @@ -0,0 +1,894 @@ +{ + "address": "0x05C62156C7C47E76223A560210EA648De5e6B53B", + "constructorArgs": "0x000000000000000000000000bd88862fcc17de436f7bd17276c537acadda9a67", + "receipt": { + "hash": "0x5b273e0b194c18ede68d16d42c38fb213f535ef5f73c5ff2373eca989624d16f", + "blockNumber": 4598830 + }, + "abi": [ + { + "type": "constructor", + "inputs": [ + { "name": "_owner", "type": "address", "internalType": "address" } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "DISPUTE_PERIOD", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "FEE_BPS", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "FEE_RATE_MAX", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "GOVERNOR_ROLE", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "GUARD_ROLE", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "MIN_DEADLINE_PERIOD", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "REFUNDER_ROLE", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "REFUND_DELAY", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "RELAYER_ROLE", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "bridge", + "inputs": [ + { + "name": "params", + "type": "tuple", + "internalType": "struct IFastBridge.BridgeParams", + "components": [ + { + "name": "dstChainId", + "type": "uint32", + "internalType": "uint32" + }, + { "name": "sender", "type": "address", "internalType": "address" }, + { "name": "to", "type": "address", "internalType": "address" }, + { + "name": "originToken", + "type": "address", + "internalType": "address" + }, + { + "name": "destToken", + "type": "address", + "internalType": "address" + }, + { + "name": "originAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "destAmount", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "sendChainGas", "type": "bool", "internalType": "bool" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" } + ] + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "bridgeProofs", + "inputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "outputs": [ + { "name": "timestamp", "type": "uint96", "internalType": "uint96" }, + { "name": "relayer", "type": "address", "internalType": "address" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "bridgeRelays", + "inputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "bridgeStatuses", + "inputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "outputs": [ + { + "name": "", + "type": "uint8", + "internalType": "enum FastBridge.BridgeStatus" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "canClaim", + "inputs": [ + { + "name": "transactionId", + "type": "bytes32", + "internalType": "bytes32" + }, + { "name": "relayer", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "chainGasAmount", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "claim", + "inputs": [ + { "name": "request", "type": "bytes", "internalType": "bytes" }, + { "name": "to", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "deployBlock", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "dispute", + "inputs": [ + { + "name": "transactionId", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "getBridgeTransaction", + "inputs": [ + { "name": "request", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct IFastBridge.BridgeTransaction", + "components": [ + { + "name": "originChainId", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "destChainId", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "originSender", + "type": "address", + "internalType": "address" + }, + { + "name": "destRecipient", + "type": "address", + "internalType": "address" + }, + { + "name": "originToken", + "type": "address", + "internalType": "address" + }, + { + "name": "destToken", + "type": "address", + "internalType": "address" + }, + { + "name": "originAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "destAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "originFeeAmount", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "sendChainGas", "type": "bool", "internalType": "bool" }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { "name": "nonce", "type": "uint256", "internalType": "uint256" } + ] + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "getRoleAdmin", + "inputs": [ + { "name": "role", "type": "bytes32", "internalType": "bytes32" } + ], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRoleMember", + "inputs": [ + { "name": "role", "type": "bytes32", "internalType": "bytes32" }, + { "name": "index", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRoleMemberCount", + "inputs": [ + { "name": "role", "type": "bytes32", "internalType": "bytes32" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "grantRole", + "inputs": [ + { "name": "role", "type": "bytes32", "internalType": "bytes32" }, + { "name": "account", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "hasRole", + "inputs": [ + { "name": "role", "type": "bytes32", "internalType": "bytes32" }, + { "name": "account", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "multicallNoResults", + "inputs": [ + { "name": "data", "type": "bytes[]", "internalType": "bytes[]" }, + { "name": "ignoreReverts", "type": "bool", "internalType": "bool" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "multicallWithResults", + "inputs": [ + { "name": "data", "type": "bytes[]", "internalType": "bytes[]" }, + { "name": "ignoreReverts", "type": "bool", "internalType": "bool" } + ], + "outputs": [ + { + "name": "results", + "type": "tuple[]", + "internalType": "struct IMulticallTarget.Result[]", + "components": [ + { "name": "success", "type": "bool", "internalType": "bool" }, + { "name": "returnData", "type": "bytes", "internalType": "bytes" } + ] + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "nonce", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "protocolFeeRate", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "protocolFees", + "inputs": [{ "name": "", "type": "address", "internalType": "address" }], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "prove", + "inputs": [ + { "name": "request", "type": "bytes", "internalType": "bytes" }, + { "name": "destTxHash", "type": "bytes32", "internalType": "bytes32" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "refund", + "inputs": [ + { "name": "request", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "relay", + "inputs": [ + { "name": "request", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "renounceRole", + "inputs": [ + { "name": "role", "type": "bytes32", "internalType": "bytes32" }, + { + "name": "callerConfirmation", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "revokeRole", + "inputs": [ + { "name": "role", "type": "bytes32", "internalType": "bytes32" }, + { "name": "account", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setChainGasAmount", + "inputs": [ + { + "name": "newChainGasAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setProtocolFeeRate", + "inputs": [ + { "name": "newFeeRate", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "supportsInterface", + "inputs": [ + { "name": "interfaceId", "type": "bytes4", "internalType": "bytes4" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "sweepProtocolFees", + "inputs": [ + { "name": "token", "type": "address", "internalType": "address" }, + { "name": "recipient", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "BridgeDepositClaimed", + "inputs": [ + { + "name": "transactionId", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "relayer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "token", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "BridgeDepositRefunded", + "inputs": [ + { + "name": "transactionId", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "token", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "BridgeProofDisputed", + "inputs": [ + { + "name": "transactionId", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "relayer", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "BridgeProofProvided", + "inputs": [ + { + "name": "transactionId", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "relayer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "transactionHash", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "BridgeRelayed", + "inputs": [ + { + "name": "transactionId", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "relayer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "originChainId", + "type": "uint32", + "indexed": false, + "internalType": "uint32" + }, + { + "name": "originToken", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "destToken", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "originAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "destAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "chainGasAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "BridgeRequested", + "inputs": [ + { + "name": "transactionId", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "request", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + }, + { + "name": "destChainId", + "type": "uint32", + "indexed": false, + "internalType": "uint32" + }, + { + "name": "originToken", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "destToken", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "originAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "destAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "sendChainGas", + "type": "bool", + "indexed": false, + "internalType": "bool" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ChainGasAmountUpdated", + "inputs": [ + { + "name": "oldChainGasAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newChainGasAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "FeeRateUpdated", + "inputs": [ + { + "name": "oldFeeRate", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newFeeRate", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "FeesSwept", + "inputs": [ + { + "name": "token", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "recipient", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RoleAdminChanged", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "previousAdminRole", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "newAdminRole", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RoleGranted", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RoleRevoked", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { "type": "error", "name": "AccessControlBadConfirmation", "inputs": [] }, + { + "type": "error", + "name": "AccessControlUnauthorizedAccount", + "inputs": [ + { "name": "account", "type": "address", "internalType": "address" }, + { "name": "neededRole", "type": "bytes32", "internalType": "bytes32" } + ] + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { "name": "target", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "AddressInsufficientBalance", + "inputs": [ + { "name": "account", "type": "address", "internalType": "address" } + ] + }, + { "type": "error", "name": "AmountIncorrect", "inputs": [] }, + { "type": "error", "name": "ChainIncorrect", "inputs": [] }, + { "type": "error", "name": "DeadlineExceeded", "inputs": [] }, + { "type": "error", "name": "DeadlineNotExceeded", "inputs": [] }, + { "type": "error", "name": "DeadlineTooShort", "inputs": [] }, + { "type": "error", "name": "DisputePeriodNotPassed", "inputs": [] }, + { "type": "error", "name": "DisputePeriodPassed", "inputs": [] }, + { "type": "error", "name": "FailedInnerCall", "inputs": [] }, + { "type": "error", "name": "MsgValueIncorrect", "inputs": [] }, + { + "type": "error", + "name": "MulticallTarget__UndeterminedRevert", + "inputs": [] + }, + { + "type": "error", + "name": "SafeERC20FailedOperation", + "inputs": [ + { "name": "token", "type": "address", "internalType": "address" } + ] + }, + { "type": "error", "name": "SenderIncorrect", "inputs": [] }, + { "type": "error", "name": "StatusIncorrect", "inputs": [] }, + { "type": "error", "name": "TokenNotContract", "inputs": [] }, + { "type": "error", "name": "TransactionRelayed", "inputs": [] }, + { "type": "error", "name": "ZeroAddress", "inputs": [] } + ] +} diff --git a/packages/contracts-rfq/foundry.toml b/packages/contracts-rfq/foundry.toml index 09bacaeae3..5bca86282a 100644 --- a/packages/contracts-rfq/foundry.toml +++ b/packages/contracts-rfq/foundry.toml @@ -26,6 +26,7 @@ linea = "${LINEA_RPC}" mainnet = "${MAINNET_RPC}" optimism = "${OPTIMISM_RPC}" scroll = "${SCROLL_RPC}" +worldchain = "${WORLDCHAIN_RPC}" # Testnets arb_sepolia = "${ARB_SEPOLIA_RPC}" @@ -45,6 +46,7 @@ linea = { key = "${LINEA_VERIFIER_KEY}", url = "${LINEA_VERIFIER_URL}" } mainnet = { key = "${MAINNET_VERIFIER_KEY}", url = "${MAINNET_VERIFIER_URL}" } optimism = { key = "${OPTIMISM_VERIFIER_KEY}", url = "${OPTIMISM_VERIFIER_URL}" } scroll = { key = "${SCROLL_VERIFIER_KEY}", url = "${SCROLL_VERIFIER_URL}" } +worldchain = { key = "${WORLDCHAIN_VERIFIER_KEY}", url = "${WORLDCHAIN_VERIFIER_URL}" } # Testnets arb_sepolia = { key = "${ARB_SEPOLIA_VERIFIER_KEY}", url = "${ARB_SEPOLIA_VERIFIER_URL}" } diff --git a/packages/contracts-rfq/package.json b/packages/contracts-rfq/package.json index c44b5de54a..3d5779555c 100644 --- a/packages/contracts-rfq/package.json +++ b/packages/contracts-rfq/package.json @@ -1,7 +1,7 @@ { "name": "@synapsecns/contracts-rfq", "license": "MIT", - "version": "0.9.1", + "version": "0.9.3", "description": "FastBridge contracts.", "private": true, "files": [ diff --git a/packages/explorer-ui/CHANGELOG.md b/packages/explorer-ui/CHANGELOG.md index 0673d41fd0..f4e3cef09f 100644 --- a/packages/explorer-ui/CHANGELOG.md +++ b/packages/explorer-ui/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.5.1](https://github.com/synapsecns/sanguine/compare/@synapsecns/explorer-ui@0.5.0...@synapsecns/explorer-ui@0.5.1) (2024-10-21) + +**Note:** Version bump only for package @synapsecns/explorer-ui + + + + + # [0.5.0](https://github.com/synapsecns/sanguine/compare/@synapsecns/explorer-ui@0.4.0...@synapsecns/explorer-ui@0.5.0) (2024-10-16) diff --git a/packages/explorer-ui/package.json b/packages/explorer-ui/package.json index c6184d6d41..efabcb0f80 100644 --- a/packages/explorer-ui/package.json +++ b/packages/explorer-ui/package.json @@ -1,6 +1,6 @@ { "name": "@synapsecns/explorer-ui", - "version": "0.5.0", + "version": "0.5.1", "private": true, "engines": { "node": ">=18.17.0" @@ -17,7 +17,7 @@ "@mui/x-date-pickers": "^5.0.17", "@next/third-parties": "^14.2.14", "@popperjs/core": "^2.11.5", - "@synapsecns/synapse-constants": "^1.7.0", + "@synapsecns/synapse-constants": "^1.8.0", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.2.0", "@testing-library/user-event": "^13.5.0", diff --git a/packages/rest-api/CHANGELOG.md b/packages/rest-api/CHANGELOG.md index 686ec53fd4..cfbea9a030 100644 --- a/packages/rest-api/CHANGELOG.md +++ b/packages/rest-api/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.1](https://github.com/synapsecns/sanguine/compare/@synapsecns/rest-api@1.7.0...@synapsecns/rest-api@1.7.1) (2024-10-22) + +**Note:** Version bump only for package @synapsecns/rest-api + + + + + # [1.7.0](https://github.com/synapsecns/sanguine/compare/@synapsecns/rest-api@1.6.1...@synapsecns/rest-api@1.7.0) (2024-10-21) diff --git a/packages/rest-api/package.json b/packages/rest-api/package.json index 8e2cb778a5..dc542ec764 100644 --- a/packages/rest-api/package.json +++ b/packages/rest-api/package.json @@ -1,6 +1,6 @@ { "name": "@synapsecns/rest-api", - "version": "1.7.0", + "version": "1.7.1", "private": "true", "engines": { "node": ">=18.17.0" diff --git a/packages/rest-api/src/controllers/bridgeTxStatusController.ts b/packages/rest-api/src/controllers/bridgeTxStatusController.ts index a0074a954e..a8eadc173e 100644 --- a/packages/rest-api/src/controllers/bridgeTxStatusController.ts +++ b/packages/rest-api/src/controllers/bridgeTxStatusController.ts @@ -15,10 +15,14 @@ export const bridgeTxStatusController = async (req, res) => { try { const { destChainId, bridgeModule, synapseTxId } = req.query + const txIdWith0x = !synapseTxId.startsWith('0x') + ? `0x${synapseTxId}` + : synapseTxId + const status = await Synapse.getBridgeTxStatus( Number(destChainId), bridgeModule, - synapseTxId + txIdWith0x ) if (status) { diff --git a/packages/rest-api/src/routes/bridgeTxStatusRoute.ts b/packages/rest-api/src/routes/bridgeTxStatusRoute.ts index 85227c760f..a9a22e65b1 100644 --- a/packages/rest-api/src/routes/bridgeTxStatusRoute.ts +++ b/packages/rest-api/src/routes/bridgeTxStatusRoute.ts @@ -5,6 +5,7 @@ import { showFirstValidationError } from '../middleware/showFirstValidationError import { bridgeTxStatusController } from '../controllers/bridgeTxStatusController' import { CHAINS_ARRAY } from '../constants/chains' import { VALID_BRIDGE_MODULES } from '../constants' +import { validateKappa } from '../validations/validateKappa' const router = express.Router() @@ -134,7 +135,10 @@ router.get( check('synapseTxId') .exists() .withMessage('synapseTxId is required') - .isString(), + .isString() + .withMessage('synapseTxId must be a string') + .custom((value) => validateKappa(value)) + .withMessage('synapseTxId must be valid hex string'), ], showFirstValidationError, bridgeTxStatusController diff --git a/packages/rest-api/src/tests/bridgeTxStatusRoute.test.ts b/packages/rest-api/src/tests/bridgeTxStatusRoute.test.ts index b806ef282b..2a1fa71ed9 100644 --- a/packages/rest-api/src/tests/bridgeTxStatusRoute.test.ts +++ b/packages/rest-api/src/tests/bridgeTxStatusRoute.test.ts @@ -64,6 +64,20 @@ describe('Get Bridge TX Status Route', () => { expect(response.body.error).toHaveProperty('field', 'synapseTxId') }, 10000) + it('should return 400 for invalid synapseTxId', async () => { + const response = await request(app).get('/bridgeTxStatus').query({ + destChainId: '1', + bridgeModule: 'SynapseRFQ', + synapseTxId: "'0x1234' OR '1'='1'", + }) + + expect(response.status).toBe(400) + expect(response.body.error).toHaveProperty( + 'message', + 'synapseTxId must be valid hex string' + ) + }, 10000) + it('should return 400 for missing destChainId', async () => { const response = await request(app).get('/bridgeTxStatus').query({ bridgeModule: 'bridge', diff --git a/packages/rest-api/src/validations/validateKappa.ts b/packages/rest-api/src/validations/validateKappa.ts new file mode 100644 index 0000000000..22ee8023c9 --- /dev/null +++ b/packages/rest-api/src/validations/validateKappa.ts @@ -0,0 +1,11 @@ +export const validateKappa = (synapseTxId: string) => { + let hexRegex + + if (synapseTxId.startsWith('0x')) { + hexRegex = /^0x[0-9a-fA-F]{64}$/ + } else { + hexRegex = /^[0-9a-fA-F]{64}$/ + } + + return hexRegex.test(synapseTxId) +} diff --git a/packages/synapse-constants/CHANGELOG.md b/packages/synapse-constants/CHANGELOG.md index 67ce8de75f..bbff111752 100644 --- a/packages/synapse-constants/CHANGELOG.md +++ b/packages/synapse-constants/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.8.0](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-constants@1.7.0...@synapsecns/synapse-constants@1.8.0) (2024-10-21) + + +### Features + +* **synapse-constants:** chain and token updates ([#3320](https://github.com/synapsecns/sanguine/issues/3320)) ([d0f6e6f](https://github.com/synapsecns/sanguine/commit/d0f6e6fb57b3aa08f97737572aaf7a6230161878)) + + + + + # [1.7.0](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-constants@1.6.1...@synapsecns/synapse-constants@1.7.0) (2024-10-10) diff --git a/packages/synapse-constants/package.json b/packages/synapse-constants/package.json index 42c99d8383..af3919b93a 100644 --- a/packages/synapse-constants/package.json +++ b/packages/synapse-constants/package.json @@ -1,6 +1,6 @@ { "name": "@synapsecns/synapse-constants", - "version": "1.7.0", + "version": "1.8.0", "description": "This is an npm package that maintains all synapse constants", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/packages/widget/CHANGELOG.md b/packages/widget/CHANGELOG.md index f50c33cd7e..a0c1e81cb1 100644 --- a/packages/widget/CHANGELOG.md +++ b/packages/widget/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [0.8.0](https://github.com/synapsecns/sanguine/compare/@synapsecns/widget@0.7.4...@synapsecns/widget@0.8.0) (2024-10-21) + + +### Features + +* **synapse-constants:** chain and token updates ([#3320](https://github.com/synapsecns/sanguine/issues/3320)) ([d0f6e6f](https://github.com/synapsecns/sanguine/commit/d0f6e6fb57b3aa08f97737572aaf7a6230161878)) + + + + + ## [0.7.4](https://github.com/synapsecns/sanguine/compare/@synapsecns/widget@0.7.3...@synapsecns/widget@0.7.4) (2024-10-11) **Note:** Version bump only for package @synapsecns/widget diff --git a/packages/widget/package.json b/packages/widget/package.json index 8d5e6de077..03659d8714 100644 --- a/packages/widget/package.json +++ b/packages/widget/package.json @@ -1,7 +1,7 @@ { "name": "@synapsecns/widget", "description": "Widget library for interacting with the Synapse Protocol", - "version": "0.7.4", + "version": "0.8.0", "license": "MIT", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/services/omnirpc/cmd/cmd.go b/services/omnirpc/cmd/cmd.go index 66e084b3f9..a0a548bba5 100644 --- a/services/omnirpc/cmd/cmd.go +++ b/services/omnirpc/cmd/cmd.go @@ -12,6 +12,7 @@ import ( // Start starts the command line. func Start(args []string, buildInfo config.BuildInfo) { app := cli.NewApp() + app.Name = buildInfo.Name() app.Version = buildInfo.Version() app.Usage = fmt.Sprintf("%s --help", buildInfo.Name()) diff --git a/services/rfq/relayer/quoter/quoter.go b/services/rfq/relayer/quoter/quoter.go index fb5ef025e2..36669422d3 100644 --- a/services/rfq/relayer/quoter/quoter.go +++ b/services/rfq/relayer/quoter/quoter.go @@ -818,7 +818,7 @@ func (m *Manager) getDestAmount(parentCtx context.Context, originAmount *big.Int if err != nil { return nil, fmt.Errorf("error getting quote offset bps: %w", err) } - quoteWidthBps, err := m.config.GetQuoteWidthBps(input.DestChainID) + quoteWidthBps, err := m.config.GetQuoteWidthBps(input.DestChainID, tokenName) if err != nil { return nil, fmt.Errorf("error getting quote width bps: %w", err) } diff --git a/services/rfq/relayer/quoter/quoter_test.go b/services/rfq/relayer/quoter/quoter_test.go index 48a5b1f19b..d9a4822f8b 100644 --- a/services/rfq/relayer/quoter/quoter_test.go +++ b/services/rfq/relayer/quoter/quoter_test.go @@ -354,12 +354,12 @@ func (s *QuoterSuite) TestGetDestAmount() { DestBalance: balance, } setQuoteParams := func(originQuoteOffsetBps, destQuoteOffsetBps, quoteWidthBps float64) { - s.config.BaseChainConfig.QuoteWidthBps = quoteWidthBps tokenCfg := s.config.Chains[origin].Tokens["USDC"] tokenCfg.QuoteOffsetBps = originQuoteOffsetBps s.config.Chains[origin].Tokens["USDC"] = tokenCfg tokenCfg = s.config.Chains[dest].Tokens["USDC"] tokenCfg.QuoteOffsetBps = destQuoteOffsetBps + tokenCfg.QuoteWidthBps = quoteWidthBps s.config.Chains[dest].Tokens["USDC"] = tokenCfg s.manager.SetConfig(s.config) } @@ -398,12 +398,11 @@ func (s *QuoterSuite) TestGetDestAmount() { expectedAmount = big.NewInt(960_000_000) s.Equal(expectedAmount, destAmount) - // Set QuoteWidthBps to -100, should default to balance. + // Set QuoteWidthBps to -100, should error. setQuoteParams(0, 0, -100) destAmount, err = s.manager.GetDestAmount(s.GetTestContext(), balance, "USDC", input) - s.NoError(err) - expectedAmount = balance - s.Equal(expectedAmount, destAmount) + s.Error(err) + s.Nil(destAmount) // Set origin offset to 100, should return 101% of balance. setQuoteParams(100, 0, 0) diff --git a/services/rfq/relayer/relconfig/config.go b/services/rfq/relayer/relconfig/config.go index 220627e13a..5804c3da5d 100644 --- a/services/rfq/relayer/relconfig/config.go +++ b/services/rfq/relayer/relconfig/config.go @@ -99,9 +99,6 @@ type ChainConfig struct { MinGasToken string `yaml:"min_gas_token"` // QuotePct is the percent of balance to quote. QuotePct *float64 `yaml:"quote_pct"` - // QuoteWidthBps is the number of basis points to deduct from the dest amount. - // Note that this parameter is applied on a chain level and must be positive. - QuoteWidthBps float64 `yaml:"quote_width_bps"` // QuoteFixedFeeMultiplier is the multiplier for the fixed fee, applied when generating quotes. QuoteFixedFeeMultiplier *float64 `yaml:"quote_fixed_fee_multiplier"` // RelayFixedFeeMultiplier is the multiplier for the fixed fee, applied when relaying. @@ -142,6 +139,9 @@ type TokenConfig struct { // Note that this value can be positive or negative; if positive it effectively increases the quoted price // of the given token, and vice versa. QuoteOffsetBps float64 `yaml:"quote_offset_bps"` + // QuoteWidthBps is the number of basis points to deduct from the dest amount. + // Note that this parameter must be positive. + QuoteWidthBps float64 `yaml:"quote_width_bps"` // MaxBalance is the maximum balance that should be accumulated for this token on this chain (human-readable units) MaxBalance *string `yaml:"max_balance"` } diff --git a/services/rfq/relayer/relconfig/config_test.go b/services/rfq/relayer/relconfig/config_test.go index 355f92264a..13c1060863 100644 --- a/services/rfq/relayer/relconfig/config_test.go +++ b/services/rfq/relayer/relconfig/config_test.go @@ -31,7 +31,6 @@ func TestChainGetters(t *testing.T) { L1FeeDestGasEstimate: 40000, MinGasToken: "1000", QuotePct: relconfig.NewFloatPtr(0), - QuoteWidthBps: 10, QuoteFixedFeeMultiplier: relconfig.NewFloatPtr(1.1), RebalanceConfigs: relconfig.RebalanceConfigs{ Synapse: &relconfig.SynapseCCTPRebalanceConfig{ @@ -60,7 +59,6 @@ func TestChainGetters(t *testing.T) { L1FeeDestGasEstimate: 40001, MinGasToken: "1001", QuotePct: relconfig.NewFloatPtr(51), - QuoteWidthBps: 11, QuoteFixedFeeMultiplier: relconfig.NewFloatPtr(1.2), RebalanceConfigs: relconfig.RebalanceConfigs{ Synapse: &relconfig.SynapseCCTPRebalanceConfig{ @@ -91,7 +89,6 @@ func TestChainGetters(t *testing.T) { L1FeeDestGasEstimate: 40000, MinGasToken: "1000", QuotePct: relconfig.NewFloatPtr(50), - QuoteWidthBps: 10, QuoteFixedFeeMultiplier: relconfig.NewFloatPtr(1.1), Tokens: map[string]relconfig.TokenConfig{ "USDC": { @@ -257,20 +254,6 @@ func TestChainGetters(t *testing.T) { assert.Equal(t, chainVal, 0.) }) - t.Run("GetQuoteWidthBps", func(t *testing.T) { - defaultVal, err := cfg.GetQuoteWidthBps(badChainID) - assert.NoError(t, err) - assert.Equal(t, defaultVal, relconfig.DefaultChainConfig.QuoteWidthBps) - - baseVal, err := cfgWithBase.GetQuoteWidthBps(badChainID) - assert.NoError(t, err) - assert.Equal(t, baseVal, cfgWithBase.BaseChainConfig.QuoteWidthBps) - - chainVal, err := cfgWithBase.GetQuoteWidthBps(chainID) - assert.NoError(t, err) - assert.Equal(t, chainVal, cfgWithBase.Chains[chainID].QuoteWidthBps) - }) - t.Run("GetQuoteFixedFeeMultiplier", func(t *testing.T) { defaultVal, err := cfg.GetQuoteFixedFeeMultiplier(badChainID) assert.NoError(t, err) @@ -311,7 +294,6 @@ func TestGetQuoteOffset(t *testing.T) { L1FeeDestGasEstimate: 40000, MinGasToken: "1000", QuotePct: relconfig.NewFloatPtr(50), - QuoteWidthBps: 10, QuoteFixedFeeMultiplier: relconfig.NewFloatPtr(1.1), Tokens: map[string]relconfig.TokenConfig{ "USDC": { diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go index a3fbb25cc7..cbc4f56edb 100644 --- a/services/rfq/relayer/relconfig/getters.go +++ b/services/rfq/relayer/relconfig/getters.go @@ -20,7 +20,6 @@ var DefaultChainConfig = ChainConfig{ DestGasEstimate: 100000, MinGasToken: "100000000000000000", // 1 ETH QuotePct: NewFloatPtr(100), - QuoteWidthBps: 0, QuoteFixedFeeMultiplier: NewFloatPtr(1), RelayFixedFeeMultiplier: NewFloatPtr(1), } @@ -440,20 +439,23 @@ func (c Config) GetMaxBalance(chainID int, addr common.Address) *big.Int { } // GetQuoteWidthBps returns the QuoteWidthBps for the given chainID. -func (c Config) GetQuoteWidthBps(chainID int) (value float64, err error) { - rawValue, err := c.getChainConfigValue(chainID, "QuoteWidthBps") - if err != nil { - return value, err +func (c Config) GetQuoteWidthBps(chainID int, tokenName string) (value float64, err error) { + chainCfg, ok := c.Chains[chainID] + if !ok { + return 0, fmt.Errorf("no chain config for chain %d", chainID) } - value, ok := rawValue.(float64) + tokenCfg, ok := chainCfg.Tokens[tokenName] if !ok { - return value, fmt.Errorf("failed to cast QuoteWidthBps to float") + return 0, fmt.Errorf("no token config for chain %d and token %s", chainID, tokenName) } - if value <= 0 { - value = DefaultChainConfig.QuoteWidthBps + + width := tokenCfg.QuoteWidthBps + if width < 0 { + return 0, fmt.Errorf("quote width bps must be positive: %f", width) } - return value, nil + + return width, nil } // GetQuoteFixedFeeMultiplier returns the QuoteFixedFeeMultiplier for the given chainID.