Skip to content

Commit e37a8aa

Browse files
committed
Fixes deployment scripts and unit tests
1 parent 8dc384d commit e37a8aa

15 files changed

+293
-317
lines changed

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,10 @@ The `NETWORK` variable should be set to a chain name as defined by `@api3/contra
111111
- **`NormalizedApi3ReaderProxyV1`**:
112112
- `NETWORK`: Target network name.
113113
- `FEED`: Address of the external data feed (e.g., a Chainlink `AggregatorV2V3Interface` compatible feed).
114+
- `DAPP_ID`: The dApp ID to associate with this proxy.
114115
- Example:
115116
```bash
116-
NETWORK=polygon FEED=0xExternalFeedAddress pnpm deploy:NormalizedApi3ReaderProxyV1
117+
NETWORK=polygon FEED=0xExternalFeedAddress DAPP_ID=YourDappId pnpm deploy:NormalizedApi3ReaderProxyV1
117118
```
118119

119120
- **`ProductApi3ReaderProxyV1`**:
@@ -183,13 +184,13 @@ Imagine your dApp requires a USD/ETH price feed with 8 decimal places, but the a
183184
1. **Deploy `InverseApi3ReaderProxyV1`**:
184185
- Input `PROXY`: Address of the ETH/USD `IApi3ReaderProxy` dAPI.
185186
- Output: An `IApi3ReaderProxy` contract. This deployed instance of `InverseApi3ReaderProxyV1` reads USD/ETH.
186-
- Example command: `NETWORK=your_network PROXY=0xAddressOfEthUsdDapi pnpm deploy:InverseApi3ReaderProxyV1`
187+
- Example command: `NETWORK=base PROXY=0xAddressOfEthUsdDapi pnpm deploy:InverseApi3ReaderProxyV1`
187188
188189
2. **Deploy `ScaledApi3FeedProxyV1`**:
189190
- Input `PROXY`: Address of the `InverseApi3ReaderProxyV1` instance deployed in step 1.
190191
- Input `DECIMALS`: `8`.
191192
- Output: An `AggregatorV2V3Interface` contract. This deployed instance of `ScaledApi3FeedProxyV1` reads USD/ETH scaled to 8 decimals.
192-
- Example command: `NETWORK=your_network PROXY=0xAddressOfDeployedInverseApi3ReaderProxyV1FromStep1 DECIMALS=8 pnpm deploy:ScaledApi3FeedProxyV1`
193+
- Example command: `NETWORK=base PROXY=0xAddressOfDeployedInverseApi3ReaderProxyV1FromStep1 DECIMALS=8 pnpm deploy:ScaledApi3FeedProxyV1`
193194
_Note: Replace `0xAddressOfDeployedInverseApi3ReaderProxyV1FromStep1` with the actual address obtained from the deployment artifact of step 1._
194195
195196
This pipeline successfully provides the dApp with the required USD/ETH feed at the desired precision and interface.
@@ -229,16 +230,17 @@ To derive the desired uStETH/USD feed and make it compatible with the Api3 ecosy
229230
1. **Deploy `NormalizedApi3ReaderProxyV1`**:
230231
- 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.
231232
- Input `FEED`: Address of the external uStETH/ETH `AggregatorV2V3Interface` feed.
233+
- Input `DAPP_ID`: The dApp ID to associate with this proxy.
232234
- Output: An `IApi3ReaderProxy` contract. This deployed instance of `NormalizedApi3ReaderProxyV1` reads uStETH/ETH, with its value normalized to 18 decimals.
233-
- Example command: `NETWORK=your_network FEED=0xAddressOfExternal_uStETH_ETH_Feed pnpm deploy:NormalizedApi3ReaderProxyV1`
235+
- Example command: `NETWORK=base FEED=0xAddressOfExternal_uStETH_ETH_Feed DAPP_ID=YourDappId pnpm deploy:NormalizedApi3ReaderProxyV1`
234236
235237
2. **Deploy `ProductApi3ReaderProxyV1` to calculate uStETH/USD**:
236238
- This step multiplies the normalized uStETH/ETH rate by the ETH/USD rate from the Api3 dAPI.
237239
- Input `PROXY1`: Address of the `NormalizedApi3ReaderProxyV1` instance deployed in step 1.
238240
- Input `PROXY2`: Address of the existing ETH/USD `IApi3ReaderProxy` dAPI.
239241
- Output: An `IApi3ReaderProxy` contract. This deployed instance of `ProductApi3ReaderProxyV1` reads uStETH/USD.
240242
- Calculation: `(uStETH/ETH) * (ETH/USD) = uStETH/USD`.
241-
- Example command: `NETWORK=your_network PROXY1=0xAddressOfDeployedNormalizedApi3ReaderProxyV1FromStep1 PROXY2=0xAddressOfApi3EthUsdDapi pnpm deploy:ProductApi3ReaderProxyV1`
243+
- Example command: `NETWORK=base PROXY1=0xAddressOfDeployedNormalizedApi3ReaderProxyV1FromStep1 PROXY2=0xAddressOfApi3EthUsdDapi pnpm deploy:ProductApi3ReaderProxyV1`
242244
_(Note: Replace `0xAddressOfDeployedNormalizedApi3ReaderProxyV1FromStep1` with the actual address obtained from the deployment artifact of step 1)._
243245
244246
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.

contracts/test/AccessControlRegistry.sol

Lines changed: 0 additions & 4 deletions
This file was deleted.

contracts/test/Api3ReaderProxyV1.sol

Lines changed: 0 additions & 4 deletions
This file was deleted.

contracts/test/Api3ServerV1.sol

Lines changed: 0 additions & 4 deletions
This file was deleted.

contracts/test/Api3ServerV1OevExtension.sol

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.27;
3+
4+
import {
5+
IApi3ReaderProxyV1
6+
} from "@api3/contracts/api3-server-v1/proxies/interfaces/IApi3ReaderProxyV1.sol";
7+
8+
/// @title A mock contract for IApi3ReaderProxyV1
9+
/// @dev This mock implements the minimal functions required for testing the
10+
/// data-feed-proxy-combinators contracts. Other functions will revert.
11+
contract MockApi3ReaderProxyV1 is IApi3ReaderProxyV1 {
12+
/// @notice The dApp ID of the mock proxy.
13+
uint256 public immutable override dappId;
14+
/// @notice The mock value to be returned by read().
15+
int224 private _value;
16+
/// @notice The mock timestamp to be returned by read().
17+
uint32 private _timestamp;
18+
19+
constructor(uint256 dappId_, int224 value_, uint32 timestamp_) {
20+
dappId = dappId_;
21+
_value = value_;
22+
_timestamp = timestamp_;
23+
}
24+
25+
function read() public view override returns (int224, uint32) {
26+
return (_value, _timestamp);
27+
}
28+
29+
function update(int224 value, uint32 timestamp) external {
30+
_value = value;
31+
_timestamp = timestamp;
32+
}
33+
34+
// --- Stubbed functions from IApi3ReaderProxyV1 that are not used by combinators ---
35+
36+
function initialize(address) external pure override {
37+
revert("Mock: Not implemented");
38+
}
39+
40+
function api3ServerV1() external pure override returns (address) {
41+
revert("Mock: Not implemented");
42+
}
43+
44+
function api3ServerV1OevExtension()
45+
external
46+
pure
47+
override
48+
returns (address)
49+
{
50+
revert("Mock: Not implemented");
51+
}
52+
53+
function dapiName() external pure override returns (bytes32) {
54+
revert("Mock: Not implemented");
55+
}
56+
}

deploy/001_deploy_InverseApi3ReaderProxyV1.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
11
import type { HardhatRuntimeEnvironment } from 'hardhat/types';
2+
import type { DeploymentsExtension } from 'hardhat-deploy/types';
23

34
import { getDeploymentName } from '../src';
5+
import * as testUtils from '../test/test-utils';
46

57
export const CONTRACT_NAME = 'InverseApi3ReaderProxyV1';
68

9+
const deployMockApi3ReaderProxyV1 = async (deployments: DeploymentsExtension, deployerAddress: string) => {
10+
const { address } = await deployments.deploy('MockApi3ReaderProxyV1', {
11+
from: deployerAddress,
12+
args: [
13+
testUtils.generateRandomBytes32(), // A mock dappId
14+
'2000000000000000000000', // A mock value (2000e18)
15+
Math.floor(Date.now() / 1000), // A mock timestamp
16+
],
17+
log: true,
18+
});
19+
return address;
20+
};
21+
722
module.exports = async (hre: HardhatRuntimeEnvironment) => {
823
const { getUnnamedAccounts, deployments, ethers, network, run } = hre;
924
const { deploy, log } = deployments;
@@ -14,7 +29,11 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => {
1429
}
1530
log(`Deployer address: ${deployerAddress}`);
1631

17-
const proxyAddress = process.env.PROXY;
32+
const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost';
33+
34+
const proxyAddress = isLocalNetwork
35+
? await deployMockApi3ReaderProxyV1(deployments, deployerAddress)
36+
: process.env.PROXY;
1837
if (!proxyAddress) {
1938
throw new Error('PROXY environment variable not set. Please provide the address of the proxy contract.');
2039
}
@@ -23,7 +42,7 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => {
2342
}
2443
log(`Proxy address: ${proxyAddress}`);
2544

26-
const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost';
45+
// TODO: check that proxyAddress returns a dappId
2746

2847
const confirmations = isLocalNetwork ? 1 : 5;
2948
log(`Deployment confirmations: ${confirmations}`);

deploy/002_deploy_NormalizedApi3ReaderProxyV1.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@ import type { HardhatRuntimeEnvironment } from 'hardhat/types';
22
import type { DeploymentsExtension } from 'hardhat-deploy/types';
33

44
import { getDeploymentName } from '../src';
5+
import * as testUtils from '../test/test-utils';
56

67
export const CONTRACT_NAME = 'NormalizedApi3ReaderProxyV1';
78

8-
const deployTestFeed = async (deployments: DeploymentsExtension, deployerAddress: string) => {
9-
const { address: scaledApi3FeedProxyV1Address } = await deployments.get('ScaledApi3FeedProxyV1').catch(async () => {
10-
return deployments.deploy('ScaledApi3FeedProxyV1', {
11-
from: deployerAddress,
12-
args: ['0x5b0cf2b36a65a6BB085D501B971e4c102B9Cd473', 8],
13-
log: true,
14-
});
9+
const deployMockAggregatorV2V3 = async (deployments: DeploymentsExtension, deployerAddress: string) => {
10+
const { address } = await deployments.deploy('MockAggregatorV2V3', {
11+
from: deployerAddress,
12+
args: [
13+
8, // A mock decimals
14+
'25000000', // A mock value (0.25e8)
15+
Math.floor(Date.now() / 1000), // A mock timestamp
16+
],
17+
log: true,
1518
});
16-
return scaledApi3FeedProxyV1Address;
19+
return address;
1720
};
1821

1922
module.exports = async (hre: HardhatRuntimeEnvironment) => {
@@ -26,8 +29,9 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => {
2629
}
2730
log(`Deployer address: ${deployerAddress}`);
2831

29-
const feedAddress =
30-
network.name === 'hardhat' ? await deployTestFeed(deployments, deployerAddress) : process.env.FEED;
32+
const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost';
33+
34+
const feedAddress = isLocalNetwork ? await deployMockAggregatorV2V3(deployments, deployerAddress) : process.env.FEED;
3135
if (!feedAddress) {
3236
throw new Error(
3337
'FEED environment variable not set. Please provide the address of the AggregatorV2V3Interface contract.'
@@ -38,13 +42,20 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => {
3842
}
3943
log(`Feed address: ${feedAddress}`);
4044

41-
const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost';
45+
const dappId = isLocalNetwork ? testUtils.generateRandomBytes32() : process.env.DAPP_ID;
46+
if (!dappId) {
47+
throw new Error('DAPP_ID environment variable not set. Please provide the dApp ID.');
48+
}
49+
if (!ethers.isHexString(dappId, 32)) {
50+
throw new Error(`Invalid dApp ID provided: ${dappId}`);
51+
}
52+
log(`dApp ID: ${dappId}`);
4253

4354
const confirmations = isLocalNetwork ? 1 : 5;
4455
log(`Deployment confirmations: ${confirmations}`);
4556

46-
const constructorArgs = [feedAddress];
47-
const constructorArgTypes = ['address'];
57+
const constructorArgs = [feedAddress, dappId];
58+
const constructorArgTypes = ['address', 'uint256'];
4859

4960
const deploymentName = getDeploymentName(CONTRACT_NAME, constructorArgTypes, constructorArgs);
5061
log(`Generated deterministic deployment name for this instance: ${deploymentName}`);

deploy/003_deploy_ProductApi3ReaderProxyV1.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,28 @@
11
import type { HardhatRuntimeEnvironment } from 'hardhat/types';
2+
import type { DeploymentsExtension } from 'hardhat-deploy/types';
23

34
import { getDeploymentName } from '../src';
5+
import * as testUtils from '../test/test-utils';
46

57
export const CONTRACT_NAME = 'ProductApi3ReaderProxyV1';
68

9+
const deployMockApi3ReaderProxyV1 = async (
10+
deployments: DeploymentsExtension,
11+
deployerAddress: string,
12+
dappId: string
13+
) => {
14+
const { address } = await deployments.deploy('MockApi3ReaderProxyV1', {
15+
from: deployerAddress,
16+
args: [
17+
dappId,
18+
'2000000000000000000000', // A mock value (2000e18)
19+
Math.floor(Date.now() / 1000), // A mock timestamp
20+
],
21+
log: true,
22+
});
23+
return address;
24+
};
25+
726
module.exports = async (hre: HardhatRuntimeEnvironment) => {
827
const { getUnnamedAccounts, deployments, ethers, network, run } = hre;
928
const { deploy, log } = deployments;
@@ -14,7 +33,12 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => {
1433
}
1534
log(`Deployer address: ${deployerAddress}`);
1635

17-
const proxy1Address = process.env.PROXY1;
36+
const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost';
37+
38+
const dappId = testUtils.generateRandomBytes32();
39+
const proxy1Address = isLocalNetwork
40+
? await deployMockApi3ReaderProxyV1(deployments, deployerAddress, dappId)
41+
: process.env.PROXY1;
1842
if (!proxy1Address) {
1943
throw new Error('PROXY1 environment variable not set. Please provide the address of the first proxy contract.');
2044
}
@@ -23,7 +47,14 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => {
2347
}
2448
log(`Proxy 1 address: ${proxy1Address}`);
2549

26-
const proxy2Address = process.env.PROXY2;
50+
// Sleep for 1 sec when deploying to local network in order to generate a different proxy address
51+
if (isLocalNetwork) {
52+
await new Promise((resolve) => setTimeout(resolve, 1000));
53+
}
54+
55+
const proxy2Address = isLocalNetwork
56+
? await deployMockApi3ReaderProxyV1(deployments, deployerAddress, dappId)
57+
: process.env.PROXY2;
2758
if (!proxy2Address) {
2859
throw new Error('PROXY2 environment variable not set. Please provide the address of the second proxy contract.');
2960
}
@@ -32,8 +63,6 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => {
3263
}
3364
log(`Proxy 2 address: ${proxy2Address}`);
3465

35-
const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost';
36-
3766
const confirmations = isLocalNetwork ? 1 : 5;
3867
log(`Deployment confirmations: ${confirmations}`);
3968

deploy/004_deploy_ScaledApi3FeedProxyV1.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@ import type { HardhatRuntimeEnvironment } from 'hardhat/types';
22
import type { DeploymentsExtension } from 'hardhat-deploy/types';
33

44
import { getDeploymentName } from '../src';
5+
import * as testUtils from '../test/test-utils';
56

67
export const CONTRACT_NAME = 'ScaledApi3FeedProxyV1';
78

8-
const deployTestProxy = async (deployments: DeploymentsExtension, deployerAddress: string) => {
9-
const { address: inverseApi3ReaderProxyV1Address } = await deployments
10-
.get('InverseApi3ReaderProxyV1')
11-
.catch(async () => {
12-
return deployments.deploy('InverseApi3ReaderProxyV1', {
13-
from: deployerAddress,
14-
args: ['0x5b0cf2b36a65a6BB085D501B971e4c102B9Cd473'],
15-
log: true,
16-
});
17-
});
18-
return inverseApi3ReaderProxyV1Address;
9+
const deployMockApi3ReaderProxyV1 = async (deployments: DeploymentsExtension, deployerAddress: string) => {
10+
const { address } = await deployments.deploy('MockApi3ReaderProxyV1', {
11+
from: deployerAddress,
12+
args: [
13+
testUtils.generateRandomBytes32(), // A mock dappId
14+
'2000000000000000000000', // A mock value (2000e18)
15+
Math.floor(Date.now() / 1000), // A mock timestamp
16+
],
17+
log: true,
18+
});
19+
return address;
1920
};
2021

2122
module.exports = async (hre: HardhatRuntimeEnvironment) => {
@@ -34,8 +35,11 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => {
3435
const decimals = Number.parseInt(process.env.DECIMALS, 10);
3536
log(`Decimals: ${decimals}`);
3637

37-
const proxyAddress =
38-
network.name === 'hardhat' ? await deployTestProxy(deployments, deployerAddress) : process.env.PROXY;
38+
const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost';
39+
40+
const proxyAddress = isLocalNetwork
41+
? await deployMockApi3ReaderProxyV1(deployments, deployerAddress)
42+
: process.env.PROXY;
3943
if (!proxyAddress) {
4044
throw new Error('PROXY environment variable not set. Please provide the address of the Api3ReaderProxy contract.');
4145
}
@@ -44,8 +48,6 @@ module.exports = async (hre: HardhatRuntimeEnvironment) => {
4448
}
4549
log(`Proxy address: ${proxyAddress}`);
4650

47-
const isLocalNetwork = network.name === 'hardhat' || network.name === 'localhost';
48-
4951
const confirmations = isLocalNetwork ? 1 : 5;
5052
log(`Deployment confirmations: ${confirmations}`);
5153

0 commit comments

Comments
 (0)