Skip to content

Commit

Permalink
Revise docs
Browse files Browse the repository at this point in the history
  • Loading branch information
bbenligiray committed Aug 18, 2024
1 parent 6c5ee8e commit f403a1c
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 57 deletions.
58 changes: 31 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# Migration Guide from Chainlink to API3 Feeds

dApps migrate from Chainlink to API3 feeds for reasons such as:
dApps that are using Chainlink feeds may want to switch to API3 for reasons such as:

- API3 feeds are trust-minimized due to being based on first-party oracles, while alternatives rely on middlemen (Chainlink node operators, Wormhole validators, etc.) in addition to the data sources.
- API3 feeds are trust-minimized due to being based on first-party oracles, while alternatives have middlemen (Chainlink node operators, Wormhole validators, etc.) as a point of failure in addition to the data sources.
- API3 allows dApps to recoup the value they otherwise would have bled as MEV through the OEV mechanism, effectively providing dApps an entirely new revenue source.

In addition, it is a common case for a dApp that is designed to work with Chainlink feeds to be deployed on newly launched chains.
[API3 Market](https://market.api3.org/) has excellent chain coverage and enables managed feeds to be spun up in an on-demand manner, which often makes API3 feeds the only viable option in such cases.
In addition, it is a common need for a dApp that is designed to use Chainlink feeds to be deployed on a chain that Chainlink does not (adequately) support.
[API3 Market](https://market.api3.org/) has excellent chain coverage and enables feeds to be spun up in an on-demand manner, which often makes API3 feeds the only managed alternative in such cases.

A dApp can be designed to read a Chainlink feed in two ways:

- The Chainlink feed interface is baked into the dApp
- The Chainlink feed interface is integrated into the dApp through an adapter contract
- The Chainlink feed interface is baked into the dApp.
- The Chainlink feed interface is integrated into the dApp through an adapter contract.

Both alternatives can be migrated to using an API3 feed instead through the adapter contract provided in this repo.
Both alternatives can be migrated to using an API3 feed instead through the Api3ProxyToAggregatorV2V3Interface contract provided in this repo.

## Instructions

Expand All @@ -30,25 +30,25 @@ Both alternatives can be migrated to using an API3 feed instead through the adap
4. Get the address of the proxy contract that belongs to the feed.
For example, clicking the Integrate button at https://market.api3.org/polygon/eth-usd displays the proxy address `0x98643CB1BDA4060d8BD2dc19bceB0acF6F03ae17`.

5. Deploy Api3ProxyToAggregatorV2V3Interface that wraps this proxy.
5. Deploy the Api3ProxyToAggregatorV2V3Interface that wraps this proxy.
(Note that `NETWORK` is identical to what is in the Market URL.)

```sh
NETWORK=polygon PROXY_ADDRESS=0x98643CB1BDA4060d8BD2dc19bceB0acF6F03ae17 yarn deploy-deterministically
```
```sh
NETWORK=polygon PROXY_ADDRESS=0x98643CB1BDA4060d8BD2dc19bceB0acF6F03ae17 yarn deploy-deterministically
```

You can also just print the expected address after deployment.
You can also just print the expected address after deployment.

```sh
PROXY_ADDRESS=0x98643CB1BDA4060d8BD2dc19bceB0acF6F03ae17 yarn print-deterministic-deployment-address
```
```sh
PROXY_ADDRESS=0x98643CB1BDA4060d8BD2dc19bceB0acF6F03ae17 yarn print-deterministic-deployment-address
```

## Differences between API3 and Chainlink feed interfaces

There are many architectural, protocol-related and operational differences between Chainlink and API3 feeds.
For brevity, we will focus on the differences between the interfaces.

### API3 interface
### API3 feed interface

The API3 feed interface is simply

Expand Down Expand Up @@ -79,13 +79,13 @@ Let us reiterate the related contract docstrings:
>
> Try to be strict about validations, but be wary of:
>
> 1. Overly strict validation that may invalidate valid values
> 2. Mutable validation parameters that are controlled by a trusted party (which eliminate the trust-minimization guarantees of first-party oracles)
> 1. Overly strict validation that may invalidate valid values.
> 2. Mutable validation parameters that are controlled by a trusted party (which eliminate the trust-minimization guarantees of first-party oracles).
> 3. Validation parameters that need to be tuned according to external conditions.
> If these are not maintained as intended, the result will be equivalent to (1).
> Look up the Venus Protocol exploit as a result of the LUNA feed malfunction as an example.
### Chainlink interfaces
### Chainlink feed interfaces
At the time of writing this, Chainlink supports two interfaces and a combination of them:
Expand All @@ -100,29 +100,33 @@ There are two important points to note:
> Data feeds are updated in rounds.
> Rounds are identified by their `roundId`, which increases with each new round.
> This increase may not be monotonic.
2. Chainlink feeds allow past updates to be queried.
2. Chainlink feeds support past updates to be queried.
## When to use Api3ProxyToAggregatorV2V3Interface
Api3ProxyToAggregatorV2V3Interface should be used as is when the following apply:
- The dApp mainly depends on the current feed value (`latestAnswer()` of AggregatorInterface or `answer` returned by `latestRoundData()` of AggregatorV3Interface).
- In the case that the dApp uses the current feed timestamp (`latestTimestamp()` of AggregatorInterface or `updatedAt` returned by `latestRoundData()` of AggregatorV3Interface), it is only for a staleness check, e.g., to check if the feed has been updated in the last heartbeat interval.
- If the dApp uses the current feed timestamp (`latestTimestamp()` of AggregatorInterface or `updatedAt` returned by `latestRoundData()` of AggregatorV3Interface), it is only for a staleness check, e.g., to check if the feed has been updated in the last heartbeat interval.
- If any other values are used, they do not affect the contract or off-chain infrastructure logic.
For example, the dApp only emits `roundId` strictly for logging purposes.
For example, the dApp only emits `roundId` in an event, and strictly for logging purposes.
- The off-chain infrastructure does not depend on the events defined in AggregatorInterface.
Alternatively, Api3ProxyToAggregatorV2V3Interface should not be used as is, and a more specialized adapter contract needs to be implemented if any of the following applies:
In contrast, Api3ProxyToAggregatorV2V3Interface should not be used as is, and a more specialized adapter contract should be implemented if any of the following applies:
- The dApp logic depends on Chainlink feed idiosyncrasies, such as the round ID increasing with every update.
- The dApp depends on being able to query past values using `getAnswer()` or `getTimestamp()` of AggregatorInterface, or `getRoundData()` of AggregatorV3Interface.
- The off-chain infrastructure depends on the events defined in AggregatorInterface.
### Specialized adapter contracts
An adapter that satisfies all Chainlink feed idiosyncrasies would need to create a new round each time its latest values are read and emit the respective events.
For example, `roundId` can be a storage variable that gets incremented each time a function starting with `latest-` is called.
Furthermore, users that are planning to refer to a round in the past would need to ensure that the round is created by sending a transaction that calls any of the functions that starts with `latest-`.
An adapter that simulates all Chainlink feed idiosyncrasies would need to create a new round each time its latest values are read and emit the respective events.
For example, `roundId` can be a storage variable that gets incremented each time a function starting with `latest-` is called, during which the respective value and timestamp would also be stored and the event would be emitted.
Furthermore, users that are planning to refer to a round in the past would need to ensure that such a round has been created by sending a transaction that calls any of the functions that starts with `latest-` at the respective point in time.
Another inconsistency in behavior is that Api3ProxyToAggregatorV2V3Interface does not guarantee that the timestamp of a feed will never decrease.
For example, in the case that the data sources of an API3 feed are updated to a new set whose latest updates are less recent, the feed timestamp will decrease.
If the dApp depends on the feed timestamps to never decrease, `block.timestamp` can be used as the latest timestamp returned by the feed.
For gas optimization, specialized adapter contracts should replicate Chainlink feed idiosyncrasies only to the extent necessary.
For gas optimization, specialized adapter contracts should simulate Chainlink feed idiosyncrasies only to the extent necessary.
The development of such alternative adapter contracts is beyond the scope of this repo.
42 changes: 12 additions & 30 deletions contracts/Api3ProxyToAggregatorV2V3Interface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@ interface IProxy {
function read() external view returns (int224 value, uint32 timestamp);
}

/// @title Contract for migrating from using Chainlink to API3 feeds
/// @notice This contract wraps an API3 feed proxy contract and implements
/// AggregatorV2V3Interface. By deploying this contract with the appropriate
/// API3 feed proxy address (refer to https://market.api3.org/), you can use it
/// as if it is a Chainlink feed contract.
/// @dev API3 feed proxies are deployed deterministically and their addresses
/// can be derived using the npm package (at)api3/contracts. This contract is
/// recommended to be deployed deterministically for its address to be easily
/// verifiable as well.
/// @title Contract for migrating from Chainlink to API3 Feeds
/// @notice This contract wraps an API3 feed proxy contract and approximates
/// AggregatorV2V3Interface so that it can be treated as a Chainlink feed
/// contract. Please refer to the
/// https://github.com/api3dao/migrate-from-chainlink-to-api3 README for
/// guidance about if the approximation is sufficient for a specific use-case.
contract Api3ProxyToAggregatorV2V3Interface is AggregatorV2V3Interface {
error Api3ProxyAddressIsZero();

Expand Down Expand Up @@ -47,16 +44,9 @@ contract Api3ProxyToAggregatorV2V3Interface is AggregatorV2V3Interface {
/// @dev A Chainlink feed contract returns the block timestamp at which the
/// feed was last updated. On the other hand, an API3 feed timestamp
/// denotes the point in time at which the first-party oracles signed the
/// data used to do the last update. We find that to be a reasonable
/// approximation in the case that it is being used to check if the last
/// update is stale.
/// An important point to note is that in the case that the proxy reads a
/// dAPI (i.e., a name that is pointed to a Beacon or Beacon set), pointing
/// the dAPI to another feed that has been updated less recently would
/// result in this function to return a smaller value than it once did.
/// Therefore, if your contract depends on what `latestTimestamp()` returns
/// to never decrease, you are recommended to use a specialized adapter
/// (e.g., one that returns `block.timestamp` here).
/// data used to do the last update. We find this to be a reasonable
/// approximation in the case that the timestamp is being used to check if
/// the last update is stale.
function latestTimestamp()
external
view
Expand All @@ -67,15 +57,7 @@ contract Api3ProxyToAggregatorV2V3Interface is AggregatorV2V3Interface {
}

/// @dev Since API3 feeds do not have the concept of rounds, we return the
/// block number as an alternative that is similarly guaranteed to never
/// decrease.
/// An important point to note is that this may cause two different feed
/// values to be read with the same round ID, for example, if the feed is
/// read, updated, and read again in the same block. Therefore, if your
/// contract depends on values read with a specific round ID to be
/// identical, you are recommended to use a specialized adapter (e.g., one
/// that keeps the round ID in a counter that gets incremented every time
/// the feed is read).
/// block number as a replacement that is guaranteed to never decrease.
function latestRound() external view override returns (uint256) {
return block.number;
}
Expand Down Expand Up @@ -114,7 +96,7 @@ contract Api3ProxyToAggregatorV2V3Interface is AggregatorV2V3Interface {

/// @dev A unique version is chosen to easily check if an unverified
/// contract that acts as a Chainlink feed is an
/// Api3ProxyToAggregatorV2V3Interface
/// Api3ProxyToAggregatorV2V3Interface.
function version() external pure override returns (uint256) {
return 4913;
}
Expand Down Expand Up @@ -145,7 +127,7 @@ contract Api3ProxyToAggregatorV2V3Interface is AggregatorV2V3Interface {
}

/// @dev Similar to `latestAnswer()`, we leave the validation of the
/// returned values to the caller.
/// returned value to the caller.
function latestRoundData()
external
view
Expand Down

0 comments on commit f403a1c

Please sign in to comment.