This pallet provides Chainlink price feed functionality to Substrate-based chains that include it.
It aims to mostly port the FluxAggregator Solidity contract but changes the design in the following ways:
- Storage Pruning: It is usually not advisable to keep all oracle state around, so a feed supports automatic storage pruning.
- View Functions: Substrate does not have an exact equivalent of Solidity view functions. We will instead make the client side smarter to read and interpret storage to surface the same information.
- Contracts --> Feeds: Vanilla Substrate does not have contracts. We will thus replace the deployment of a contract with
the creation of per-feed metadata and a storage prefix.
- Oracle metadata (admin and withdrawable funds) will be tracked globally (for the whole pallet) per oracle.
- On-Chain Interaction: In Substrate runtimes, pallets interact with other pallets differently from how users interact
with the pallet. We thus define two interfaces for interaction with the pallet:
- Rust traits for pallets (
FeedOracle
as well asFeedInterface
andMutableFeedInterface
) - Extrinsics for users of the chain
- Rust traits for pallets (
- Token (Separation of Concerns): Most of the interaction with the LINK token will happen via a dedicated token pallet
and thus the token-based interaction with the feed pallet will be minimal.
(Token changes are done via the configured type implementing the
Currency
trait.)- We do introduce a
Debt
storage item to track how much the pallet owes to oracles in cases where the reserves are insufficient.
- We do introduce a
Include the pallet in your runtime and configure it. The following snippet shows an example:
parameter_types! {
// Used to generate the fund account that pools oracle payments.
pub const FeedModule: PalletId = PalletId(*b"linkfeed");
// The minimum amount of tokens to keep in reserve for oracle payment.
pub const MinimumReserve: Balance = ExistentialDeposit::get() * 1000;
// Maximum length of the feed description.
pub const StringLimit: u32 = 30;
// Maximum number of oracles per feed.
pub const OracleCountLimit: u32 = 25;
// Maximum number of feeds.
pub const FeedLimit: FeedId = 100;
}
impl pallet_chainlink_feed::Config for Runtime {
type Event = Event;
type FeedId = FeedId;
type Value = Value;
// A module that provides currency functionality to manage
// oracle rewards. Balances in this example.
type Currency = Balances;
type PalletId = FeedPalletId;
type MinimumReserve = MinimumReserve;
type StringLimit = StringLimit;
type OracleCountLimit = OracleCountLimit;
type FeedLimit = FeedLimit;
// Provide your custom callback that gets called once a new value is available
// `()` is a noop
type OnAnswerHandler = ();
// Implementation of the WeightInfo trait for your runtime.
// Default weights available in the pallet but not recommended for production.
type WeightInfo = ChainlinkWeightInfo;
}
You need to inject the pallet into the consuming pallet in a similar way to how the feed pallet
depends on a pallet implementing the Currency
trait.
Define an associated Oracle
type implementing the FeedOracle
trait.
pub trait Config: frame_system::Config {
// -- snip --
type Oracle: FeedOracle<Self>;
}
You can then access a feed by calling the feed
and feed_mut
functions in your pallet code:
let feed = T::Oracle::feed(0.into()).ok_or(Error::<T>::FeedMissing)?;
let RoundData { answer, .. } = feed.latest_data();
do_something_with_answer(answer);
Most storage items in the pallet are scoped under a feed identified by an id. The storage layout follows the following structure.
Scoped to a feed:
FeedId => FeedConfig
(FeedId, RoundId) => Round
(FeedId, RoundId) => RoundDetails
(FeedId, requester: AccountId) => Requester
(FeedId, oracle_acc: AccountId) => OracleStatus
Associated with an account:
oracle_acc: AccountId => OracleMeta
feed_creator: AccountId => ()
Pallet-global values:
PalletAdmin
PendingPalletAdmin
FeedCounter
Access to a feed is done via the Feed
type which automatically syncs changes to storage on drop
(if the should_sync
flag is set). This makes it harder to forgot to update the storage with changes
but means that care should be taken with scoping the variable. (E.g. the feed needs to be initialized
within the closure passed to with_transaction_result
in order for the auto-sync writes to be
covered by the transactional write.)