Skip to content

Adds dappId to combinator contracts #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,10 @@ The `NETWORK` variable should be set to a chain name as defined by `@api3/contra
- **`NormalizedApi3ReaderProxyV1`**:
- `NETWORK`: Target network name.
- `FEED`: Address of the external data feed (e.g., a Chainlink `AggregatorV2V3Interface` compatible feed).
- `DAPP_ID`: The dApp ID to associate with this proxy.
- Example:
```bash
NETWORK=polygon FEED=0xExternalFeedAddress pnpm deploy:NormalizedApi3ReaderProxyV1
NETWORK=polygon FEED=0xExternalFeedAddress DAPP_ID=YourDappId pnpm deploy:NormalizedApi3ReaderProxyV1
```

- **`ProductApi3ReaderProxyV1`**:
Expand Down Expand Up @@ -183,13 +184,13 @@ Imagine your dApp requires a USD/ETH price feed with 8 decimal places, but the a
1. **Deploy `InverseApi3ReaderProxyV1`**:
- Input `PROXY`: Address of the ETH/USD `IApi3ReaderProxy` dAPI.
- Output: An `IApi3ReaderProxy` contract. This deployed instance of `InverseApi3ReaderProxyV1` reads USD/ETH.
- Example command: `NETWORK=your_network PROXY=0xAddressOfEthUsdDapi pnpm deploy:InverseApi3ReaderProxyV1`
- Example command: `NETWORK=base PROXY=0xAddressOfEthUsdDapi pnpm deploy:InverseApi3ReaderProxyV1`

2. **Deploy `ScaledApi3FeedProxyV1`**:
- Input `PROXY`: Address of the `InverseApi3ReaderProxyV1` instance deployed in step 1.
- Input `DECIMALS`: `8`.
- Output: An `AggregatorV2V3Interface` contract. This deployed instance of `ScaledApi3FeedProxyV1` reads USD/ETH scaled to 8 decimals.
- Example command: `NETWORK=your_network PROXY=0xAddressOfDeployedInverseApi3ReaderProxyV1FromStep1 DECIMALS=8 pnpm deploy:ScaledApi3FeedProxyV1`
- Example command: `NETWORK=base PROXY=0xAddressOfDeployedInverseApi3ReaderProxyV1FromStep1 DECIMALS=8 pnpm deploy:ScaledApi3FeedProxyV1`
_Note: Replace `0xAddressOfDeployedInverseApi3ReaderProxyV1FromStep1` with the actual address obtained from the deployment artifact of step 1._

This pipeline successfully provides the dApp with the required USD/ETH feed at the desired precision and interface.
Expand Down Expand Up @@ -229,16 +230,17 @@ To derive the desired uStETH/USD feed and make it compatible with the Api3 ecosy
1. **Deploy `NormalizedApi3ReaderProxyV1`**:
- This step adapts the external uStETH/ETH feed, which implements the `AggregatorV2V3Interface`, to the `IApi3ReaderProxy` interface. A key function of `NormalizedApi3ReaderProxyV1` is to read the `decimals()` from the external feed and automatically scale its value to the 18 decimal places expected by the `IApi3ReaderProxy` interface. For instance, if the uStETH/ETH feed returns its value with a different precision (e.g., 8 or 36 decimals), this proxy will normalize it.
- Input `FEED`: Address of the external uStETH/ETH `AggregatorV2V3Interface` feed.
- Input `DAPP_ID`: The dApp ID to associate with this proxy.
- Output: An `IApi3ReaderProxy` contract. This deployed instance of `NormalizedApi3ReaderProxyV1` reads uStETH/ETH, with its value normalized to 18 decimals.
- Example command: `NETWORK=your_network FEED=0xAddressOfExternal_uStETH_ETH_Feed pnpm deploy:NormalizedApi3ReaderProxyV1`
- Example command: `NETWORK=base FEED=0xAddressOfExternal_uStETH_ETH_Feed DAPP_ID=YourDappId pnpm deploy:NormalizedApi3ReaderProxyV1`

2. **Deploy `ProductApi3ReaderProxyV1` to calculate uStETH/USD**:
- This step multiplies the normalized uStETH/ETH rate by the ETH/USD rate from the Api3 dAPI.
- Input `PROXY1`: Address of the `NormalizedApi3ReaderProxyV1` instance deployed in step 1.
- Input `PROXY2`: Address of the existing ETH/USD `IApi3ReaderProxy` dAPI.
- Output: An `IApi3ReaderProxy` contract. This deployed instance of `ProductApi3ReaderProxyV1` reads uStETH/USD.
- Calculation: `(uStETH/ETH) * (ETH/USD) = uStETH/USD`.
- Example command: `NETWORK=your_network PROXY1=0xAddressOfDeployedNormalizedApi3ReaderProxyV1FromStep1 PROXY2=0xAddressOfApi3EthUsdDapi pnpm deploy:ProductApi3ReaderProxyV1`
- Example command: `NETWORK=base PROXY1=0xAddressOfDeployedNormalizedApi3ReaderProxyV1FromStep1 PROXY2=0xAddressOfApi3EthUsdDapi pnpm deploy:ProductApi3ReaderProxyV1`
_(Note: Replace `0xAddressOfDeployedNormalizedApi3ReaderProxyV1FromStep1` with the actual address obtained from the deployment artifact of step 1)._

This scenario highlights how `NormalizedApi3ReaderProxyV1` serves as a crucial bridge, enabling dApps to integrate valuable data from external sources (that may not meet Api3 dAPI listing criteria or are simply outside the current offerings) and combine it with trusted Api3 dAPIs using the standard set of combinator tools.
Expand Down
16 changes: 10 additions & 6 deletions contracts/InverseApi3ReaderProxyV1.sol
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

import "@api3/contracts/interfaces/IApi3ReaderProxy.sol";
import "@api3/contracts/api3-server-v1/proxies/interfaces/IApi3ReaderProxyV1.sol";
import "./interfaces/IInverseApi3ReaderProxyV1.sol";

/// @title An immutable proxy contract that inverts the value returned by an
/// IApi3ReaderProxy data feed
/// IApi3ReaderProxyV1 data feed
/// @dev This contract implements the AggregatorV2V3Interface to be compatible
/// with Chainlink aggregators. This allows the contract to be used as a drop-in
/// replacement for Chainlink aggregators in existing dApps.
/// Refer to https://github.com/api3dao/migrate-from-chainlink-to-api3 for more
/// information about the Chainlink interface implementation.
contract InverseApi3ReaderProxyV1 is IInverseApi3ReaderProxyV1 {
/// @notice IApi3ReaderProxy contract address
/// @notice IApi3ReaderProxyV1 contract address
address public immutable override proxy;

/// @param proxy_ IApi3ReaderProxy contract address
/// @notice dApp ID of the proxy
uint256 public immutable override dappId;

/// @param proxy_ IApi3ReaderProxyV1 contract address
constructor(address proxy_) {
if (proxy_ == address(0)) {
revert ZeroProxyAddress();
}
proxy = proxy_;
dappId = IApi3ReaderProxyV1(proxy_).dappId();
}

/// @notice Returns the inverted value of the underlying IApi3ReaderProxy
/// @notice Returns the inverted value of the underlying IApi3ReaderProxyV1
/// @dev Calculates `int224(1e36) / baseValue`. The operation will revert if
/// `baseValue` is zero. If `baseValue` is non-zero but its absolute value is
/// greater than `1e36`, the result of the integer division will be `0`.
Expand All @@ -35,7 +39,7 @@ contract InverseApi3ReaderProxyV1 is IInverseApi3ReaderProxyV1 {
override
returns (int224 value, uint32 timestamp)
{
(int224 baseValue, uint32 baseTimestamp) = IApi3ReaderProxy(proxy)
(int224 baseValue, uint32 baseTimestamp) = IApi3ReaderProxyV1(proxy)
.read();

if (baseValue == 0) {
Expand Down
7 changes: 6 additions & 1 deletion contracts/NormalizedApi3ReaderProxyV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ contract NormalizedApi3ReaderProxyV1 is INormalizedApi3ReaderProxyV1 {
/// @notice Chainlink AggregatorV2V3Interface contract address
address public immutable override feed;

/// @notice dApp ID of the proxy
uint256 public immutable override dappId;

/// @notice Pre-calculated factor for scaling the feed's value to 18
/// decimals.
int256 public immutable scalingFactor;
Expand All @@ -24,7 +27,8 @@ contract NormalizedApi3ReaderProxyV1 is INormalizedApi3ReaderProxyV1 {
bool public immutable isUpscaling;

/// @param feed_ The address of the Chainlink AggregatorV2V3Interface feed
constructor(address feed_) {
/// @param dappId_ dApp ID of the proxy
constructor(address feed_, uint256 dappId_) {
if (feed_ == address(0)) {
revert ZeroProxyAddress();
}
Expand All @@ -36,6 +40,7 @@ contract NormalizedApi3ReaderProxyV1 is INormalizedApi3ReaderProxyV1 {
revert NoNormalizationNeeded();
}
feed = feed_;
dappId = dappId_;
uint8 delta = feedDecimals_ > 18
? feedDecimals_ - 18
: 18 - feedDecimals_;
Expand Down
16 changes: 10 additions & 6 deletions contracts/PriceCappedApi3ReaderProxyV1.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

import "@api3/contracts/interfaces/IApi3ReaderProxy.sol";
import "@api3/contracts/api3-server-v1/proxies/interfaces/IApi3ReaderProxyV1.sol";
import "./interfaces/IPriceCappedApi3ReaderProxyV1.sol";

/**
Expand All @@ -21,16 +21,19 @@ import "./interfaces/IPriceCappedApi3ReaderProxyV1.sol";
* floored at 0 if `lowerBound_` is 0.
*/
contract PriceCappedApi3ReaderProxyV1 is IPriceCappedApi3ReaderProxyV1 {
/// @notice IApi3ReaderProxy contract address
/// @notice IApi3ReaderProxyV1 contract address
address public immutable override proxy;

/// @notice dApp ID of the proxy
uint256 public immutable override dappId;

/// @notice The minimum price (inclusive) that this proxy will report.
int224 public immutable override lowerBound;

/// @notice The maximum price (inclusive) that this proxy will report.
int224 public immutable override upperBound;

/// @param proxy_ IApi3ReaderProxy contract address
/// @param proxy_ IApi3ReaderProxyV1 contract address
/// @param lowerBound_ The minimum price (inclusive) this proxy will report
/// @param upperBound_ The maximum price (inclusive) this proxy will report
constructor(address proxy_, int224 lowerBound_, int224 upperBound_) {
Expand All @@ -44,12 +47,13 @@ contract PriceCappedApi3ReaderProxyV1 is IPriceCappedApi3ReaderProxyV1 {
revert UpperBoundMustBeGreaterOrEqualToLowerBound();
}
proxy = proxy_;
dappId = IApi3ReaderProxyV1(proxy_).dappId();
lowerBound = lowerBound_;
upperBound = upperBound_;
}

/// @notice Reads the current value and timestamp from the underlying
/// `IApi3ReaderProxy` and applies the price bounds.
/// `IApi3ReaderProxyV1` and applies the price bounds.
/// @dev If the `baseValue` from the underlying proxy is less than
/// `lowerBound`, then `lowerBound` is returned as the `value`. If
/// `baseValue` is greater than `upperBound`, then `upperBound` is returned.
Expand All @@ -63,7 +67,7 @@ contract PriceCappedApi3ReaderProxyV1 is IPriceCappedApi3ReaderProxyV1 {
override
returns (int224 value, uint32 timestamp)
{
(int224 baseValue, uint32 baseTimestamp) = IApi3ReaderProxy(proxy)
(int224 baseValue, uint32 baseTimestamp) = IApi3ReaderProxyV1(proxy)
.read();

timestamp = baseTimestamp;
Expand All @@ -82,7 +86,7 @@ contract PriceCappedApi3ReaderProxyV1 is IPriceCappedApi3ReaderProxyV1 {
/// @return True if the base value is less than `lowerBound` or greater
/// than `upperBound`, false otherwise.
function isCapped() external view returns (bool) {
(int224 baseValue, ) = IApi3ReaderProxy(proxy).read();
(int224 baseValue, ) = IApi3ReaderProxyV1(proxy).read();
return baseValue < lowerBound || baseValue > upperBound;
}

Expand Down
27 changes: 18 additions & 9 deletions contracts/ProductApi3ReaderProxyV1.sol
Original file line number Diff line number Diff line change
@@ -1,38 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

import "@api3/contracts/interfaces/IApi3ReaderProxy.sol";
import "@api3/contracts/api3-server-v1/proxies/interfaces/IApi3ReaderProxyV1.sol";
import "./interfaces/IProductApi3ReaderProxyV1.sol";

/// @title An immutable proxy contract that is used to read a composition of two
/// IApi3ReaderProxy data feeds by multiplying their values
/// IApi3ReaderProxyV1 data feeds by multiplying their values
/// @dev This contract implements the AggregatorV2V3Interface to be compatible
/// with Chainlink aggregators. This allows the contract to be used as a drop-in
/// replacement for Chainlink aggregators in existing dApps.
/// Refer to https://github.com/api3dao/migrate-from-chainlink-to-api3 for more
/// information about the Chainlink interface implementation.
contract ProductApi3ReaderProxyV1 is IProductApi3ReaderProxyV1 {
/// @notice First IApi3ReaderProxy contract address
/// @notice First IApi3ReaderProxyV1 contract address
address public immutable override proxy1;

/// @notice Second IApi3ReaderProxy contract address
/// @notice Second IApi3ReaderProxyV1 contract address
address public immutable override proxy2;

/// @param proxy1_ First IApi3ReaderProxy contract address
/// @param proxy2_ Second IApi3ReaderProxy contract address
/// @notice The dApp ID of the two proxies
uint256 public immutable override dappId;

/// @param proxy1_ First IApi3ReaderProxyV1 contract address
/// @param proxy2_ Second IApi3ReaderProxyV1 contract address
constructor(address proxy1_, address proxy2_) {
if (proxy1_ == address(0) || proxy2_ == address(0)) {
revert ZeroProxyAddress();
}
if (proxy1_ == proxy2_) {
revert SameProxyAddress();
}
uint256 dappId1 = IApi3ReaderProxyV1(proxy1_).dappId();
uint256 dappId2 = IApi3ReaderProxyV1(proxy2_).dappId();
if (dappId1 != dappId2) {
revert DappIdMismatch();
}
proxy1 = proxy1_;
proxy2 = proxy2_;
dappId = dappId1;
}

/// @notice Returns the current value and timestamp of the rate composition
/// between two IApi3ReaderProxy proxies by multiplying their values
/// between two IApi3ReaderProxyV1 proxies by multiplying their values
/// @dev Calculates product as `(int256(value1) * int256(value2)) / 1e18`.
/// The initial multiplication `int256(value1) * int256(value2)` may revert
/// on `int256` overflow. The final `int256` result of the full expression
Expand All @@ -49,8 +58,8 @@ contract ProductApi3ReaderProxyV1 is IProductApi3ReaderProxyV1 {
override
returns (int224 value, uint32 timestamp)
{
(int224 value1, ) = IApi3ReaderProxy(proxy1).read();
(int224 value2, ) = IApi3ReaderProxy(proxy2).read();
(int224 value1, ) = IApi3ReaderProxyV1(proxy1).read();
(int224 value2, ) = IApi3ReaderProxyV1(proxy2).read();

value = int224((int256(value1) * int256(value2)) / 1e18);
timestamp = uint32(block.timestamp);
Expand Down
22 changes: 13 additions & 9 deletions contracts/adapters/ScaledApi3FeedProxyV1.sol
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

import "@api3/contracts/interfaces/IApi3ReaderProxy.sol";
import "@api3/contracts/api3-server-v1/proxies/interfaces/IApi3ReaderProxyV1.sol";
import "./interfaces/IScaledApi3FeedProxyV1.sol";

/// @title An immutable Chainlink AggregatorV2V3Interface feed contract that
/// scales the value of an IApi3ReaderProxy data feed to a target number of
/// scales the value of an IApi3ReaderProxyV1 data feed to a target number of
/// decimals
/// @dev This contract reads an `int224` value (assumed to be 18 decimals)
/// from the underlying `IApi3ReaderProxy` and scales it to `targetDecimals`.
/// from the underlying `IApi3ReaderProxyV1` and scales it to `targetDecimals`.
/// The scaling arithmetic uses `int256` for intermediate results, allowing the
/// scaled value to exceed `int224` limits if upscaling significantly; it will
/// revert on `int256` overflow.
/// When downscaling, integer division (`proxyValue / scalingFactor`) is used,
/// which truncates and may lead to precision loss. Integrators must carefully
/// consider this potential precision loss for their specific use case.
contract ScaledApi3FeedProxyV1 is IScaledApi3FeedProxyV1 {
/// @notice IApi3ReaderProxy contract address
/// @notice IApi3ReaderProxyV1 contract address
address public immutable override proxy;

/// @notice dApp ID of the proxy
uint256 public immutable override dappId;

/// @dev Target decimals for the scaled value.
uint8 private immutable targetDecimals;

Expand All @@ -30,8 +33,8 @@ contract ScaledApi3FeedProxyV1 is IScaledApi3FeedProxyV1 {
/// downscaling (divide by `scalingFactor`), to scale to `targetDecimals`.
bool public immutable isUpscaling;

/// @param proxy_ IApi3ReaderProxy contract address
/// @param targetDecimals_ Decimals used to scale the IApi3ReaderProxy value
/// @param proxy_ IApi3ReaderProxyV1 contract address
/// @param targetDecimals_ Decimals to scale the IApi3ReaderProxyV1 value
constructor(address proxy_, uint8 targetDecimals_) {
if (proxy_ == address(0)) {
revert ZeroProxyAddress();
Expand All @@ -43,6 +46,7 @@ contract ScaledApi3FeedProxyV1 is IScaledApi3FeedProxyV1 {
revert NoScalingNeeded();
}
proxy = proxy_;
dappId = IApi3ReaderProxyV1(proxy_).dappId();
targetDecimals = targetDecimals_;
uint8 delta = targetDecimals_ > 18
? targetDecimals_ - 18
Expand Down Expand Up @@ -88,7 +92,7 @@ contract ScaledApi3FeedProxyV1 is IScaledApi3FeedProxyV1 {
revert FunctionIsNotSupported();
}

/// @dev Decimals used to scale the IApi3ReaderProxy value
/// @dev Decimals used to scale the IApi3ReaderProxyV1 value
function decimals() external view override returns (uint8) {
return targetDecimals;
}
Expand Down Expand Up @@ -137,7 +141,7 @@ contract ScaledApi3FeedProxyV1 is IScaledApi3FeedProxyV1 {
updatedAt = startedAt;
}

/// @notice Reads a value from the underlying `IApi3ReaderProxy` and
/// @notice Reads a value from the underlying `IApi3ReaderProxyV1` and
/// scales it to `targetDecimals`.
/// @dev Reads from the underlying proxy and applies scaling to
/// `targetDecimals`. Upscaling uses multiplication; downscaling uses integer
Expand All @@ -146,7 +150,7 @@ contract ScaledApi3FeedProxyV1 is IScaledApi3FeedProxyV1 {
/// @return value The scaled signed fixed-point value with `targetDecimals`.
/// @return timestamp The timestamp from the underlying proxy.
function _read() internal view returns (int256 value, uint32 timestamp) {
(int224 proxyValue, uint32 proxyTimestamp) = IApi3ReaderProxy(proxy)
(int224 proxyValue, uint32 proxyTimestamp) = IApi3ReaderProxyV1(proxy)
.read();

value = isUpscaling
Expand Down
2 changes: 2 additions & 0 deletions contracts/adapters/interfaces/IScaledApi3FeedProxyV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ interface IScaledApi3FeedProxyV1 is AggregatorV2V3Interface {
function scalingFactor() external view returns (int256);

function isUpscaling() external view returns (bool);

function dappId() external view returns (uint256);
}
2 changes: 2 additions & 0 deletions contracts/interfaces/IInverseApi3ReaderProxyV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ interface IInverseApi3ReaderProxyV1 is
error FunctionIsNotSupported();

function proxy() external view returns (address proxy);

function dappId() external view returns (uint256);
}
2 changes: 2 additions & 0 deletions contracts/interfaces/INormalizedApi3ReaderProxyV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ interface INormalizedApi3ReaderProxyV1 is

function feed() external view returns (address feed);

function dappId() external view returns (uint256);

function scalingFactor() external view returns (int256);

function isUpscaling() external view returns (bool);
Expand Down
2 changes: 2 additions & 0 deletions contracts/interfaces/IPriceCappedApi3ReaderProxyV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ interface IPriceCappedApi3ReaderProxyV1 is

function proxy() external view returns (address proxy);

function dappId() external view returns (uint256);

function lowerBound() external view returns (int224 lowerBound);

function upperBound() external view returns (int224 upperBound);
Expand Down
4 changes: 4 additions & 0 deletions contracts/interfaces/IProductApi3ReaderProxyV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ interface IProductApi3ReaderProxyV1 is

error SameProxyAddress();

error DappIdMismatch();

error ZeroDenominator();

error FunctionIsNotSupported();

function proxy1() external view returns (address proxy1);

function proxy2() external view returns (address proxy2);

function dappId() external view returns (uint256);
}
4 changes: 0 additions & 4 deletions contracts/test/AccessControlRegistry.sol

This file was deleted.

4 changes: 0 additions & 4 deletions contracts/test/Api3ReaderProxyV1.sol

This file was deleted.

4 changes: 0 additions & 4 deletions contracts/test/Api3ServerV1.sol

This file was deleted.

4 changes: 0 additions & 4 deletions contracts/test/Api3ServerV1OevExtension.sol

This file was deleted.

Loading