diff --git a/README.md b/README.md index f6c09b6..511b161 100644 --- a/README.md +++ b/README.md @@ -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`**: @@ -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. @@ -229,8 +230,9 @@ 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. @@ -238,7 +240,7 @@ To derive the desired uStETH/USD feed and make it compatible with the Api3 ecosy - 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. diff --git a/contracts/InverseApi3ReaderProxyV1.sol b/contracts/InverseApi3ReaderProxyV1.sol index 3d141bc..59a7e51 100644 --- a/contracts/InverseApi3ReaderProxyV1.sol +++ b/contracts/InverseApi3ReaderProxyV1.sol @@ -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`. @@ -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) { diff --git a/contracts/NormalizedApi3ReaderProxyV1.sol b/contracts/NormalizedApi3ReaderProxyV1.sol index 2ed00a4..375b374 100644 --- a/contracts/NormalizedApi3ReaderProxyV1.sol +++ b/contracts/NormalizedApi3ReaderProxyV1.sol @@ -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; @@ -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(); } @@ -36,6 +40,7 @@ contract NormalizedApi3ReaderProxyV1 is INormalizedApi3ReaderProxyV1 { revert NoNormalizationNeeded(); } feed = feed_; + dappId = dappId_; uint8 delta = feedDecimals_ > 18 ? feedDecimals_ - 18 : 18 - feedDecimals_; diff --git a/contracts/PriceCappedApi3ReaderProxyV1.sol b/contracts/PriceCappedApi3ReaderProxyV1.sol index 2e4d939..964e087 100644 --- a/contracts/PriceCappedApi3ReaderProxyV1.sol +++ b/contracts/PriceCappedApi3ReaderProxyV1.sol @@ -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"; /** @@ -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_) { @@ -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. @@ -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; @@ -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; } diff --git a/contracts/ProductApi3ReaderProxyV1.sol b/contracts/ProductApi3ReaderProxyV1.sol index 762f09f..28169e6 100644 --- a/contracts/ProductApi3ReaderProxyV1.sol +++ b/contracts/ProductApi3ReaderProxyV1.sol @@ -1,25 +1,28 @@ // 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(); @@ -27,12 +30,18 @@ contract ProductApi3ReaderProxyV1 is IProductApi3ReaderProxyV1 { 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 @@ -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); diff --git a/contracts/adapters/ScaledApi3FeedProxyV1.sol b/contracts/adapters/ScaledApi3FeedProxyV1.sol index 9d94b56..8a0013c 100644 --- a/contracts/adapters/ScaledApi3FeedProxyV1.sol +++ b/contracts/adapters/ScaledApi3FeedProxyV1.sol @@ -1,14 +1,14 @@ // 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. @@ -16,9 +16,12 @@ import "./interfaces/IScaledApi3FeedProxyV1.sol"; /// 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; @@ -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(); @@ -43,6 +46,7 @@ contract ScaledApi3FeedProxyV1 is IScaledApi3FeedProxyV1 { revert NoScalingNeeded(); } proxy = proxy_; + dappId = IApi3ReaderProxyV1(proxy_).dappId(); targetDecimals = targetDecimals_; uint8 delta = targetDecimals_ > 18 ? targetDecimals_ - 18 @@ -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; } @@ -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 @@ -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 diff --git a/contracts/adapters/interfaces/IScaledApi3FeedProxyV1.sol b/contracts/adapters/interfaces/IScaledApi3FeedProxyV1.sol index 2ba6b2c..5d6f2a6 100644 --- a/contracts/adapters/interfaces/IScaledApi3FeedProxyV1.sol +++ b/contracts/adapters/interfaces/IScaledApi3FeedProxyV1.sol @@ -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); } diff --git a/contracts/interfaces/IInverseApi3ReaderProxyV1.sol b/contracts/interfaces/IInverseApi3ReaderProxyV1.sol index cf2cf36..ffd74a2 100644 --- a/contracts/interfaces/IInverseApi3ReaderProxyV1.sol +++ b/contracts/interfaces/IInverseApi3ReaderProxyV1.sol @@ -15,4 +15,6 @@ interface IInverseApi3ReaderProxyV1 is error FunctionIsNotSupported(); function proxy() external view returns (address proxy); + + function dappId() external view returns (uint256); } diff --git a/contracts/interfaces/INormalizedApi3ReaderProxyV1.sol b/contracts/interfaces/INormalizedApi3ReaderProxyV1.sol index 8350633..1c5f8a9 100644 --- a/contracts/interfaces/INormalizedApi3ReaderProxyV1.sol +++ b/contracts/interfaces/INormalizedApi3ReaderProxyV1.sol @@ -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); diff --git a/contracts/interfaces/IPriceCappedApi3ReaderProxyV1.sol b/contracts/interfaces/IPriceCappedApi3ReaderProxyV1.sol index 7af5f58..a6758b6 100644 --- a/contracts/interfaces/IPriceCappedApi3ReaderProxyV1.sol +++ b/contracts/interfaces/IPriceCappedApi3ReaderProxyV1.sol @@ -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); diff --git a/contracts/interfaces/IProductApi3ReaderProxyV1.sol b/contracts/interfaces/IProductApi3ReaderProxyV1.sol index 3cab97f..ee22f4a 100644 --- a/contracts/interfaces/IProductApi3ReaderProxyV1.sol +++ b/contracts/interfaces/IProductApi3ReaderProxyV1.sol @@ -12,6 +12,8 @@ interface IProductApi3ReaderProxyV1 is error SameProxyAddress(); + error DappIdMismatch(); + error ZeroDenominator(); error FunctionIsNotSupported(); @@ -19,4 +21,6 @@ interface IProductApi3ReaderProxyV1 is function proxy1() external view returns (address proxy1); function proxy2() external view returns (address proxy2); + + function dappId() external view returns (uint256); } diff --git a/contracts/test/AccessControlRegistry.sol b/contracts/test/AccessControlRegistry.sol deleted file mode 100644 index 3300cb9..0000000 --- a/contracts/test/AccessControlRegistry.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import "@api3/contracts/access/AccessControlRegistry.sol"; diff --git a/contracts/test/Api3ReaderProxyV1.sol b/contracts/test/Api3ReaderProxyV1.sol deleted file mode 100644 index 048486d..0000000 --- a/contracts/test/Api3ReaderProxyV1.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.27; - -import "@api3/contracts/api3-server-v1/proxies/Api3ReaderProxyV1.sol"; diff --git a/contracts/test/Api3ServerV1.sol b/contracts/test/Api3ServerV1.sol deleted file mode 100644 index cbb862d..0000000 --- a/contracts/test/Api3ServerV1.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import "@api3/contracts/api3-server-v1/Api3ServerV1.sol"; diff --git a/contracts/test/Api3ServerV1OevExtension.sol b/contracts/test/Api3ServerV1OevExtension.sol deleted file mode 100644 index 2b3846d..0000000 --- a/contracts/test/Api3ServerV1OevExtension.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import "@api3/contracts/api3-server-v1/Api3ServerV1OevExtension.sol"; diff --git a/contracts/test/MockApi3ReaderProxyV1.sol b/contracts/test/MockApi3ReaderProxyV1.sol new file mode 100644 index 0000000..377ec76 --- /dev/null +++ b/contracts/test/MockApi3ReaderProxyV1.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import { + IApi3ReaderProxyV1 +} from "@api3/contracts/api3-server-v1/proxies/interfaces/IApi3ReaderProxyV1.sol"; + +/// @title A mock contract for IApi3ReaderProxyV1 +/// @dev This mock implements the minimal functions required for testing the +/// data-feed-proxy-combinators contracts. Other functions will revert. +contract MockApi3ReaderProxyV1 is IApi3ReaderProxyV1 { + /// @notice The dApp ID of the mock proxy. + uint256 public immutable override dappId; + /// @notice The mock value to be returned by read(). + int224 private _value; + /// @notice The mock timestamp to be returned by read(). + uint32 private _timestamp; + + constructor(uint256 dappId_, int224 value_, uint32 timestamp_) { + dappId = dappId_; + _value = value_; + _timestamp = timestamp_; + } + + function read() public view override returns (int224, uint32) { + return (_value, _timestamp); + } + + function update(int224 value, uint32 timestamp) external { + _value = value; + _timestamp = timestamp; + } + + // --- Stubbed functions from IApi3ReaderProxyV1 that are not used by combinators --- + + function initialize(address) external pure override { + revert("Mock: Not implemented"); + } + + function api3ServerV1() external pure override returns (address) { + revert("Mock: Not implemented"); + } + + function api3ServerV1OevExtension() + external + pure + override + returns (address) + { + revert("Mock: Not implemented"); + } + + function dapiName() external pure override returns (bytes32) { + revert("Mock: Not implemented"); + } +} diff --git a/deploy/001_deploy_InverseApi3ReaderProxyV1.ts b/deploy/001_deploy_InverseApi3ReaderProxyV1.ts index fa169f4..195ee48 100644 --- a/deploy/001_deploy_InverseApi3ReaderProxyV1.ts +++ b/deploy/001_deploy_InverseApi3ReaderProxyV1.ts @@ -1,9 +1,24 @@ import type { HardhatRuntimeEnvironment } from 'hardhat/types'; +import type { DeploymentsExtension } from 'hardhat-deploy/types'; import { getDeploymentName } from '../src'; +import * as testUtils from '../test/test-utils'; export const CONTRACT_NAME = 'InverseApi3ReaderProxyV1'; +const deployMockApi3ReaderProxyV1 = async (deployments: DeploymentsExtension, deployerAddress: string) => { + const { address } = await deployments.deploy('MockApi3ReaderProxyV1', { + from: deployerAddress, + args: [ + testUtils.generateRandomBytes32(), // A mock dappId + '2000000000000000000000', // A mock value (2000e18) + Math.floor(Date.now() / 1000), // A mock timestamp + ], + log: true, + }); + return address; +}; + module.exports = async (hre: HardhatRuntimeEnvironment) => { const { getUnnamedAccounts, deployments, ethers, network, run } = hre; const { deploy, log } = deployments; @@ -14,7 +29,11 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => { } log(`Deployer address: ${deployerAddress}`); - const proxyAddress = process.env.PROXY; + const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost'; + + const proxyAddress = isLocalNetwork + ? await deployMockApi3ReaderProxyV1(deployments, deployerAddress) + : process.env.PROXY; if (!proxyAddress) { throw new Error('PROXY environment variable not set. Please provide the address of the proxy contract.'); } @@ -23,7 +42,7 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => { } log(`Proxy address: ${proxyAddress}`); - const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost'; + // TODO: check that proxyAddress returns a dappId const confirmations = isLocalNetwork ? 1 : 5; log(`Deployment confirmations: ${confirmations}`); diff --git a/deploy/002_deploy_NormalizedApi3ReaderProxyV1.ts b/deploy/002_deploy_NormalizedApi3ReaderProxyV1.ts index 5f97e81..fe2504e 100644 --- a/deploy/002_deploy_NormalizedApi3ReaderProxyV1.ts +++ b/deploy/002_deploy_NormalizedApi3ReaderProxyV1.ts @@ -2,18 +2,21 @@ import type { HardhatRuntimeEnvironment } from 'hardhat/types'; import type { DeploymentsExtension } from 'hardhat-deploy/types'; import { getDeploymentName } from '../src'; +import * as testUtils from '../test/test-utils'; export const CONTRACT_NAME = 'NormalizedApi3ReaderProxyV1'; -const deployTestFeed = async (deployments: DeploymentsExtension, deployerAddress: string) => { - const { address: scaledApi3FeedProxyV1Address } = await deployments.get('ScaledApi3FeedProxyV1').catch(async () => { - return deployments.deploy('ScaledApi3FeedProxyV1', { - from: deployerAddress, - args: ['0x5b0cf2b36a65a6BB085D501B971e4c102B9Cd473', 8], - log: true, - }); +const deployMockAggregatorV2V3 = async (deployments: DeploymentsExtension, deployerAddress: string) => { + const { address } = await deployments.deploy('MockAggregatorV2V3', { + from: deployerAddress, + args: [ + 8, // A mock decimals + '25000000', // A mock value (0.25e8) + Math.floor(Date.now() / 1000), // A mock timestamp + ], + log: true, }); - return scaledApi3FeedProxyV1Address; + return address; }; module.exports = async (hre: HardhatRuntimeEnvironment) => { @@ -26,8 +29,9 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => { } log(`Deployer address: ${deployerAddress}`); - const feedAddress = - network.name === 'hardhat' ? await deployTestFeed(deployments, deployerAddress) : process.env.FEED; + const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost'; + + const feedAddress = isLocalNetwork ? await deployMockAggregatorV2V3(deployments, deployerAddress) : process.env.FEED; if (!feedAddress) { throw new Error( 'FEED environment variable not set. Please provide the address of the AggregatorV2V3Interface contract.' @@ -38,13 +42,20 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => { } log(`Feed address: ${feedAddress}`); - const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost'; + const dappId = isLocalNetwork ? testUtils.generateRandomBytes32() : process.env.DAPP_ID; + if (!dappId) { + throw new Error('DAPP_ID environment variable not set. Please provide the dApp ID.'); + } + if (!ethers.isHexString(dappId, 32)) { + throw new Error(`Invalid dApp ID provided: ${dappId}`); + } + log(`dApp ID: ${dappId}`); const confirmations = isLocalNetwork ? 1 : 5; log(`Deployment confirmations: ${confirmations}`); - const constructorArgs = [feedAddress]; - const constructorArgTypes = ['address']; + const constructorArgs = [feedAddress, dappId]; + const constructorArgTypes = ['address', 'uint256']; const deploymentName = getDeploymentName(CONTRACT_NAME, constructorArgTypes, constructorArgs); log(`Generated deterministic deployment name for this instance: ${deploymentName}`); diff --git a/deploy/003_deploy_ProductApi3ReaderProxyV1.ts b/deploy/003_deploy_ProductApi3ReaderProxyV1.ts index 189afa1..f5092b2 100644 --- a/deploy/003_deploy_ProductApi3ReaderProxyV1.ts +++ b/deploy/003_deploy_ProductApi3ReaderProxyV1.ts @@ -1,9 +1,28 @@ import type { HardhatRuntimeEnvironment } from 'hardhat/types'; +import type { DeploymentsExtension } from 'hardhat-deploy/types'; import { getDeploymentName } from '../src'; +import * as testUtils from '../test/test-utils'; export const CONTRACT_NAME = 'ProductApi3ReaderProxyV1'; +const deployMockApi3ReaderProxyV1 = async ( + deployments: DeploymentsExtension, + deployerAddress: string, + dappId: string +) => { + const { address } = await deployments.deploy('MockApi3ReaderProxyV1', { + from: deployerAddress, + args: [ + dappId, + '2000000000000000000000', // A mock value (2000e18) + Math.floor(Date.now() / 1000), // A mock timestamp + ], + log: true, + }); + return address; +}; + module.exports = async (hre: HardhatRuntimeEnvironment) => { const { getUnnamedAccounts, deployments, ethers, network, run } = hre; const { deploy, log } = deployments; @@ -14,7 +33,12 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => { } log(`Deployer address: ${deployerAddress}`); - const proxy1Address = process.env.PROXY1; + const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost'; + + const dappId = testUtils.generateRandomBytes32(); + const proxy1Address = isLocalNetwork + ? await deployMockApi3ReaderProxyV1(deployments, deployerAddress, dappId) + : process.env.PROXY1; if (!proxy1Address) { throw new Error('PROXY1 environment variable not set. Please provide the address of the first proxy contract.'); } @@ -23,7 +47,14 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => { } log(`Proxy 1 address: ${proxy1Address}`); - const proxy2Address = process.env.PROXY2; + // Sleep for 1 sec when deploying to local network in order to generate a different proxy address + if (isLocalNetwork) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + const proxy2Address = isLocalNetwork + ? await deployMockApi3ReaderProxyV1(deployments, deployerAddress, dappId) + : process.env.PROXY2; if (!proxy2Address) { throw new Error('PROXY2 environment variable not set. Please provide the address of the second proxy contract.'); } @@ -32,8 +63,6 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => { } log(`Proxy 2 address: ${proxy2Address}`); - const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost'; - const confirmations = isLocalNetwork ? 1 : 5; log(`Deployment confirmations: ${confirmations}`); diff --git a/deploy/004_deploy_ScaledApi3FeedProxyV1.ts b/deploy/004_deploy_ScaledApi3FeedProxyV1.ts index da21aee..82572fc 100644 --- a/deploy/004_deploy_ScaledApi3FeedProxyV1.ts +++ b/deploy/004_deploy_ScaledApi3FeedProxyV1.ts @@ -2,20 +2,21 @@ import type { HardhatRuntimeEnvironment } from 'hardhat/types'; import type { DeploymentsExtension } from 'hardhat-deploy/types'; import { getDeploymentName } from '../src'; +import * as testUtils from '../test/test-utils'; export const CONTRACT_NAME = 'ScaledApi3FeedProxyV1'; -const deployTestProxy = async (deployments: DeploymentsExtension, deployerAddress: string) => { - const { address: inverseApi3ReaderProxyV1Address } = await deployments - .get('InverseApi3ReaderProxyV1') - .catch(async () => { - return deployments.deploy('InverseApi3ReaderProxyV1', { - from: deployerAddress, - args: ['0x5b0cf2b36a65a6BB085D501B971e4c102B9Cd473'], - log: true, - }); - }); - return inverseApi3ReaderProxyV1Address; +const deployMockApi3ReaderProxyV1 = async (deployments: DeploymentsExtension, deployerAddress: string) => { + const { address } = await deployments.deploy('MockApi3ReaderProxyV1', { + from: deployerAddress, + args: [ + testUtils.generateRandomBytes32(), // A mock dappId + '2000000000000000000000', // A mock value (2000e18) + Math.floor(Date.now() / 1000), // A mock timestamp + ], + log: true, + }); + return address; }; module.exports = async (hre: HardhatRuntimeEnvironment) => { @@ -34,8 +35,11 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => { const decimals = Number.parseInt(process.env.DECIMALS, 10); log(`Decimals: ${decimals}`); - const proxyAddress = - network.name === 'hardhat' ? await deployTestProxy(deployments, deployerAddress) : process.env.PROXY; + const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost'; + + const proxyAddress = isLocalNetwork + ? await deployMockApi3ReaderProxyV1(deployments, deployerAddress) + : process.env.PROXY; if (!proxyAddress) { throw new Error('PROXY environment variable not set. Please provide the address of the Api3ReaderProxy contract.'); } @@ -44,8 +48,6 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => { } log(`Proxy address: ${proxyAddress}`); - const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost'; - const confirmations = isLocalNetwork ? 1 : 5; log(`Deployment confirmations: ${confirmations}`); diff --git a/deploy/005_deploy_PriceCappedApi3ReaderProxyV1.ts b/deploy/005_deploy_PriceCappedApi3ReaderProxyV1.ts index 7019adf..b23987f 100644 --- a/deploy/005_deploy_PriceCappedApi3ReaderProxyV1.ts +++ b/deploy/005_deploy_PriceCappedApi3ReaderProxyV1.ts @@ -1,9 +1,24 @@ import type { HardhatRuntimeEnvironment } from 'hardhat/types'; +import type { DeploymentsExtension } from 'hardhat-deploy/types'; import { getDeploymentName } from '../src'; +import * as testUtils from '../test/test-utils'; export const CONTRACT_NAME = 'PriceCappedApi3ReaderProxyV1'; +const deployMockApi3ReaderProxyV1 = async (deployments: DeploymentsExtension, deployerAddress: string) => { + const { address } = await deployments.deploy('MockApi3ReaderProxyV1', { + from: deployerAddress, + args: [ + testUtils.generateRandomBytes32(), // A mock dappId + '2000000000000000000000', // A mock value (2000e18) + Math.floor(Date.now() / 1000), // A mock timestamp + ], + log: true, + }); + return address; +}; + module.exports = async (hre: HardhatRuntimeEnvironment) => { const { getUnnamedAccounts, deployments, ethers, network, run } = hre; const { deploy, log } = deployments; @@ -14,7 +29,11 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => { } log(`Deployer address: ${deployerAddress}`); - const proxyAddress = process.env.PROXY; + const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost'; + + const proxyAddress = isLocalNetwork + ? await deployMockApi3ReaderProxyV1(deployments, deployerAddress) + : process.env.PROXY; if (!proxyAddress) { throw new Error('PROXY environment variable not set. Please provide the address of the proxy contract.'); } @@ -29,8 +48,6 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => { const upperBound = process.env.UPPER_BOUND ? BigInt(process.env.UPPER_BOUND) : BigInt(2) ** BigInt(223) - BigInt(1); // Defaults to type(int224).max log(`Using upper bound: ${upperBound.toString()}`); - const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost'; - const confirmations = isLocalNetwork ? 1 : 5; log(`Deployment confirmations: ${confirmations}`); diff --git a/test/InverseApi3ReaderProxyV1.sol.ts b/test/InverseApi3ReaderProxyV1.sol.ts index 8d7059f..a10bfc3 100644 --- a/test/InverseApi3ReaderProxyV1.sol.ts +++ b/test/InverseApi3ReaderProxyV1.sol.ts @@ -3,14 +3,17 @@ import * as helpers from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; +import * as testUtils from './test-utils'; + describe('InverseApi3ReaderProxyV1', function () { async function deploy() { - const roleNames = ['deployer', 'manager', 'airnode', 'auctioneer', 'searcher']; + const roleNames = ['deployer']; const accounts = await ethers.getSigners(); const roles: Record = roleNames.reduce((acc, roleName, index) => { return { ...acc, [roleName]: accounts[index] }; }, {}); + const dappId = testUtils.generateRandomBytes32(); const decimals = 20; const answer = ethers.parseUnits('1824.97', decimals); const timestamp = await helpers.time.latest(); @@ -22,13 +25,14 @@ describe('InverseApi3ReaderProxyV1', function () { 'NormalizedApi3ReaderProxyV1', roles.deployer ); - const proxy = await normalizedApi3ReaderProxyV1Factory.deploy(await feed.getAddress()); + const proxy = await normalizedApi3ReaderProxyV1Factory.deploy(await feed.getAddress(), dappId); const inverseApi3ReaderProxyV1Factory = await ethers.getContractFactory('InverseApi3ReaderProxyV1', roles.deployer); const inverseApi3ReaderProxyV1 = await inverseApi3ReaderProxyV1Factory.deploy(await proxy.getAddress()); return { proxy, + dappId, inverseApi3ReaderProxyV1, roles, }; @@ -39,6 +43,7 @@ describe('InverseApi3ReaderProxyV1', function () { it('constructs', async function () { const { proxy, inverseApi3ReaderProxyV1 } = await helpers.loadFixture(deploy); expect(await inverseApi3ReaderProxyV1.proxy()).to.equal(await proxy.getAddress()); + expect(await inverseApi3ReaderProxyV1.dappId()).to.equal(await proxy.dappId()); }); }); context('proxy is zero address', function () { diff --git a/test/NormalizedApi3ReaderProxyV1.sol.ts b/test/NormalizedApi3ReaderProxyV1.sol.ts index be9f72d..619c207 100644 --- a/test/NormalizedApi3ReaderProxyV1.sol.ts +++ b/test/NormalizedApi3ReaderProxyV1.sol.ts @@ -3,14 +3,17 @@ import * as helpers from '@nomicfoundation/hardhat-network-helpers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; +import * as testUtils from './test-utils'; + describe('NormalizedApi3ReaderProxyV1', function () { async function deploy() { - const roleNames = ['deployer', 'manager', 'airnode', 'auctioneer', 'searcher']; + const roleNames = ['deployer']; const accounts = await ethers.getSigners(); const roles: Record = roleNames.reduce((acc, roleName, index) => { return { ...acc, [roleName]: accounts[index] }; }, {}); + const dappId = testUtils.generateRandomBytes32(); const decimals = 8; const answer = ethers.parseUnits('0.25', decimals); const timestamp = await helpers.time.latest(); @@ -22,10 +25,14 @@ describe('NormalizedApi3ReaderProxyV1', function () { 'NormalizedApi3ReaderProxyV1', roles.deployer ); - const normalizedApi3ReaderProxyV1 = await normalizedApi3ReaderProxyV1Factory.deploy(await feed.getAddress()); + const normalizedApi3ReaderProxyV1 = await normalizedApi3ReaderProxyV1Factory.deploy( + await feed.getAddress(), + dappId + ); return { feed, + dappId, mockAggregatorV2V3Factory, normalizedApi3ReaderProxyV1, roles, @@ -44,21 +51,22 @@ describe('NormalizedApi3ReaderProxyV1', function () { context('feed is not zero address', function () { context('feed does not have 18 decimals', function () { it('constructs', async function () { - const { feed, normalizedApi3ReaderProxyV1 } = await helpers.loadFixture(deploy); + const { feed, dappId, normalizedApi3ReaderProxyV1 } = await helpers.loadFixture(deploy); expect(await normalizedApi3ReaderProxyV1.feed()).to.equal(await feed.getAddress()); + expect(await normalizedApi3ReaderProxyV1.dappId()).to.equal(dappId); expect(await normalizedApi3ReaderProxyV1.isUpscaling()).to.equal(true); // 8 < 18 is true expect(await normalizedApi3ReaderProxyV1.scalingFactor()).to.equal(10_000_000_000n); // 10**(18-8) }); }); context('feed has 18 decimals', function () { it('reverts', async function () { - const { roles, mockAggregatorV2V3Factory } = await helpers.loadFixture(deploy); + const { dappId, roles, mockAggregatorV2V3Factory } = await helpers.loadFixture(deploy); const feed = await mockAggregatorV2V3Factory.deploy(18, ethers.parseEther('1'), await helpers.time.latest()); const normalizedApi3ReaderProxyV1Factory = await ethers.getContractFactory( 'NormalizedApi3ReaderProxyV1', roles.deployer ); - await expect(normalizedApi3ReaderProxyV1Factory.deploy(feed)) + await expect(normalizedApi3ReaderProxyV1Factory.deploy(feed, dappId)) .to.be.revertedWithCustomError(normalizedApi3ReaderProxyV1Factory, 'NoNormalizationNeeded') .withArgs(); }); @@ -66,12 +74,12 @@ describe('NormalizedApi3ReaderProxyV1', function () { }); context('feed is zero address', function () { it('reverts', async function () { - const { roles } = await helpers.loadFixture(deploy); + const { dappId, roles } = await helpers.loadFixture(deploy); const normalizedApi3ReaderProxyV1Factory = await ethers.getContractFactory( 'NormalizedApi3ReaderProxyV1', roles.deployer ); - await expect(normalizedApi3ReaderProxyV1Factory.deploy(ethers.ZeroAddress)) + await expect(normalizedApi3ReaderProxyV1Factory.deploy(ethers.ZeroAddress, dappId)) .to.be.revertedWithCustomError(normalizedApi3ReaderProxyV1Factory, 'ZeroProxyAddress') .withArgs(); }); @@ -80,7 +88,8 @@ describe('NormalizedApi3ReaderProxyV1', function () { describe('read', function () { it('reads the normalized to 18 decimals rate', async function () { - const { feed, mockAggregatorV2V3Factory, normalizedApi3ReaderProxyV1, roles } = await helpers.loadFixture(deploy); + const { feed, dappId, mockAggregatorV2V3Factory, normalizedApi3ReaderProxyV1, roles } = + await helpers.loadFixture(deploy); const decimals = await feed.decimals(); const [, answer, , updatedAt] = await feed.latestRoundData(); @@ -97,7 +106,8 @@ describe('NormalizedApi3ReaderProxyV1', function () { roles.deployer ); const newNormalizedApi3ReaderProxyV1 = await normalizedApi3ReaderProxyV1Factory.deploy( - await newFeed.getAddress() + await newFeed.getAddress(), + dappId ); const newDataFeed = await newNormalizedApi3ReaderProxyV1.read(); expect(newDataFeed.value).to.equal(normalize(answer, 8)); diff --git a/test/PriceCappedApi3ReaderProxyV1.sol.ts b/test/PriceCappedApi3ReaderProxyV1.sol.ts index 7f0e8eb..faf32cc 100644 --- a/test/PriceCappedApi3ReaderProxyV1.sol.ts +++ b/test/PriceCappedApi3ReaderProxyV1.sol.ts @@ -7,60 +7,17 @@ import * as testUtils from './test-utils'; describe('PriceCappedApi3ReaderProxyV1', function () { async function deploy() { - const roleNames = ['deployer', 'manager', 'airnode', 'auctioneer', 'searcher']; + const roleNames = ['deployer']; const accounts = await ethers.getSigners(); const roles: Record = roleNames.reduce((acc, roleName, index) => { return { ...acc, [roleName]: accounts[index] }; }, {}); - const accessControlRegistryFactory = await ethers.getContractFactory('AccessControlRegistry', roles.deployer); - const accessControlRegistry = await accessControlRegistryFactory.deploy(); - - const api3ServerV1Factory = await ethers.getContractFactory('Api3ServerV1', roles.deployer); - const api3ServerV1 = await api3ServerV1Factory.deploy( - accessControlRegistry.getAddress(), - 'Api3ServerV1 admin', - roles.manager!.address - ); - - const api3ServerV1OevExtensionAdminRoleDescription = 'Api3ServerV1OevExtension admin'; - const api3ServerV1OevExtensionFactory = await ethers.getContractFactory('Api3ServerV1OevExtension', roles.deployer); - const api3ServerV1OevExtension = await api3ServerV1OevExtensionFactory.deploy( - accessControlRegistry.getAddress(), - api3ServerV1OevExtensionAdminRoleDescription, - roles.manager!.address, - api3ServerV1.getAddress() - ); - - const dapiName = ethers.encodeBytes32String('DAI/USD'); - const dappId = 1; - - const api3ReaderProxyV1Factory = await ethers.getContractFactory('Api3ReaderProxyV1', roles.deployer); - const api3ReaderProxyV1 = await api3ReaderProxyV1Factory.deploy( - api3ServerV1OevExtension.getAddress(), - dapiName, - dappId - ); - - const endpointId = testUtils.generateRandomBytes32(); - const templateParameters = testUtils.generateRandomBytes(); - const templateId = ethers.keccak256(ethers.solidityPacked(['bytes32', 'bytes'], [endpointId, templateParameters])); - const beaconId = ethers.keccak256( - ethers.solidityPacked(['address', 'bytes32'], [roles.airnode!.address, templateId]) - ); - await api3ServerV1.connect(roles.manager).setDapiName(dapiName, beaconId); - - const baseBeaconValue = ethers.parseEther('1.0001'); - const baseBeaconTimestamp = await helpers.time.latest(); - const data = ethers.AbiCoder.defaultAbiCoder().encode(['int256'], [baseBeaconValue]); - const signature = await testUtils.signData(roles.airnode! as any, templateId, baseBeaconTimestamp, data); - await api3ServerV1.updateBeaconWithSignedData( - roles.airnode!.address, - templateId, - baseBeaconTimestamp, - data, - signature - ); + const dappId = testUtils.generateRandomBytes32(); + const beaconValue = ethers.parseEther('1.0001'); + const beaconTimestamp = await helpers.time.latest(); + const mockApi3ReaderProxyV1Factory = await ethers.getContractFactory('MockApi3ReaderProxyV1', roles.deployer); + const proxy = await mockApi3ReaderProxyV1Factory.deploy(dappId, beaconValue, beaconTimestamp); const lowerBound = ethers.parseEther('0.9995'); const upperBound = ethers.parseEther('1.0005'); @@ -70,18 +27,16 @@ describe('PriceCappedApi3ReaderProxyV1', function () { roles.deployer ); const priceCappedApi3ReaderProxyV1 = await priceCappedApi3ReaderProxyV1Factory.deploy( - await api3ReaderProxyV1.getAddress(), + await proxy.getAddress(), lowerBound, upperBound ); return { - api3ServerV1, - api3ReaderProxyV1, + proxy, priceCappedApi3ReaderProxyV1, lowerBound, upperBound, - templateId, roles, }; } @@ -91,21 +46,21 @@ describe('PriceCappedApi3ReaderProxyV1', function () { context('lowerBound is not negative', function () { context('upperBound is greater or equal to lowerBound', function () { it('constructs', async function () { - const { api3ReaderProxyV1, priceCappedApi3ReaderProxyV1, lowerBound, upperBound } = - await helpers.loadFixture(deploy); - expect(await priceCappedApi3ReaderProxyV1.proxy()).to.equal(await api3ReaderProxyV1.getAddress()); + const { proxy, priceCappedApi3ReaderProxyV1, lowerBound, upperBound } = await helpers.loadFixture(deploy); + expect(await priceCappedApi3ReaderProxyV1.proxy()).to.equal(await proxy.getAddress()); + expect(await priceCappedApi3ReaderProxyV1.dappId()).to.equal(await proxy.dappId()); expect(await priceCappedApi3ReaderProxyV1.lowerBound()).to.equal(lowerBound); expect(await priceCappedApi3ReaderProxyV1.upperBound()).to.equal(upperBound); }); }); context('upperBound is less than lowerBound', function () { it('reverts', async function () { - const { api3ReaderProxyV1, lowerBound, upperBound, roles } = await helpers.loadFixture(deploy); + const { proxy, lowerBound, upperBound, roles } = await helpers.loadFixture(deploy); const priceCappedApi3ReaderProxyV1 = await ethers.getContractFactory( 'PriceCappedApi3ReaderProxyV1', roles.deployer ); - await expect(priceCappedApi3ReaderProxyV1.deploy(api3ReaderProxyV1, upperBound, lowerBound)) + await expect(priceCappedApi3ReaderProxyV1.deploy(proxy, upperBound, lowerBound)) .to.be.revertedWithCustomError(priceCappedApi3ReaderProxyV1, 'UpperBoundMustBeGreaterOrEqualToLowerBound') .withArgs(); }); @@ -113,12 +68,12 @@ describe('PriceCappedApi3ReaderProxyV1', function () { }); context('lowerBound is negative', function () { it('reverts', async function () { - const { api3ReaderProxyV1, upperBound, roles } = await helpers.loadFixture(deploy); + const { proxy, upperBound, roles } = await helpers.loadFixture(deploy); const priceCappedApi3ReaderProxyV1 = await ethers.getContractFactory( 'PriceCappedApi3ReaderProxyV1', roles.deployer ); - await expect(priceCappedApi3ReaderProxyV1.deploy(api3ReaderProxyV1, ethers.parseEther('-0.9995'), upperBound)) + await expect(priceCappedApi3ReaderProxyV1.deploy(proxy, ethers.parseEther('-0.9995'), upperBound)) .to.be.revertedWithCustomError(priceCappedApi3ReaderProxyV1, 'LowerBoundMustBeNonNegative') .withArgs(); }); @@ -140,48 +95,26 @@ describe('PriceCappedApi3ReaderProxyV1', function () { describe('read', function () { it('reads the capped rate', async function () { - const { - api3ServerV1, - api3ReaderProxyV1, - priceCappedApi3ReaderProxyV1, - templateId, - lowerBound, - upperBound, - roles, - } = await helpers.loadFixture(deploy); + const { proxy, priceCappedApi3ReaderProxyV1, lowerBound, upperBound } = await helpers.loadFixture(deploy); const dataFeed = await priceCappedApi3ReaderProxyV1.read(); - const [value, timestamp] = await api3ReaderProxyV1.read(); + const [value, timestamp] = await proxy.read(); expect(dataFeed.value).to.equal(value); expect(dataFeed.timestamp).to.equal(timestamp); - let data = ethers.AbiCoder.defaultAbiCoder().encode(['int256'], [ethers.parseEther('0.9991')]); - let beaconTimestamp = await helpers.time.latest(); - let signature = await testUtils.signData(roles.airnode! as any, templateId, beaconTimestamp, data); - await api3ServerV1.updateBeaconWithSignedData( - roles.airnode!.address, - templateId, - beaconTimestamp, - data, - signature - ); + let newTimestamp = await helpers.time.latest(); + await proxy.update(ethers.parseEther('0.9991'), newTimestamp); + const cappedToLowerBoundDataFeed = await priceCappedApi3ReaderProxyV1.read(); expect(cappedToLowerBoundDataFeed.value).to.equal(lowerBound); - expect(cappedToLowerBoundDataFeed.timestamp).to.equal(beaconTimestamp); + expect(cappedToLowerBoundDataFeed.timestamp).to.equal(newTimestamp); + + newTimestamp = await helpers.time.latest(); + await proxy.update(ethers.parseEther('1.0006'), newTimestamp); - data = ethers.AbiCoder.defaultAbiCoder().encode(['int256'], [ethers.parseEther('1.0006')]); - beaconTimestamp = await helpers.time.latest(); - signature = await testUtils.signData(roles.airnode! as any, templateId, beaconTimestamp, data); - await api3ServerV1.updateBeaconWithSignedData( - roles.airnode!.address, - templateId, - beaconTimestamp, - data, - signature - ); const cappedToUpperBoundDataFeed = await priceCappedApi3ReaderProxyV1.read(); expect(cappedToUpperBoundDataFeed.value).to.equal(upperBound); - expect(cappedToUpperBoundDataFeed.timestamp).to.equal(beaconTimestamp); + expect(cappedToUpperBoundDataFeed.timestamp).to.equal(newTimestamp); }); }); diff --git a/test/ProductApi3ReaderProxyV1.sol.ts b/test/ProductApi3ReaderProxyV1.sol.ts index 8171ce4..c6ace49 100644 --- a/test/ProductApi3ReaderProxyV1.sol.ts +++ b/test/ProductApi3ReaderProxyV1.sol.ts @@ -7,119 +7,38 @@ import * as testUtils from './test-utils'; describe('ProductApi3ReaderProxyV1', function () { async function deploy() { - const roleNames = ['deployer', 'manager', 'airnode', 'auctioneer', 'searcher']; + const roleNames = ['deployer']; const accounts = await ethers.getSigners(); const roles: Record = roleNames.reduce((acc, roleName, index) => { return { ...acc, [roleName]: accounts[index] }; }, {}); - const accessControlRegistryFactory = await ethers.getContractFactory('AccessControlRegistry', roles.deployer); - const accessControlRegistry = await accessControlRegistryFactory.deploy(); - - const api3ServerV1Factory = await ethers.getContractFactory('Api3ServerV1', roles.deployer); - const api3ServerV1 = await api3ServerV1Factory.deploy( - accessControlRegistry.getAddress(), - 'Api3ServerV1 admin', - roles.manager!.address - ); - - const api3ServerV1OevExtensionAdminRoleDescription = 'Api3ServerV1OevExtension admin'; - const api3ServerV1OevExtensionFactory = await ethers.getContractFactory('Api3ServerV1OevExtension', roles.deployer); - const api3ServerV1OevExtension = await api3ServerV1OevExtensionFactory.deploy( - accessControlRegistry.getAddress(), - api3ServerV1OevExtensionAdminRoleDescription, - roles.manager!.address, - api3ServerV1.getAddress() - ); - - const api3ReaderProxyV1Factory = await ethers.getContractFactory('Api3ReaderProxyV1', roles.deployer); - - const dappId = 1; - const dapiNameEthUsd = ethers.encodeBytes32String('ETH/USD'); - const api3ReaderProxyV1EthUsd = await api3ReaderProxyV1Factory.deploy( - api3ServerV1OevExtension.getAddress(), - dapiNameEthUsd, - dappId - ); - const dapiNameSolEth = ethers.encodeBytes32String('SOL/ETH'); - const api3ReaderProxyV1SolEth = await api3ReaderProxyV1Factory.deploy( - api3ServerV1OevExtension.getAddress(), - dapiNameSolEth, - dappId - ); + const dappId = testUtils.generateRandomBytes32(); + const mockApi3ReaderProxyV1Factory = await ethers.getContractFactory('MockApi3ReaderProxyV1', roles.deployer); + const beaconValue1 = ethers.parseEther('1824.97'); + const beaconTimestamp1 = await helpers.time.latest(); + const proxy1 = await mockApi3ReaderProxyV1Factory.deploy(dappId, beaconValue1, beaconTimestamp1); + const beaconValue2 = ethers.parseEther('0.08202'); + const beaconTimestamp2 = await helpers.time.latest(); + const proxy2 = await mockApi3ReaderProxyV1Factory.deploy(dappId, beaconValue2, beaconTimestamp2); const productApi3ReaderProxyV1Factory = await ethers.getContractFactory('ProductApi3ReaderProxyV1', roles.deployer); - const productApi3ReaderProxyV1SolUsd = await productApi3ReaderProxyV1Factory.deploy( - api3ReaderProxyV1EthUsd.getAddress(), - api3ReaderProxyV1SolEth.getAddress() - ); - - const productApi3ReaderProxyV1EthSol = await productApi3ReaderProxyV1Factory.deploy( - api3ReaderProxyV1EthUsd.getAddress(), - productApi3ReaderProxyV1SolUsd.getAddress() + const productApi3ReaderProxyV1 = await productApi3ReaderProxyV1Factory.deploy( + proxy1.getAddress(), + proxy2.getAddress() ); - const endpointIdEthUsd = testUtils.generateRandomBytes32(); - const templateParametersEthUsd = testUtils.generateRandomBytes(); - const templateIdEthUsd = ethers.keccak256( - ethers.solidityPacked(['bytes32', 'bytes'], [endpointIdEthUsd, templateParametersEthUsd]) - ); - const beaconIdEthUsd = ethers.keccak256( - ethers.solidityPacked(['address', 'bytes32'], [roles.airnode!.address, templateIdEthUsd]) - ); - await api3ServerV1.connect(roles.manager).setDapiName(dapiNameEthUsd, beaconIdEthUsd); - - const baseBeaconValueEthUsd = ethers.parseEther('1824.97'); - const baseBeaconTimestampEthUsd = await helpers.time.latest(); - const dataEthUsd = ethers.AbiCoder.defaultAbiCoder().encode(['int256'], [baseBeaconValueEthUsd]); - const signatureEthUsd = await testUtils.signData( - roles.airnode! as any, - templateIdEthUsd, - baseBeaconTimestampEthUsd, - dataEthUsd - ); - await api3ServerV1.updateBeaconWithSignedData( - roles.airnode!.address, - templateIdEthUsd, - baseBeaconTimestampEthUsd, - dataEthUsd, - signatureEthUsd - ); - - const endpointIdSolEth = testUtils.generateRandomBytes32(); - const templateParametersSolEth = testUtils.generateRandomBytes(); - const templateIdSolEth = ethers.keccak256( - ethers.solidityPacked(['bytes32', 'bytes'], [endpointIdSolEth, templateParametersSolEth]) - ); - const beaconIdSolEth = ethers.keccak256( - ethers.solidityPacked(['address', 'bytes32'], [roles.airnode!.address, templateIdSolEth]) - ); - await api3ServerV1.connect(roles.manager).setDapiName(dapiNameSolEth, beaconIdSolEth); - - const baseBeaconValueSolEth = ethers.parseEther('0.08202'); - const baseBeaconTimestampSolEth = await helpers.time.latest(); - const dataSolEth = ethers.AbiCoder.defaultAbiCoder().encode(['int256'], [baseBeaconValueSolEth]); - const signatureSolEth = await testUtils.signData( - roles.airnode! as any, - templateIdSolEth, - baseBeaconTimestampSolEth, - dataSolEth - ); - await api3ServerV1.updateBeaconWithSignedData( - roles.airnode!.address, - templateIdSolEth, - baseBeaconTimestampSolEth, - dataSolEth, - signatureSolEth + const productApi3ReaderProxyV1Compound = await productApi3ReaderProxyV1Factory.deploy( + proxy1.getAddress(), + productApi3ReaderProxyV1.getAddress() ); return { - api3ReaderProxyV1EthUsd, - api3ReaderProxyV1SolEth, - api3ServerV1, - productApi3ReaderProxyV1EthSol, - productApi3ReaderProxyV1SolUsd, + proxy1, + proxy2, + productApi3ReaderProxyV1, + productApi3ReaderProxyV1Compound, roles, }; } @@ -129,33 +48,28 @@ describe('ProductApi3ReaderProxyV1', function () { context('proxy2 is not zero address', function () { context('proxy1 is not the same as proxy2', function () { it('constructs', async function () { - const { - productApi3ReaderProxyV1SolUsd, - productApi3ReaderProxyV1EthSol, - api3ReaderProxyV1EthUsd, - api3ReaderProxyV1SolEth, - } = await helpers.loadFixture(deploy); - expect(await productApi3ReaderProxyV1SolUsd.proxy1()).to.equal(await api3ReaderProxyV1EthUsd.getAddress()); - expect(await productApi3ReaderProxyV1SolUsd.proxy2()).to.equal(await api3ReaderProxyV1SolEth.getAddress()); - expect(await productApi3ReaderProxyV1EthSol.proxy1()).to.equal(await api3ReaderProxyV1EthUsd.getAddress()); - expect(await productApi3ReaderProxyV1EthSol.proxy2()).to.equal( - await productApi3ReaderProxyV1SolUsd.getAddress() + const { proxy1, proxy2, productApi3ReaderProxyV1, productApi3ReaderProxyV1Compound } = + await helpers.loadFixture(deploy); + expect(await productApi3ReaderProxyV1.proxy1()).to.equal(await proxy1.getAddress()); + expect(await productApi3ReaderProxyV1.proxy2()).to.equal(await proxy2.getAddress()); + expect(await productApi3ReaderProxyV1.dappId()).to.equal(await proxy1.dappId()); + expect(await productApi3ReaderProxyV1.dappId()).to.equal(await proxy2.dappId()); + expect(await productApi3ReaderProxyV1Compound.proxy1()).to.equal(await proxy1.getAddress()); + expect(await productApi3ReaderProxyV1Compound.proxy2()).to.equal( + await productApi3ReaderProxyV1.getAddress() ); + expect(await productApi3ReaderProxyV1Compound.dappId()).to.equal(await proxy1.dappId()); + expect(await productApi3ReaderProxyV1Compound.dappId()).to.equal(await productApi3ReaderProxyV1.dappId()); }); }); context('proxy1 is the same as proxy2', function () { it('reverts', async function () { - const { api3ReaderProxyV1EthUsd, roles } = await helpers.loadFixture(deploy); + const { proxy1, roles } = await helpers.loadFixture(deploy); const productApi3ReaderProxyV1Factory = await ethers.getContractFactory( 'ProductApi3ReaderProxyV1', roles.deployer ); - await expect( - productApi3ReaderProxyV1Factory.deploy( - await api3ReaderProxyV1EthUsd.getAddress(), - await api3ReaderProxyV1EthUsd.getAddress() - ) - ) + await expect(productApi3ReaderProxyV1Factory.deploy(await proxy1.getAddress(), await proxy1.getAddress())) .to.be.revertedWithCustomError(productApi3ReaderProxyV1Factory, 'SameProxyAddress') .withArgs(); }); @@ -163,14 +77,12 @@ describe('ProductApi3ReaderProxyV1', function () { }); context('proxy2 is zero address', function () { it('reverts', async function () { - const { api3ReaderProxyV1EthUsd, roles } = await helpers.loadFixture(deploy); + const { proxy1, roles } = await helpers.loadFixture(deploy); const productApi3ReaderProxyV1Factory = await ethers.getContractFactory( 'ProductApi3ReaderProxyV1', roles.deployer ); - await expect( - productApi3ReaderProxyV1Factory.deploy(await api3ReaderProxyV1EthUsd.getAddress(), ethers.ZeroAddress) - ) + await expect(productApi3ReaderProxyV1Factory.deploy(await proxy1.getAddress(), ethers.ZeroAddress)) .to.be.revertedWithCustomError(productApi3ReaderProxyV1Factory, 'ZeroProxyAddress') .withArgs(); }); @@ -178,14 +90,12 @@ describe('ProductApi3ReaderProxyV1', function () { }); context('proxy1 is zero address', function () { it('reverts', async function () { - const { api3ReaderProxyV1SolEth, roles } = await helpers.loadFixture(deploy); + const { proxy1, roles } = await helpers.loadFixture(deploy); const productApi3ReaderProxyV1Factory = await ethers.getContractFactory( 'ProductApi3ReaderProxyV1', roles.deployer ); - await expect( - productApi3ReaderProxyV1Factory.deploy(ethers.ZeroAddress, await api3ReaderProxyV1SolEth.getAddress()) - ) + await expect(productApi3ReaderProxyV1Factory.deploy(ethers.ZeroAddress, await proxy1.getAddress())) .to.be.revertedWithCustomError(productApi3ReaderProxyV1Factory, 'ZeroProxyAddress') .withArgs(); }); @@ -194,11 +104,10 @@ describe('ProductApi3ReaderProxyV1', function () { describe('read', function () { it('reads the product of the proxy rates', async function () { - const { productApi3ReaderProxyV1SolUsd, api3ReaderProxyV1EthUsd, api3ReaderProxyV1SolEth } = - await helpers.loadFixture(deploy); - const [baseBeaconValueEthUsd] = await api3ReaderProxyV1EthUsd.read(); - const [baseBeaconValueSolEth] = await api3ReaderProxyV1SolEth.read(); - const dataFeed = await productApi3ReaderProxyV1SolUsd.read(); + const { proxy1, proxy2, productApi3ReaderProxyV1 } = await helpers.loadFixture(deploy); + const [baseBeaconValueEthUsd] = await proxy1.read(); + const [baseBeaconValueSolEth] = await proxy2.read(); + const dataFeed = await productApi3ReaderProxyV1.read(); expect(dataFeed.value).to.equal((baseBeaconValueEthUsd * baseBeaconValueSolEth) / 10n ** 18n); expect(dataFeed.timestamp).to.equal(await helpers.time.latest()); }); @@ -206,86 +115,85 @@ describe('ProductApi3ReaderProxyV1', function () { describe('latestAnswer', function () { it('returns proxy value', async function () { - const { productApi3ReaderProxyV1SolUsd } = await helpers.loadFixture(deploy); - const [value] = await productApi3ReaderProxyV1SolUsd.read(); - expect(await productApi3ReaderProxyV1SolUsd.latestAnswer()).to.be.equal(value); + const { productApi3ReaderProxyV1 } = await helpers.loadFixture(deploy); + const [value] = await productApi3ReaderProxyV1.read(); + expect(await productApi3ReaderProxyV1.latestAnswer()).to.be.equal(value); }); }); describe('latestTimestamp', function () { it('returns proxy value', async function () { - const { productApi3ReaderProxyV1SolUsd } = await helpers.loadFixture(deploy); - const [, timestamp] = await productApi3ReaderProxyV1SolUsd.read(); - expect(await productApi3ReaderProxyV1SolUsd.latestTimestamp()).to.be.equal(timestamp); + const { productApi3ReaderProxyV1 } = await helpers.loadFixture(deploy); + const [, timestamp] = await productApi3ReaderProxyV1.read(); + expect(await productApi3ReaderProxyV1.latestTimestamp()).to.be.equal(timestamp); }); }); describe('latestRound', function () { it('reverts', async function () { - const { productApi3ReaderProxyV1SolUsd } = await helpers.loadFixture(deploy); - await expect(productApi3ReaderProxyV1SolUsd.latestRound()) - .to.be.revertedWithCustomError(productApi3ReaderProxyV1SolUsd, 'FunctionIsNotSupported') + const { productApi3ReaderProxyV1 } = await helpers.loadFixture(deploy); + await expect(productApi3ReaderProxyV1.latestRound()) + .to.be.revertedWithCustomError(productApi3ReaderProxyV1, 'FunctionIsNotSupported') .withArgs(); }); }); describe('getAnswer', function () { it('reverts', async function () { - const { productApi3ReaderProxyV1SolUsd } = await helpers.loadFixture(deploy); + const { productApi3ReaderProxyV1 } = await helpers.loadFixture(deploy); const blockNumber = await ethers.provider.getBlockNumber(); - await expect(productApi3ReaderProxyV1SolUsd.getAnswer(blockNumber)) - .to.be.revertedWithCustomError(productApi3ReaderProxyV1SolUsd, 'FunctionIsNotSupported') + await expect(productApi3ReaderProxyV1.getAnswer(blockNumber)) + .to.be.revertedWithCustomError(productApi3ReaderProxyV1, 'FunctionIsNotSupported') .withArgs(); }); }); describe('getTimestamp', function () { it('reverts', async function () { - const { productApi3ReaderProxyV1SolUsd } = await helpers.loadFixture(deploy); + const { productApi3ReaderProxyV1 } = await helpers.loadFixture(deploy); const blockNumber = await ethers.provider.getBlockNumber(); - await expect(productApi3ReaderProxyV1SolUsd.getTimestamp(blockNumber)) - .to.be.revertedWithCustomError(productApi3ReaderProxyV1SolUsd, 'FunctionIsNotSupported') + await expect(productApi3ReaderProxyV1.getTimestamp(blockNumber)) + .to.be.revertedWithCustomError(productApi3ReaderProxyV1, 'FunctionIsNotSupported') .withArgs(); }); }); describe('decimals', function () { it('returns 18', async function () { - const { productApi3ReaderProxyV1SolUsd } = await helpers.loadFixture(deploy); - expect(await productApi3ReaderProxyV1SolUsd.decimals()).to.equal(18); + const { productApi3ReaderProxyV1 } = await helpers.loadFixture(deploy); + expect(await productApi3ReaderProxyV1.decimals()).to.equal(18); }); }); describe('description', function () { it('returns empty string', async function () { - const { productApi3ReaderProxyV1SolUsd } = await helpers.loadFixture(deploy); - expect(await productApi3ReaderProxyV1SolUsd.description()).to.equal(''); + const { productApi3ReaderProxyV1 } = await helpers.loadFixture(deploy); + expect(await productApi3ReaderProxyV1.description()).to.equal(''); }); }); describe('version', function () { it('returns 4914', async function () { - const { productApi3ReaderProxyV1SolUsd } = await helpers.loadFixture(deploy); - expect(await productApi3ReaderProxyV1SolUsd.version()).to.equal(4914); + const { productApi3ReaderProxyV1 } = await helpers.loadFixture(deploy); + expect(await productApi3ReaderProxyV1.version()).to.equal(4914); }); }); describe('getRoundData', function () { it('reverts', async function () { - const { productApi3ReaderProxyV1SolUsd } = await helpers.loadFixture(deploy); + const { productApi3ReaderProxyV1 } = await helpers.loadFixture(deploy); const blockNumber = await ethers.provider.getBlockNumber(); - await expect(productApi3ReaderProxyV1SolUsd.getRoundData(blockNumber)) - .to.be.revertedWithCustomError(productApi3ReaderProxyV1SolUsd, 'FunctionIsNotSupported') + await expect(productApi3ReaderProxyV1.getRoundData(blockNumber)) + .to.be.revertedWithCustomError(productApi3ReaderProxyV1, 'FunctionIsNotSupported') .withArgs(); }); }); describe('latestRoundData', function () { it('returns approximated round data', async function () { - const { productApi3ReaderProxyV1SolUsd } = await helpers.loadFixture(deploy); - const [value, timestamp] = await productApi3ReaderProxyV1SolUsd.read(); - const [roundId, answer, startedAt, updatedAt, answeredInRound] = - await productApi3ReaderProxyV1SolUsd.latestRoundData(); + const { productApi3ReaderProxyV1 } = await helpers.loadFixture(deploy); + const [value, timestamp] = await productApi3ReaderProxyV1.read(); + const [roundId, answer, startedAt, updatedAt, answeredInRound] = await productApi3ReaderProxyV1.latestRoundData(); expect(roundId).to.equal(0); expect(answer).to.equal(value); expect(startedAt).to.equal(timestamp); diff --git a/test/adapters/ScaledApi3FeedProxyV1.sol.ts b/test/adapters/ScaledApi3FeedProxyV1.sol.ts index 995d38c..f84cf27 100644 --- a/test/adapters/ScaledApi3FeedProxyV1.sol.ts +++ b/test/adapters/ScaledApi3FeedProxyV1.sol.ts @@ -7,71 +7,26 @@ import * as testUtils from '../test-utils'; describe('ScaledApi3FeedProxyV1', function () { async function deploy() { - const roleNames = ['deployer', 'manager', 'airnode', 'auctioneer', 'searcher']; + const roleNames = ['deployer']; const accounts = await ethers.getSigners(); const roles: Record = roleNames.reduce((acc, roleName, index) => { return { ...acc, [roleName]: accounts[index] }; }, {}); - const accessControlRegistryFactory = await ethers.getContractFactory('AccessControlRegistry', roles.deployer); - const accessControlRegistry = await accessControlRegistryFactory.deploy(); - - const api3ServerV1Factory = await ethers.getContractFactory('Api3ServerV1', roles.deployer); - const api3ServerV1 = await api3ServerV1Factory.deploy( - accessControlRegistry.getAddress(), - 'Api3ServerV1 admin', - roles.manager!.address - ); - - const api3ServerV1OevExtensionAdminRoleDescription = 'Api3ServerV1OevExtension admin'; - const api3ServerV1OevExtensionFactory = await ethers.getContractFactory('Api3ServerV1OevExtension', roles.deployer); - const api3ServerV1OevExtension = await api3ServerV1OevExtensionFactory.deploy( - accessControlRegistry.getAddress(), - api3ServerV1OevExtensionAdminRoleDescription, - roles.manager!.address, - api3ServerV1.getAddress() - ); - - const dapiName = ethers.encodeBytes32String('ETH/USD'); - const dappId = 1; - - const api3ReaderProxyV1Factory = await ethers.getContractFactory('Api3ReaderProxyV1', roles.deployer); - const api3ReaderProxyV1 = await api3ReaderProxyV1Factory.deploy( - api3ServerV1OevExtension.getAddress(), - dapiName, - dappId - ); - - const endpointId = testUtils.generateRandomBytes32(); - const templateParameters = testUtils.generateRandomBytes(); - const templateId = ethers.keccak256(ethers.solidityPacked(['bytes32', 'bytes'], [endpointId, templateParameters])); - const beaconId = ethers.keccak256( - ethers.solidityPacked(['address', 'bytes32'], [roles.airnode!.address, templateId]) - ); - await api3ServerV1.connect(roles.manager).setDapiName(dapiName, beaconId); - - const baseBeaconValue = ethers.parseEther('1824.97'); - const baseBeaconTimestamp = await helpers.time.latest(); - const data = ethers.AbiCoder.defaultAbiCoder().encode(['int256'], [baseBeaconValue]); - const signature = await testUtils.signData(roles.airnode! as any, templateId, baseBeaconTimestamp, data); - await api3ServerV1.updateBeaconWithSignedData( - roles.airnode!.address, - templateId, - baseBeaconTimestamp, - data, - signature - ); + const dappId = testUtils.generateRandomBytes32(); + const beaconValue = ethers.parseEther('1.0001'); + const beaconTimestamp = await helpers.time.latest(); + const mockproxyFactory = await ethers.getContractFactory('MockApi3ReaderProxyV1', roles.deployer); + const proxy = await mockproxyFactory.deploy(dappId, beaconValue, beaconTimestamp); const decimals = 8; + const scaledApi3FeedProxyV1Factory = await ethers.getContractFactory('ScaledApi3FeedProxyV1', roles.deployer); - const scaledApi3FeedProxyV1 = await scaledApi3FeedProxyV1Factory.deploy( - await api3ReaderProxyV1.getAddress(), - decimals - ); + const scaledApi3FeedProxyV1 = await scaledApi3FeedProxyV1Factory.deploy(await proxy.getAddress(), decimals); return { + proxy, decimals, - api3ReaderProxyV1, scaledApi3FeedProxyV1, roles, }; @@ -90,17 +45,18 @@ describe('ScaledApi3FeedProxyV1', function () { context('targetDecimals is not invalid', function () { context('targetDecimals is not 18', function () { it('constructs', async function () { - const { api3ReaderProxyV1, scaledApi3FeedProxyV1 } = await helpers.loadFixture(deploy); - expect(await scaledApi3FeedProxyV1.proxy()).to.equal(await api3ReaderProxyV1.getAddress()); + const { proxy, scaledApi3FeedProxyV1 } = await helpers.loadFixture(deploy); + expect(await scaledApi3FeedProxyV1.proxy()).to.equal(await proxy.getAddress()); + expect(await scaledApi3FeedProxyV1.dappId()).to.equal(await proxy.dappId()); expect(await scaledApi3FeedProxyV1.isUpscaling()).to.equal(false); // targetDecimals (8) > 18 is false expect(await scaledApi3FeedProxyV1.scalingFactor()).to.equal(10_000_000_000n); // 10**(18-8) }); }); context('targetDecimals is 18', function () { it('reverts', async function () { - const { api3ReaderProxyV1, roles } = await helpers.loadFixture(deploy); + const { proxy, roles } = await helpers.loadFixture(deploy); const scaledApi3FeedProxyV1 = await ethers.getContractFactory('ScaledApi3FeedProxyV1', roles.deployer); - await expect(scaledApi3FeedProxyV1.deploy(await api3ReaderProxyV1.getAddress(), 18)) + await expect(scaledApi3FeedProxyV1.deploy(await proxy.getAddress(), 18)) .to.be.revertedWithCustomError(scaledApi3FeedProxyV1, 'NoScalingNeeded') .withArgs(); }); @@ -108,12 +64,12 @@ describe('ScaledApi3FeedProxyV1', function () { }); context('targetDecimals is invalid', function () { it('reverts', async function () { - const { api3ReaderProxyV1, roles } = await helpers.loadFixture(deploy); + const { proxy, roles } = await helpers.loadFixture(deploy); const scaledApi3FeedProxyV1 = await ethers.getContractFactory('ScaledApi3FeedProxyV1', roles.deployer); - await expect(scaledApi3FeedProxyV1.deploy(await api3ReaderProxyV1.getAddress(), 0)) + await expect(scaledApi3FeedProxyV1.deploy(await proxy.getAddress(), 0)) .to.be.revertedWithCustomError(scaledApi3FeedProxyV1, 'InvalidDecimals') .withArgs(); - await expect(scaledApi3FeedProxyV1.deploy(await api3ReaderProxyV1.getAddress(), 37)) + await expect(scaledApi3FeedProxyV1.deploy(await proxy.getAddress(), 37)) .to.be.revertedWithCustomError(scaledApi3FeedProxyV1, 'InvalidDecimals') .withArgs(); }); @@ -132,16 +88,16 @@ describe('ScaledApi3FeedProxyV1', function () { describe('latestAnswer', function () { it('returns proxy value', async function () { - const { decimals, api3ReaderProxyV1, scaledApi3FeedProxyV1 } = await helpers.loadFixture(deploy); - const [value] = await api3ReaderProxyV1.read(); + const { decimals, proxy, scaledApi3FeedProxyV1 } = await helpers.loadFixture(deploy); + const [value] = await proxy.read(); expect(await scaledApi3FeedProxyV1.latestAnswer()).to.be.equal(scale(value, decimals)); }); }); describe('latestTimestamp', function () { it('returns proxy value', async function () { - const { api3ReaderProxyV1, scaledApi3FeedProxyV1 } = await helpers.loadFixture(deploy); - const [, timestamp] = await api3ReaderProxyV1.read(); + const { proxy, scaledApi3FeedProxyV1 } = await helpers.loadFixture(deploy); + const [, timestamp] = await proxy.read(); expect(await scaledApi3FeedProxyV1.latestTimestamp()).to.be.equal(timestamp); }); }); @@ -208,8 +164,8 @@ describe('ScaledApi3FeedProxyV1', function () { describe('latestRoundData', function () { it('returns approximated round data', async function () { - const { decimals, api3ReaderProxyV1, scaledApi3FeedProxyV1 } = await helpers.loadFixture(deploy); - const [value, timestamp] = await api3ReaderProxyV1.read(); + const { decimals, proxy, scaledApi3FeedProxyV1 } = await helpers.loadFixture(deploy); + const [value, timestamp] = await proxy.read(); const [roundId, answer, startedAt, updatedAt, answeredInRound] = await scaledApi3FeedProxyV1.latestRoundData(); expect(roundId).to.equal(0); expect(answer).to.equal(scale(value, decimals));