Skip to content

Latest commit

 

History

History
560 lines (430 loc) · 15.8 KB

File metadata and controls

560 lines (430 loc) · 15.8 KB

Deployment Cookbook

Scenarios, commands, and copy‑paste snippets for common tasks.

Quick Start: Complete Local Deployment

1. Deploy Everything (One Command)

# Start Anvil in separate terminal first
anvil

# Then deploy oracle + factory + feeds + adapters
export ADMIN=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
make anvil-bootstrap-all

Expected Output:

✅ Oracle deployed: 0x5FbDB2315678afecb367f032d93F642f64180aa3
✅ Factory deployed: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
✅ Feed created: ar/bytes-testv1 (0x3f32666a...)
✅ Adapter deployed: 0xD9164F568A7d21189F61bd53502BdE277883A0A2

2. Start Operator Bot

# Copy oracle address from output above
export ORACLE=0x5FbDB2315678afecb367f032d93F642f64180aa3

# For AR/byte feed (18 decimals, ~1.5e-9 AR/byte)
node scripts/bot/operators-bot.mjs \
  --rpc http://127.0.0.1:8545 \
  --oracle $ORACLE \
  --feedDesc "ar/bytes-testv1" \
  --interval 30000 \
  --priceBase 1.5e-9

# Or for AR/USD feed (8 decimals, ~$6 per AR)
node scripts/bot/operators-bot.mjs \
  --rpc http://127.0.0.1:8545 \
  --oracle $ORACLE \
  --feedDesc "ar/usd-testv1" \
  --interval 30000 \
  --priceBase 6

Expected Output:

🚀 Operator bot starting
   📊 Feed: AR/byte (decimals=18)
   💰 Base price: 1.5e-9 → 1500000000 (scaled to 18 decimals)
✅ Initialized 6/6 valid operator wallets
📤 Starting new round 1 for ar/bytes-testv1
  ✍️  0xf39F…2266 → 1485000000  ✅
  ✍️  0x7099…79C8 → 1515000010  ✅
  ✍️  0x3C44…93BC → 1492000020  ✅
  ✅ Quorum (3) reached—skipping remaining operators
🟢 latest round=1 answer=1492000020 age=1s changed=🔄

3. Test Integration

# Deploy test consumer (copy adapter address from step 1)
export ADAPTER=0xD9164F568A7d21189F61bd53502BdE277883A0A2
forge script script/DeployTestConsumer.s.sol:DeployTestConsumer \
  --rpc-url http://127.0.0.1:8545 \
  --broadcast \
  --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

# Run integration test (copy consumer address from output)
export CONSUMER=0x610178dA211FEF7D417bC0e6FeD39F05609AD788
node scripts/test-adapter-consumer.mjs

Expected Output:

🧪 Testing Adapter & Consumer Integration
📊 Testing Oracle...
  ✅ Latest Round Data: Round ID: 2, Answer: 9992000030
🔌 Testing Adapter (Chainlink-compatible)...
  ✅ Adapter data matches Oracle
🛒 Testing Consumer Contract...
  ✅ Consumer data matches Oracle & Adapter
✅ ALL TESTS PASSED!

Addresses & Feed IDs

Computing Feed IDs

Feed IDs are bytes32 values computed as the keccak256 hash of the feed description string:

Solidity:

bytes32 feedId = keccak256(abi.encodePacked("AR/byte"));

JavaScript/TypeScript (ethers v6):

import { ethers } from "ethers";
const feedId = ethers.id("AR/byte");  // id() === keccak256(toUtf8Bytes())

Command line (cast):

export FEED_ID=$(cast keccak "AR/byte")

Note: The feed description is case-sensitive. "AR/byte" and "ar/byte" produce different feed IDs.

Predicting Adapter Addresses

For deterministic CREATE2 deployments, predict adapter addresses via factory:

cast call $FACTORY "computeAdapterAddress(bytes32)" $FEED_ID --rpc-url $RPC_URL

New Oracle + Factory + Feeds + Adapters (One Shot)

For Alphanet (or other live networks)

# Set admin credentials
export ADMIN=0xYourAdminAddress
export ADMIN_PRIVATE_KEY=0xYourAdminPrivateKey

# Deploy everything (uses --slow and --gas-estimate-multiplier 300 for alphanet)
make alphanet-bootstrap-all

Note: Alphanet deployment uses --slow flag to ensure transactions are processed sequentially and --gas-estimate-multiplier 300 to account for higher gas requirements on alphanet.

For Local Anvil

# Anvil uses default test account
make anvil-bootstrap-all FEEDS_FILE=feeds-anvil.json

Modular Flow (Production)

Use this step-by-step approach for production deployments to verify each stage.

Step 1: Deploy Oracle

# Deploy PriceLoomOracle with secure admin address (e.g., Multi-Sig)
export ADMIN=0xYourMultiSigAddress
forge create src/PriceLoomOracle.sol:PriceLoomOracle \
  --rpc-url https://alphanet.load.network \
  --constructor-args $ADMIN \
  --private-key $DEPLOYER_PRIVATE_KEY

Expected Output:

Deployer: 0x123...
Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Transaction hash: 0xabc...

Step 2: Deploy Factory

# Copy oracle address from Step 1
export ORACLE=0x5FbDB2315678afecb367f032d93F642f64180aa3
make alphanet-deploy-factory

Expected Output:

✅ Factory deployed: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
   Bound to oracle: 0x5FbDB2315678afecb367f032d93F642f64180aa3

Step 3: Create Feeds from JSON

# Ensure feeds.json is configured with your feed parameters
export ORACLE=0x5FbDB2315678afecb367f032d93F642f64180aa3
make alphanet-create-feeds-json FEEDS_FILE=feeds.json

Expected Output:

Creating feeds from feeds.json...
✅ Feed created: AR/byte (feedId: 0x3f32...)
✅ Feed created: ETH/USD (feedId: 0x7a21...)
✅ 2 feeds created successfully

Step 4: Deploy Adapters

# Copy factory address from Step 2
export FACTORY=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
make alphanet-deploy-adapters-json FEEDS_FILE=feeds.json

Expected Output:

Deploying adapters from feeds.json...
✅ Adapter deployed: AR/byte → 0xD916...
✅ Adapter deployed: ETH/USD → 0x8A3C...
✅ 2 adapters deployed successfully

Step 5: Verify Deployment

# Check oracle has feeds
cast call $ORACLE "getConfig(bytes32)" $(cast keccak "AR/byte") --rpc-url https://alphanet.load.network

# Check adapter points to correct oracle
export ADAPTER=0xD9164F568A7d21189F61bd53502BdE277883A0A2
cast call $ADAPTER "decimals()" --rpc-url https://alphanet.load.network

Expected Output:

# getConfig returns tuple with decimals, min/max submissions, etc.
# decimals() returns: 8 (or your configured decimals)

Feed Configuration Best Practices

Choosing Decimals

The decimals field determines price precision. Choose based on the value range:

Feed Type Price Range Recommended Decimals Example
AR/byte ~1.5e-9 AR 18 1.5e-9 AR → 1,500,000,000 (18 decimals)
AR/USD ~$6 USD 8 $6 → 600,000,000 (8 decimals)
ETH/USD ~$2000 USD 8 $2000 → 200,000,000,000 (8 decimals)
Stablecoins ~$1 USD 8 $1 → 100,000,000 (8 decimals)

Key Guidelines:

  • Use 18 decimals for very small values (< 1e-6) to avoid rounding to zero
  • Use 8 decimals for standard token/USD pairs (Chainlink convention)
  • For rebalancing/liquidation protocols: Higher decimals = more precision for detecting small changes
  • Decimals are immutable after feed creation (requires new feed to change)

Example: AR/Byte Feed Configuration

{
  "id": "ar/bytes-v1",
  "decimals": 18,
  "description": "AR per byte",
  "minSubmissions": 3,
  "maxSubmissions": 5,
  "heartbeatSec": 3600,
  "deviationBps": 50,
  "timeoutSec": 7200,
  "minPrice": "0",
  "maxPrice": "10000000000000000000000"
}

Calculation Example:

  • Real price: 1.5e-9 AR/byte
  • With 18 decimals: 1.5e-9 * 1e18 = 1,500,000,000
  • Consumer calculation: totalCost = (bytes * price) / 1e18

Create One Feed (Env‑Driven)

# Set all feed parameters via environment variables
export ORACLE=0x5FbDB2315678afecb367f032d93F642f64180aa3
export FEED_DESC="AR/byte"
export DECIMALS=18  # Use 18 for AR/byte, 8 for AR/USD
export MIN_SUBMISSIONS=2
export MAX_SUBMISSIONS=3
export HEARTBEAT_SEC=3600
export DEVIATION_BPS=50
export TIMEOUT_SEC=900
export MIN_PRICE=0
export MAX_PRICE=10000000000000000000000
export DESCRIPTION="AR/byte price feed"
export OPERATORS_JSON='["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266","0x70997970C51812dc3A010C7d01b50e0d17dc79C8","0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"]'

# Create feed
make alphanet-create-feed-env

Expected Output:

Creating feed AR/byte...
  feedId: 0x3f32666a3e43d4d82c6c5b5e89e2d0b8c8fb4c8a9c20b7b0d8c6e8f8a4b2c6d8
  decimals: 18
  operators: 3
✅ Feed created successfully

Pause, Poke, Config Changes

Pause Submissions

export ORACLE=0x5FbDB2315678afecb367f032d93F642f64180aa3
export RPC_URL=https://alphanet.load.network
export PRIVATE_KEY=0xYourPrivateKey

cast send $ORACLE "pause()" --rpc-url $RPC_URL --private-key $PRIVATE_KEY

Expected Output:

status              1 (success)
✅ Oracle paused

Poke Feeds (Force Close Timed-Out Rounds)

# Poke single feed
export FEED_ID=$(cast keccak "AR/byte")
cast send $ORACLE "poke(bytes32)" $FEED_ID --rpc-url $RPC_URL --private-key $PRIVATE_KEY

# Or poke all feeds from JSON (works while paused)
make alphanet-poke-feeds-json ORACLE=$ORACLE FEEDS_FILE=feeds.json

Expected Output:

✅ Round 25 finalized for AR/byte
✅ Round 18 rolled forward (stale) for ETH/USD

Update Feed Config

# Ensure no open round before updating config
export FEED_ID=$(cast keccak "AR/byte")

# Update config (example: change heartbeat to 7200 seconds)
cast send $ORACLE "setFeedConfig(bytes32,(uint8,uint8,uint8,uint8,uint32,uint32,uint32,int256,int256,string))" \
  $FEED_ID "(8,2,3,0,7200,50,900,0,10000000000000000000000,'AR/byte updated')" \
  --rpc-url $RPC_URL --private-key $PRIVATE_KEY

Expected Output:

status              1 (success)
✅ Feed config updated

Add/Remove Operators

Add Operator

export FEED_ID=$(cast keccak "AR/byte")
export NEW_OPERATOR=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc

cast send $ORACLE "addOperator(bytes32,address)" $FEED_ID $NEW_OPERATOR \
  --rpc-url $RPC_URL --private-key $PRIVATE_KEY

Expected Output:

status              1 (success)
✅ Operator 0x9965...A4dc added to feed AR/byte

Remove Operator

export OLD_OPERATOR=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc

cast send $ORACLE "removeOperator(bytes32,address)" $FEED_ID $OLD_OPERATOR \
  --rpc-url $RPC_URL --private-key $PRIVATE_KEY

Expected Output:

status              1 (success)
✅ Operator 0x9965...A4dc removed from feed AR/byte

Verify Operators

# List all operators for a feed
cast call $ORACLE "getOperators(bytes32)" $FEED_ID --rpc-url $RPC_URL

Expected Output:

[0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC]

Consumer Freshness (Solidity)

(uint80 roundId, int256 answer,, uint256 updatedAt, uint80 answeredInRound) = oracle.latestRoundData(feedId);
require(roundId == answeredInRound, "stale-forwarded");
require(block.timestamp - updatedAt <= MAX_DELAY, "stale-age");

Off‑Chain Signing & Operator Bot

Test Operator Bot (Local Anvil)

After deploying oracle and feeds, run the operator bot to start submitting prices:

# Install dependencies
npm install

# Run bot with deployed addresses
node scripts/bot/operators-bot.mjs \
  --rpc http://127.0.0.1:8545 \
  --oracle $ORACLE \
  --feedDesc "ar/bytes-testv1" \
  --interval 30000

The bot will:

  • Automatically match on-chain operators with Anvil test keys
  • Submit prices sequentially (minSubmissions reached, then stop)
  • Recover automatically from stuck rounds via poke()
  • Handle pause/unpause gracefully

See docs/operator-guide.md for production setup and docs/operator-bot-fix-report.md for architecture details.

Integration Testing

Verify the full stack (oracle → adapter → consumer):

# Deploy test consumer
export ADAPTER=0xAdapterAddress
forge script script/DeployTestConsumer.s.sol:DeployTestConsumer \
  --rpc-url $RPC_URL --broadcast --sender $ADMIN

# Test integration (specify which feed to test)
export CONSUMER=0xConsumerAddress
export FEED_DESC=ar/bytes-testv1  # Or ar/usd-testv1 for AR/USD feed
node scripts/test-adapter-consumer.mjs

This tests:

  • Oracle latest round data
  • Adapter Chainlink compatibility
  • Consumer reads through adapter
  • Historical data access

Testing different feeds:

# Test AR/bytes feed (18 decimals)
export RPC_URL=https://alphanet.load.network
export ORACLE=0x8A0ffF4C118767c818C9F8a30c39E8F9bB36CEd5
export ADAPTER=0xCbbbff18714b1276756980BA7691C67052C9C9ff
export CONSUMER=0x5a65F24AEAd3154aFe3cc9c46806e3D4D2a00118
export FEED_DESC=ar/bytes-testv1
node scripts/test-adapter-consumer.mjs

# Test AR/USD feed (8 decimals)
export RPC_URL=https://alphanet.load.network
export ORACLE=0x8A0ffF4C118767c818C9F8a30c39E8F9bB36CEd5
export ADAPTER=0x920380c14685b88Bb8f6D6A35def83D085152550
export CONSUMER=0xdb067EEFC660e1b38546e670aCAC08D970911fF2
export FEED_DESC=ar/usd-testv1
node scripts/test-adapter-consumer.mjs

Important: Make sure FEED_DESC matches the feed that the ADAPTER is bound to, otherwise the test will query the Oracle with the wrong feed ID and report mismatches.

Network Safety & Diagnostics

  • Use anvil-<target> or alphanet-<target> prefixed make targets to auto-set RPC_URL and CHAIN_ID.
  • make doctor prints the selected RPC/CHAIN_ID and the live chain-id from the RPC.
  • All script targets verify the remote chain-id before broadcasting and pass --chain-id to sign with the correct domain.

Troubleshooting

Deployment Fails with "Out of Gas" on Alphanet

Symptom: Transactions succeed in simulation but fail when broadcast to alphanet.

Cause: Alphanet has different gas economics than local simulation.

Solution: The bootstrap-all target now uses --slow and --gas-estimate-multiplier 300 by default. If you're using custom deployment scripts, add these flags:

forge script YourScript.s.sol:YourScript \
  --rpc-url https://alphanet.load.network \
  --chain-id 9496 \
  --broadcast \
  --slow \
  --gas-estimate-multiplier 300

Transaction Ordering Issues

Symptom: Later transactions fail because they depend on earlier transactions that haven't confirmed yet.

Cause: Multiple dependent transactions broadcast simultaneously.

Solution: Use the --slow flag to wait for each transaction to confirm before sending the next one.

Operator Bot Can't Find Private Keys

Symptom: Bot shows "Could not find private key for registered operator" warnings.

Cause: The PRIVATE_KEYS_JSON environment variable is not set or contains keys that don't match on-chain operators.

Solution:

# Verify your keys.json matches on-chain operators
node -e "
const keys = require('./keys.json');
const { ethers } = require('ethers');
keys.forEach((key, i) => {
  const wallet = new ethers.Wallet(key);
  console.log(\`[\${i}] \${wallet.address}\`);
});
"

# Compare with on-chain operators
cast call $ORACLE "getOperators(bytes32)" $FEED_ID --rpc-url $RPC_URL

# Set PRIVATE_KEYS_JSON correctly
export PRIVATE_KEYS_JSON=$(cat keys.json | jq -c)

View Deployment Addresses

After successful deployment, addresses are saved to out/e2e-addresses.txt:

# View all deployed addresses
make show-addresses

# Or manually extract
cat out/e2e-addresses.txt

# Export to environment variables
export ORACLE=$(awk -F= '/^oracle=/{print $2}' out/e2e-addresses.txt)
export FACTORY=$(awk -F= '/^factory=/{print $2}' out/e2e-addresses.txt)

Related Documentation