Skip to content

Commit c6a747d

Browse files
author
corey
committed
fix
1 parent 5af35e9 commit c6a747d

File tree

9 files changed

+196
-199
lines changed

9 files changed

+196
-199
lines changed

token-price-oracle/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ clean:
2626
rm -rf build/
2727

2828
docker-build:
29-
docker build -t morph/gas-price-oracle:latest .
29+
docker build -t morph/token-price-oracle:latest .
3030

3131
help:
3232
@echo "Available targets:"
33-
@echo " build - Build the gas-price-oracle binary"
33+
@echo " build - Build the token-price-oracle binary"
3434
@echo " run - Build and run the service"
3535
@echo " test - Run tests"
3636
@echo " lint - Run linter"

token-price-oracle/config/config.go

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ type Config struct {
5454
PrivateKey string
5555
// Price update parameters
5656
PriceUpdateInterval time.Duration // Price update interval
57-
TokenIDs []uint16 // Token IDs to update
5857
PriceThreshold uint64 // Price change threshold percentage to trigger update
5958
PriceFeedPriority []PriceFeedType // Price feed types in priority order (fallback mechanism)
6059
TokenMappings map[PriceFeedType]map[uint16]string // Token ID to trading pair mappings for each price feed type
@@ -98,25 +97,8 @@ func LoadConfig(ctx *cli.Context) (*Config, error) {
9897
// Parse price update interval
9998
cfg.PriceUpdateInterval = ctx.Duration(flags.PriceUpdateIntervalFlag.Name)
10099

101-
// Parse token IDs
102-
tokenIDsStr := ctx.String(flags.TokenIDsFlag.Name)
103-
if tokenIDsStr != "" {
104-
parts := strings.Split(tokenIDsStr, ",")
105-
for _, part := range parts {
106-
part = strings.TrimSpace(part)
107-
if part == "" {
108-
continue
109-
}
110-
id, err := strconv.ParseUint(part, 10, 16)
111-
if err != nil {
112-
return nil, fmt.Errorf("invalid token ID '%s': %w", part, err)
113-
}
114-
cfg.TokenIDs = append(cfg.TokenIDs, uint16(id))
115-
}
116-
}
117-
118100
cfg.PriceThreshold = ctx.Uint64(flags.PriceThresholdFlag.Name)
119-
101+
120102
// Validate price threshold is reasonable (percentage should be 0-100)
121103
if cfg.PriceThreshold > 100 {
122104
return nil, fmt.Errorf("price threshold %d is too large (should be 0-100 for percentage)", cfg.PriceThreshold)

token-price-oracle/flags/flags.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,6 @@ var (
4444
EnvVar: prefixEnvVar("PRICE_UPDATE_INTERVAL"),
4545
}
4646

47-
TokenIDsFlag = cli.StringFlag{
48-
Name: "token-ids",
49-
Usage: "Comma-separated token IDs to update prices for (e.g. \"1,2,3\")",
50-
Value: "",
51-
EnvVar: prefixEnvVar("TOKEN_IDS"),
52-
}
53-
5447
PriceThresholdFlag = cli.Uint64Flag{
5548
Name: "price-threshold",
5649
Usage: "Price change threshold percentage to trigger update (e.g. 5 for 5%)",
@@ -157,7 +150,6 @@ var requiredFlags = []cli.Flag{
157150
var optionalFlags = []cli.Flag{
158151
TxnPerBatchFlag,
159152
PriceUpdateIntervalFlag,
160-
TokenIDsFlag,
161153
PriceThresholdFlag,
162154
PriceFeedPriorityFlag,
163155
TokenMappingBitgetFlag,

token-price-oracle/go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ replace (
88
)
99

1010
require (
11-
github.com/morph-l2/go-ethereum v1.10.14-0.20251119080508-d085f8c79a53
11+
github.com/morph-l2/go-ethereum v1.10.14-0.20251120124625-16a606312846
1212
github.com/prometheus/client_golang v1.17.0
1313
github.com/sirupsen/logrus v1.9.3
14+
github.com/stretchr/testify v1.10.0
1415
github.com/urfave/cli v1.22.17
1516
gopkg.in/natefinch/lumberjack.v2 v2.2.1
1617
)
@@ -84,5 +85,6 @@ require (
8485
google.golang.org/protobuf v1.33.0 // indirect
8586
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
8687
gopkg.in/urfave/cli.v1 v1.20.0 // indirect
88+
gopkg.in/yaml.v3 v3.0.1 // indirect
8789
rsc.io/tmplfunc v0.0.3 // indirect
8890
)

token-price-oracle/go.sum

Lines changed: 99 additions & 1 deletion
Large diffs are not rendered by default.

token-price-oracle/local.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
--l2-eth-rpc http://localhost:8545 \
77
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
88
--price-update-interval 30s \
9-
--price-threshold 5 \
9+
--price-threshold 0 \
1010
--price-feed-priority bitget \
11-
--token-ids "1,2" \
1211
--token-mapping-bitget "1:BGBUSDT,2:BTCUSDT" \
1312
--bitget-api-base-url https://api.bitget.com \
1413
--log-level info \

token-price-oracle/updater/factory.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,13 @@ func CreatePriceUpdater(
5252
registryContract,
5353
priceFeed,
5454
txManager,
55-
cfg.TokenIDs,
5655
allTokenMappings,
5756
cfg.PriceUpdateInterval,
5857
cfg.PriceThreshold,
5958
)
6059

6160
log.Info("Price updater configured",
6261
"price_feed_priority", cfg.PriceFeedPriority,
63-
"token_ids", cfg.TokenIDs,
6462
"token_mappings", allTokenMappings,
6563
"interval", cfg.PriceUpdateInterval,
6664
"threshold", cfg.PriceThreshold)

token-price-oracle/updater/token_price.go

Lines changed: 22 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,11 @@ type PriceUpdater struct {
2121
registryContract *bindings.L2TokenRegistry
2222
priceFeed client.PriceFeed
2323
txManager *TxManager
24-
tokenIDs []uint16
2524
tokenMapping map[uint16]string // tokenID -> trading pair (e.g. 1 -> "BTCUSDT")
2625
interval time.Duration
2726
priceThreshold uint64
28-
29-
// Cache of last updated prices
30-
lastPrices map[uint16]*big.Int
31-
mu sync.RWMutex
32-
stopChan chan struct{}
33-
stopOnce sync.Once // ensures stopChan is closed only once
27+
stopChan chan struct{}
28+
stopOnce sync.Once // ensures stopChan is closed only once
3429
}
3530

3631
// NewPriceUpdater creates a new price updater
@@ -39,7 +34,6 @@ func NewPriceUpdater(
3934
registryContract *bindings.L2TokenRegistry,
4035
priceFeed client.PriceFeed,
4136
txManager *TxManager,
42-
tokenIDs []uint16,
4337
tokenMapping map[uint16]string,
4438
interval time.Duration,
4539
priceThreshold uint64,
@@ -49,11 +43,9 @@ func NewPriceUpdater(
4943
registryContract: registryContract,
5044
priceFeed: priceFeed,
5145
txManager: txManager,
52-
tokenIDs: tokenIDs,
5346
tokenMapping: tokenMapping,
5447
interval: interval,
5548
priceThreshold: priceThreshold,
56-
lastPrices: make(map[uint16]*big.Int),
5749
stopChan: make(chan struct{}),
5850
}
5951
}
@@ -65,25 +57,6 @@ func (u *PriceUpdater) Start(ctx context.Context) error {
6557
ticker := time.NewTicker(u.interval)
6658
defer ticker.Stop()
6759

68-
// Fetch token IDs from contract if not configured
69-
// TODO: Uncomment when contract has getSupportedIDList method
70-
// if len(u.tokenIDs) == 0 {
71-
// log.Info("No tokenIDs configured, fetching from contract...")
72-
// if err := u.fetchTokenIDsFromContract(ctx); err != nil {
73-
// log.Error("Failed to fetch tokenIDs from contract, price updater will not start")
74-
// return
75-
// }
76-
// }
77-
78-
// Filter tokenIDs to only those in tokenMapping
79-
u.filterTokenIDsByMapping()
80-
81-
log.Info("Price updater started",
82-
"token_ids", u.tokenIDs,
83-
"token_mapping", u.tokenMapping,
84-
"interval", u.interval,
85-
"price_threshold", u.priceThreshold)
86-
8760
if err := u.update(ctx); err != nil {
8861
log.Error("Initial price update failed")
8962
}
@@ -125,10 +98,12 @@ func (u *PriceUpdater) update(ctx context.Context) error {
12598
}
12699
}()
127100

128-
// Snapshot tokenIDs under lock to avoid race conditions
129-
u.mu.RLock()
130-
tokenIDs := append([]uint16(nil), u.tokenIDs...)
131-
u.mu.RUnlock()
101+
// Fetch token IDs from contract if not configured
102+
tokenIDs, err := u.fetchTokenIDsFromContract(ctx)
103+
if err != nil {
104+
log.Error("Failed to fetch tokenIDs from contract, price updater will not start")
105+
return err
106+
}
132107

133108
if len(tokenIDs) == 0 {
134109
log.Warn("No tokens to update, skipping price update cycle")
@@ -211,27 +186,30 @@ func (u *PriceUpdater) update(ctx context.Context) error {
211186
log.Info("Updating token prices",
212187
"token_count", len(tokenIDsToUpdate),
213188
"token_ids", tokenIDsToUpdate,
214-
"total_tokens", len(u.tokenIDs))
189+
"total_tokens", len(tokenIDs))
215190

216191
// Step 3: Update prices on L2
217192
receipt, err := u.txManager.SendTransaction(ctx, func(auth *bind.TransactOpts) (*types.Transaction, error) {
218193
return u.registryContract.BatchUpdatePrices(auth, tokenIDsToUpdate, pricesToUpdate)
219194
})
195+
220196
if err != nil {
197+
log.Error("Failed to send transaction", "error", err)
221198
return fmt.Errorf("failed to send batch update prices transaction: %w", err)
222199
}
223200

224-
if receipt.Status == 0 {
225-
log.Error("Transaction failed", "tx_hash", receipt.TxHash.Hex())
226-
return fmt.Errorf("transaction failed on-chain: %s", receipt.TxHash.Hex())
201+
if receipt == nil {
202+
log.Error("Received nil receipt")
203+
return fmt.Errorf("received nil receipt")
227204
}
228205

229-
// Step 4: Update cache with new prices
230-
u.mu.Lock()
231-
for i, tokenID := range tokenIDsToUpdate {
232-
u.lastPrices[tokenID] = pricesToUpdate[i]
206+
if receipt.Status != types.ReceiptStatusSuccessful {
207+
log.Error("Transaction failed on-chain",
208+
"tx_hash", receipt.TxHash.Hex(),
209+
"status", receipt.Status,
210+
"gas_used", receipt.GasUsed)
211+
return fmt.Errorf("transaction failed on-chain: %s", receipt.TxHash.Hex())
233212
}
234-
u.mu.Unlock()
235213

236214
log.Info("Successfully updated token prices",
237215
"tx_hash", receipt.TxHash.Hex(),
@@ -397,111 +375,10 @@ func (u *PriceUpdater) shouldUpdatePrice(lastPrice, newPrice *big.Int) bool {
397375
return percentage.Cmp(thresholdBig) >= 0
398376
}
399377

400-
// UpdateTokenList updates the list of tokens to monitor
401-
// The input slice is copied to prevent external modifications
402-
func (u *PriceUpdater) UpdateTokenList(tokenIDs []uint16) {
403-
u.mu.Lock()
404-
defer u.mu.Unlock()
405-
406-
// Create a defensive copy to prevent external modifications
407-
u.tokenIDs = append([]uint16(nil), tokenIDs...)
408-
log.Info("Updated token list", "token_ids", u.tokenIDs)
409-
}
410-
411-
// GetTokenList returns a copy of the current token list
412-
func (u *PriceUpdater) GetTokenList() []uint16 {
413-
u.mu.RLock()
414-
defer u.mu.RUnlock()
415-
416-
// Return a copy to prevent external modifications
417-
out := make([]uint16, len(u.tokenIDs))
418-
copy(out, u.tokenIDs)
419-
return out
420-
}
421-
422-
// GetLastPrice returns the last updated price for a token
423-
func (u *PriceUpdater) GetLastPrice(tokenID uint16) *big.Int {
424-
u.mu.RLock()
425-
defer u.mu.RUnlock()
426-
427-
if price, exists := u.lastPrices[tokenID]; exists {
428-
return new(big.Int).Set(price)
429-
}
430-
return nil
431-
}
432-
433-
// GetAllLastPrices returns a copy of all cached prices
434-
func (u *PriceUpdater) GetAllLastPrices() map[uint16]*big.Int {
435-
u.mu.RLock()
436-
defer u.mu.RUnlock()
437-
438-
result := make(map[uint16]*big.Int)
439-
for tokenID, price := range u.lastPrices {
440-
result[tokenID] = new(big.Int).Set(price)
441-
}
442-
return result
443-
}
444-
445378
// fetchTokenIDsFromContract fetches supported token IDs from L2TokenRegistry contract
446-
func (u *PriceUpdater) fetchTokenIDsFromContract(ctx context.Context) error {
379+
func (u *PriceUpdater) fetchTokenIDsFromContract(ctx context.Context) ([]uint16, error) {
447380
callOpts := &bind.CallOpts{Context: ctx}
448381

449382
// Call getSupportedIDList() on the contract
450-
tokenIDs, err := u.registryContract.GetSupportedIDList(callOpts)
451-
if err != nil {
452-
return fmt.Errorf("failed to call getSupportedIDList: %w", err)
453-
}
454-
455-
if len(tokenIDs) == 0 {
456-
log.Warn("Contract returned empty token ID list")
457-
return nil
458-
}
459-
460-
u.mu.Lock()
461-
u.tokenIDs = tokenIDs
462-
u.mu.Unlock()
463-
464-
log.Info("Fetched token IDs from contract",
465-
"token_ids", tokenIDs,
466-
"count", len(tokenIDs))
467-
468-
return nil
469-
}
470-
471-
// filterTokenIDsByMapping filters tokenIDs to only include those that have a mapping configured
472-
func (u *PriceUpdater) filterTokenIDsByMapping() {
473-
// Always acquire lock before modifying u.tokenIDs to prevent race conditions
474-
u.mu.Lock()
475-
defer u.mu.Unlock()
476-
477-
// Check if token mapping is configured
478-
if len(u.tokenMapping) == 0 {
479-
log.Error("No token mapping configured for current price feed type, price updater will not work. Please configure the appropriate token-mapping flag (e.g., --token-mapping-bitget, --token-mapping-binance)")
480-
u.tokenIDs = []uint16{}
481-
return
482-
}
483-
484-
var filtered []uint16
485-
var unmapped []uint16
486-
for _, tokenID := range u.tokenIDs {
487-
if _, exists := u.tokenMapping[tokenID]; exists {
488-
filtered = append(filtered, tokenID)
489-
} else {
490-
unmapped = append(unmapped, tokenID)
491-
log.Warn("Token ID not in mapping, skipping price update for this token",
492-
"token_id", tokenID)
493-
}
494-
}
495-
496-
u.tokenIDs = filtered
497-
498-
if len(unmapped) > 0 {
499-
log.Warn("Some token IDs from contract are not mapped to trading pairs. Please update token mapping configuration if needed.",
500-
"unmapped_token_ids", unmapped,
501-
"mapped_token_ids", filtered)
502-
}
503-
504-
log.Info("Filtered token IDs by mapping",
505-
"filtered_count", len(filtered),
506-
"token_ids", filtered)
383+
return u.registryContract.GetSupportedIDList(callOpts)
507384
}

0 commit comments

Comments
 (0)