Skip to content

Conversation

@curryxbo
Copy link
Contributor

@curryxbo curryxbo commented Dec 8, 2025

Summary by CodeRabbit

  • New Features

    • Added runtime metrics for last successful update time and labeled update counts.
    • Added stablecoin support to specify fixed-price tokens directly.
  • Bug Fixes

    • Initialize metrics at startup and pre-create label values to avoid missing metrics/alerts.
    • Skip processing when no active tokens or no updates needed; record skipped cycles.
  • Documentation

    • Updated config examples and local scripts with stablecoin mapping format.
  • Chores

    • Updated dependency versions and module update tooling to include token-price component.

✏️ Tip: You can customize this high-level summary in your review settings.

@curryxbo curryxbo requested a review from a team as a code owner December 8, 2025 07:16
@curryxbo curryxbo requested review from twcctop and removed request for a team December 8, 2025 07:16
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 8, 2025

Walkthrough

Adds Prometheus metrics for last successful update timestamp and update counts; filters inactive tokens and caches token info in the updater; adds stablecoin parsing in Bitget client and example configs; and bumps github.com/morph-l2/go-ethereum pseudo-version across modules and the Makefile.

Changes

Cohort / File(s) Summary
Metrics Definition
token-price-oracle/metrics/metrics.go
Added LastSuccessfulUpdateTimestamp (prometheus.Gauge) and UpdatesTotal (*prometheus.CounterVec); imported time; registered and initialized metrics and pre-created label values in init().
Updater logic
token-price-oracle/updater/token_price.go
Added TokenInfo type and token-info caching; filters active tokens before price fetch; skips update when no active tokens or no prices to update; records LastSuccessfulUpdateTimestamp and increments UpdatesTotal with labels "updated"/"skipped".
Client — stablecoin parsing
token-price-oracle/client/bitget_sdk.go
Added exported StablecoinPrefix and logic to parse fixed-price stablecoin symbols (prefix $) in GetTokenPrice; falls back to exchange fetch for other symbols; added strings import and validation.
Config examples / local run
token-price-oracle/env.example, token-price-oracle/local.sh
Updated token-mapping examples to include a stablecoin entry ($1.0) and added guidance/comments (escape $ in shell).
Dependency bumps (go.mod)
bindings/go.mod, contracts/go.mod, node/go.mod, ops/l2-genesis/go.mod, ops/tools/go.mod, oracle/go.mod, token-price-oracle/go.mod, tx-submitter/go.mod
Updated github.com/morph-l2/go-ethereum pseudo-version to v1.10.14-0.20251203083507-49fa27bcab24 across modules.
Build tooling
Makefile
Updated ETHEREUM_SUBMODULE_COMMIT_OR_TAG and ETHEREUM_TARGET_VERSION; added MODULE=token-price-oracle to update_all_mod target.
sequenceDiagram
  autonumber
  actor Scheduler as Updater
  participant UpdaterProcess as Updater.Process
  participant Metrics as Prometheus
  participant Client as BitgetClient
  participant Store as PriceStore

  Scheduler->>UpdaterProcess: trigger update cycle
  UpdaterProcess->>UpdaterProcess: filterActiveTokens() -> activeIDs, tokenInfoMap
  alt no active tokens
    UpdaterProcess->>Metrics: LastSuccessfulUpdateTimestamp.Set(now)
    UpdaterProcess->>Metrics: UpdatesTotal.WithLabel("skipped").Add(1)
    UpdaterProcess-->>Scheduler: return (skipped)
  else active tokens exist
    UpdaterProcess->>Client: Get prices for activeIDs
    loop per token
      Client->>Client: if symbol startsWith("$") parse fixed price else fetch exchange price
      Client-->>UpdaterProcess: price or error
    end
    UpdaterProcess->>UpdaterProcess: calculatePriceRatioWithInfo(tokenInfoMap, prices)
    alt no updates needed
      UpdaterProcess->>Metrics: LastSuccessfulUpdateTimestamp.Set(now)
      UpdaterProcess->>Metrics: UpdatesTotal.WithLabel("skipped").Add(1)
      UpdaterProcess-->>Scheduler: return (skipped)
    else updates applied
      UpdaterProcess->>Store: persist updated prices
      UpdaterProcess->>Metrics: LastSuccessfulUpdateTimestamp.Set(now)
      UpdaterProcess->>Metrics: UpdatesTotal.WithLabel("updated").Add(1)
      UpdaterProcess-->>Scheduler: return (updated)
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

  • Files to inspect closely:
    • token-price-oracle/metrics/metrics.go — registration order and label initialization.
    • token-price-oracle/updater/token_price.go — token filtering, TokenInfo correctness, all metric update paths, and concurrency/safety.
    • token-price-oracle/client/bitget_sdk.go — stablecoin parsing, validation, and error handling.
    • Makefile and go.mod files — verify pseudo-version strings and update_all_mod invocation.

Possibly related PRs

Suggested reviewers

  • twcctop
  • Kukoomomo

Poem

🐇 I nibbled code at dawn so bright,

Gauges set and counters light.
Tokens hopped — some skipped, some sped,
Timestamps tick where updates led.
I twitch my nose — observability fed. 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The PR title accurately reflects both main changes: adding Prometheus metrics to the token price oracle and introducing stablecoin support with the StablecoinPrefix constant.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch addMetrics

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
token-price-oracle/updater/token_price.go (1)

182-187: Align skip metrics with all “no-op” update paths

This correctly records a successful “skipped” cycle when all price changes are below threshold. However, the earlier len(tokenIDs) == 0 path logs a skipped cycle but returns without touching these metrics, so long periods with no supported tokens would look like the oracle is stalled.

Consider treating the len(tokenIDs) == 0 case as a successful “skipped” cycle too, e.g.:

-	if len(tokenIDs) == 0 {
-		log.Warn("No tokens to update, skipping price update cycle")
-		return nil
-	}
+	if len(tokenIDs) == 0 {
+		log.Warn("No tokens to update, skipping price update cycle")
+		metrics.LastSuccessfulUpdateTimestamp.Set(float64(time.Now().Unix()))
+		metrics.UpdatesTotal.WithLabelValues("skipped").Inc()
+		return nil
+	}

This would make the new metrics fully consistent with the comments and with the log messages.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df65943 and 6f8d004.

📒 Files selected for processing (2)
  • token-price-oracle/metrics/metrics.go (1 hunks)
  • token-price-oracle/updater/token_price.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
token-price-oracle/updater/token_price.go (1)
token-price-oracle/metrics/metrics.go (2)
  • LastSuccessfulUpdateTimestamp (32-37)
  • UpdatesTotal (40-46)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (3)
token-price-oracle/updater/token_price.go (1)

223-226: Success metrics placement looks correct

Recording LastSuccessfulUpdateTimestamp and incrementing UpdatesTotal{type="updated"} only after verifying a non‑nil receipt with Status == types.ReceiptStatusSuccessful cleanly reflects fully successful on‑chain update cycles. No changes needed here.

token-price-oracle/metrics/metrics.go (2)

29-46: Metric definitions are clear and match intended semantics

LastSuccessfulUpdateTimestamp and UpdatesTotal{type="updated"|"skipped"} are well‑named, have clear help text, and align with how update() records successful “updated” vs “skipped” cycles. Once you also record metrics in the len(tokenIDs) == 0 skip path (as suggested in token_price.go), the comment that this gauge includes both updates and skips will be fully accurate across all no‑op cycles.

No issues with these definitions from a metrics/observability standpoint.


51-55: Registration in init() is consistent with existing pattern

Registering the new metrics alongside UpdateErrors and AccountBalance via prometheus.MustRegister keeps a single global registry and matches the existing approach in this package. Looks good.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f8d004 and 2ed7d9c.

📒 Files selected for processing (1)
  • token-price-oracle/metrics/metrics.go (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (4)
token-price-oracle/metrics/metrics.go (4)

5-5: LGTM!

The time package import is appropriately added for initializing the timestamp metric.


30-38: LGTM!

The metric definition is well-structured. Using a Gauge for timestamps is correct, and the documentation clearly explains that "successful" encompasses both on-chain updates and skipped updates (below threshold).


40-47: LGTM!

The CounterVec definition is appropriate for tracking update cycles. The label-based distinction between "updated" and "skipped" events provides good observability granularity.


54-55: LGTM!

Metric registration follows the established pattern and correctly uses MustRegister to ensure metrics are properly initialized at startup.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
Makefile (1)

33-33: Add token-price-oracle to fmt-go target for consistency.

The update_all_mod target now includes token-price-oracle (line 33), but the fmt-go target (lines 90–102) does not invoke go mod tidy for this module, unlike the other modules (bindings, contracts, node, ops/l2-genesis, ops/tools, oracle, tx-submitter). This is inconsistent, especially since token-price-oracle/go.mod is being updated as part of this PR.

Add token-price-oracle to the fmt-go target:

 fmt-go:
 	go work sync
 	cd $(PWD)/bindings/ && go mod tidy
 	cd $(PWD)/contracts/ && go mod tidy
 	cd $(PWD)/node/ && go mod tidy
 	cd $(PWD)/ops/l2-genesis/ && go mod tidy
 	cd $(PWD)/ops/tools/ && go mod tidy
 	cd $(PWD)/oracle/ && go mod tidy
 	cd $(PWD)/tx-submitter/ && go mod tidy
+	cd $(PWD)/token-price-oracle/ && go mod tidy
 	find . -name '*.go' -type f -not -path "./go-ethereum*" -not -name '*.pb.go' | xargs gofmt -w -s
 	find . -name '*.go' -type f -not -path "./go-ethereum*" -not -name '*.pb.go' | xargs misspell -w
 	find . -name '*.go' -type f -not -path "./go-ethereum*" -not -name '*.pb.go' | xargs goimports -w -local $(PWD)/
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ed7d9c and c5ebf10.

⛔ Files ignored due to path filters (9)
  • bindings/go.sum is excluded by !**/*.sum
  • contracts/go.sum is excluded by !**/*.sum
  • go.work.sum is excluded by !**/*.sum
  • node/go.sum is excluded by !**/*.sum
  • ops/l2-genesis/go.sum is excluded by !**/*.sum
  • ops/tools/go.sum is excluded by !**/*.sum
  • oracle/go.sum is excluded by !**/*.sum
  • token-price-oracle/go.sum is excluded by !**/*.sum
  • tx-submitter/go.sum is excluded by !**/*.sum
📒 Files selected for processing (9)
  • Makefile (2 hunks)
  • bindings/go.mod (1 hunks)
  • contracts/go.mod (1 hunks)
  • node/go.mod (1 hunks)
  • ops/l2-genesis/go.mod (1 hunks)
  • ops/tools/go.mod (1 hunks)
  • oracle/go.mod (1 hunks)
  • token-price-oracle/go.mod (1 hunks)
  • tx-submitter/go.mod (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: test
  • GitHub Check: check
  • GitHub Check: test
  • GitHub Check: test
  • GitHub Check: check
  • GitHub Check: test
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
token-price-oracle/go.mod (1)

11-11: Missing implementation files for token-price-oracle metrics.

The go-ethereum dependency is updated consistently; however, the PR objectives reference new Prometheus metrics (token-price-oracle/metrics/metrics.go) and updater changes (token-price-oracle/updater/token_price.go) that are not provided for review.

Ensure that the full PR changeset includes the metrics instrumentation implementation files mentioned in the PR summary, and verify they follow Prometheus best practices for metric naming and registration.

node/go.mod (1)

14-14: Coordinated go-ethereum version bump across modules.

Line 14 updates the go-ethereum pseudo-version from v1.10.14-0.20251119080508-d085f8c79a53 to v1.10.14-0.20251203083507-49fa27bcab24, consistent with parallel updates in other module go.mod files. The new version corresponds to the merge of PR #256 ("Fix precompile address") into morph-l2/go-ethereum on December 3, 2025, which standardizes precompile address notation and updates BLS12381 precompile tests in core/vm.

Makefile (1)

2-3: Version alignment looks correct.

The ETHEREUM_SUBMODULE_COMMIT_OR_TAG (49fa27bcab24...) correctly matches the commit hash in ETHEREUM_TARGET_VERSION.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5ebf10 and 65a050b.

📒 Files selected for processing (3)
  • token-price-oracle/client/bitget_sdk.go (4 hunks)
  • token-price-oracle/env.example (1 hunks)
  • token-price-oracle/local.sh (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: check
  • GitHub Check: check
  • GitHub Check: test
  • GitHub Check: test
  • GitHub Check: test
  • GitHub Check: test
  • GitHub Check: check
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (7)
token-price-oracle/client/bitget_sdk.go (3)

20-23: LGTM! Well-documented constant.

The exported constant is clearly documented with the format and example, making it easy for other parts of the codebase to understand and use stablecoin pricing.


71-74: LGTM! Clear documentation.

The documentation clearly explains the stablecoin handling behavior with a concrete example, making it easy for users to understand the feature.


107-121: LGTM! Clean separation of stablecoin and exchange price paths.

The code correctly handles both stablecoin and regular token price fetching with appropriate logging for each path. The error handling and logging context are comprehensive.

token-price-oracle/local.sh (2)

11-11: LGTM! Proper bash escaping for stablecoin mapping.

The dollar sign is correctly escaped with a backslash to prevent bash variable expansion, ensuring the literal string "3:$1.0" is passed to the application.


19-22: LGTM! Clear and helpful documentation.

The comments provide clear guidance on the token mapping format for both regular tokens and stablecoins, with the important reminder about bash escaping.

token-price-oracle/env.example (2)

21-25: LGTM! Comprehensive format documentation with good examples.

The documentation clearly explains both regular token and stablecoin formats, with realistic examples including an off-peg stablecoin ($0.9999). The format is consistent with the implementation in bitget_sdk.go and local.sh.


27-28: LGTM! Consistent format across price feed providers.

The Binance mapping example follows the same format as Bitget, including stablecoin support, ensuring consistency and making it easy for users to enable multiple price feeds.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
token-price-oracle/updater/token_price.go (1)

303-366: Add guard for token decimals exceeding 18 to prevent unsigned arithmetic underflow

The decimal adjustment calculation has an unsigned arithmetic underflow vulnerability:

decimalAdjustment := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(18-tokenDecimals)), nil)

Since tokenDecimals is uint8, the expression 18 - tokenDecimals uses unsigned arithmetic. If any token has decimals > 18, this underflows to a large positive value (e.g., 18 - 19 = 255 in uint8), causing the exponent to be 10^255 instead of a negative scaling factor. This would grossly inflate the price ratio.

Add a guard to enforce the constraint:

if tokenDecimals > 18 {
    return nil, fmt.Errorf("unsupported token decimals %d for token %d (must be <= 18)", tokenDecimals, tokenID)
}

decimalAdjustmentExp := int64(18) - int64(tokenDecimals)
decimalAdjustment := new(big.Int).Exp(big.NewInt(10), big.NewInt(decimalAdjustmentExp), nil)

This ensures the subtraction happens in signed space and fails explicitly if an invalid token is encountered.

🧹 Nitpick comments (2)
token-price-oracle/updater/token_price.go (2)

189-194: Clarify whether “no tokens / no active tokens” should count as successful skipped cycles in metrics

You correctly record metrics for:

  • “threshold skip” cycles (len(tokenIDsToUpdate) == 0) as "skipped", and
  • successful on-chain updates as "updated".

However, earlier short-circuit paths:

  • Line 109–112: len(tokenIDs) == 0
  • Line 116–119: len(activeTokenIDs) == 0

return nil without touching LastSuccessfulUpdateTimestamp or UpdatesTotal. If the intent of last_successful_update_timestamp and updates_total{type="skipped"} is to reflect every successful invocation of update (including cycles where there is literally nothing to do), these branches will make the metrics look stale even though the updater is healthy.

Consider treating these branches as successful skipped cycles, e.g. by calling a small helper like:

func recordUpdateCycle(result string) {
    metrics.LastSuccessfulUpdateTimestamp.Set(float64(time.Now().Unix()))
    metrics.UpdatesTotal.WithLabelValues(result).Inc()
}

and using result="skipped" in those early-return cases as well.

Also applies to: 197-202, 231-235


245-252: TokenInfo cache and filterActiveTokens helper are generally well-structured

The TokenInfo cache and filterActiveTokens helper are structured cleanly and make the later update path easier to reason about. Logging inactive tokens and the final summary ("Filtered tokens by active status") is useful operationally.

Two small, optional refinements you might consider:

  1. Log level for per-token inactive logs
    For large registries with many inactive entries, the per-token log.Info("Token is inactive, skipping price update", ...) could get noisy. If this is expected to be common in production, dropping that to Debug and relying on the aggregate "Filtered tokens by active status" info log would reduce log volume.

  2. Observability for GetTokenInfo failures
    Currently, GetTokenInfo failures only emit a warning and silently drop the token. If these failures are meaningful in your environment, you might want to hook them into a dedicated metric (e.g., token_info_errors_total) so they’re visible in Prometheus as well as logs.

Both are non-blocking; the current implementation is functionally correct.

Also applies to: 253-301

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 65a050b and aa743af.

📒 Files selected for processing (1)
  • token-price-oracle/updater/token_price.go (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
token-price-oracle/updater/token_price.go (2)
token-price-oracle/metrics/metrics.go (2)
  • LastSuccessfulUpdateTimestamp (33-38)
  • UpdatesTotal (41-47)
token-price-oracle/client/price_feed.go (1)
  • TokenPrice (12-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: test
  • GitHub Check: check
  • GitHub Check: test
  • GitHub Check: test
  • GitHub Check: check
  • GitHub Check: test
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (go)
🔇 Additional comments (1)
token-price-oracle/updater/token_price.go (1)

114-127: Active-token prefiltering and TokenInfo-based price ratio look solid

Prefiltering inactive tokens before calling GetBatchTokenPrices and reusing tokenInfoMap in calculatePriceRatioWithInfo is a good optimization and avoids redundant contract calls. Indexing tokenInfoMap[tokenID] is safe given the nil-check inside calculatePriceRatioWithInfo, and the skip-on-error behavior is reasonable here.

Also applies to: 130-138

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
token-price-oracle/client/bitget_sdk.go (1)

90-124: Stablecoin price path looks correct; consider small helper for reuse.

Logic correctly:

  • Detects $-prefixed symbols.
  • Parses the fixed price, validates it’s > 0, and wraps parse errors with context.
  • Avoids Bitget API calls while still logging and returning a TokenPrice consistent with the non-stablecoin path.

If this pattern is reused elsewhere, you could extract a small helper like parseStablecoinPrice(symbol string) (*big.Float, error) to centralize parsing/validation and make unit testing easier, but it’s not required for correctness here.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa743af and 3ccbeac.

📒 Files selected for processing (1)
  • token-price-oracle/client/bitget_sdk.go (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: test
  • GitHub Check: test
  • GitHub Check: test
  • GitHub Check: check
  • GitHub Check: test
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (3)
token-price-oracle/client/bitget_sdk.go (3)

11-11: Import usage is correct and minimal.

The new strings import is used appropriately for prefix detection and trimming, with no unused imports introduced.


21-23: Stablecoin prefix constant is well-documented and scoped.

Exporting StablecoinPrefix = "$" with a clear format comment is good for keeping env/config parsing consistent across packages.


71-75: API doc comment accurately reflects new stablecoin behavior.

The added explanation of the $<price> stablecoin format matches the implementation and helps callers understand how to configure token maps.

@curryxbo curryxbo changed the title Add token price oracle metrics Add token price oracle metrics and stable coins Dec 11, 2025
@curryxbo curryxbo merged commit 7df07a7 into main Dec 12, 2025
15 checks passed
@curryxbo curryxbo deleted the addMetrics branch December 12, 2025 02:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants