Skip to content

Commit

Permalink
Merge pull request #82 from buttonwood-protocol/PROM-1247-button-wrap…
Browse files Browse the repository at this point in the history
…pers-api-3-oracle-adapter

PROM-1247 - button wrappers - api3 oracle adapter
  • Loading branch information
SocksNFlops committed Mar 15, 2024
2 parents 5363a50 + dc43596 commit a4a0092
Show file tree
Hide file tree
Showing 25 changed files with 668 additions and 338 deletions.
2 changes: 1 addition & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"rules": {
"prettier/prettier": "error",
"not-rely-on-time": "off",
"max-line-length": ["error", 105]
"max-line-length": ["error", 120]
}
}
9 changes: 9 additions & 0 deletions contracts/interfaces/IDapiProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Actual interface taken from https://arbiscan.io/address/0x1bEB65b15689cCAeb5dA191c9fd5F94513923Cab#code#F11
// This should be a useful reference but at time of writing it is unclear how the import resolves to source code:
// https://github.com/api3dao/contracts/blob/main/contracts/v0.8/interfaces/IDapiProxy.sol
interface IDapiProxy {
function read() external view returns (int224 value, uint32 timestamp);
}
32 changes: 32 additions & 0 deletions contracts/mocks/MockDapiProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.4;

import "../interfaces/IDapiProxy.sol";

/**
* @title Mock DapiProxy
*
* @notice Provides a value onchain from an API3 oracle
*/
contract MockDapiProxy is IDapiProxy {
int224 public answer;
uint32 public updatedAt;

function read() external view override returns (int224 value, uint32 timestamp) {
return (answer, updatedAt);
}

/**
* Set the latest answer to be returned from now on
*/
function setLatestAnswer(int224 _answer) public {
answer = _answer;
}

/**
* Set the latest answer to be returned from now on
*/
function setUpdatedAt(uint32 _updatedAt) public {
updatedAt = _updatedAt;
}
}
47 changes: 47 additions & 0 deletions contracts/oracles/API3Oracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.4;

import {IOracle} from "../interfaces/IOracle.sol";
import {IDapiProxy} from "../interfaces/IDapiProxy.sol";

/**
* @title API3 Oracle
*
* @notice Provides a value onchain from a API3 oracle
*/
contract API3Oracle is IOracle {
/// @dev Per the docs API3 values are always 18 decimal format:
/// https://docs.api3.org/guides/dapis/read-a-dapi/#:~:text=the%20latest%20value%20with%2018%20decimals
uint256 public constant PRICE_DECIMALS = 18;
// The address of the API3 DapiProxy contract
IDapiProxy public immutable oracle;
uint256 public immutable stalenessThresholdSecs;

constructor(address _oracle, uint256 _stalenessThresholdSecs) {
oracle = IDapiProxy(_oracle);
stalenessThresholdSecs = _stalenessThresholdSecs;
}

/**
* @notice Fetches the decimal precision used in the market price from API3
* @return priceDecimals_: Number of decimals in the price
*/
function priceDecimals() external pure override returns (uint256) {
return PRICE_DECIMALS;
}

/**
* @notice Fetches the latest market price from API3
* @return Value: Latest market price.
* valid: Boolean indicating an value was fetched successfully.
*/
function getData() external view override returns (uint256, bool) {
(int224 value, uint32 timestamp) = oracle.read();
// Return invalid if value cannot be converted into a uint256
if (value < 0) {
return (0, false);
}
uint256 diff = block.timestamp - uint256(timestamp);
return (uint256(int256(value)), diff <= stalenessThresholdSecs);
}
}
4 changes: 2 additions & 2 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ export default {
accounts: process.env.ARB_PROD_PKEY
? [process.env.ARB_PROD_PKEY]
: {
mnemonic: process.env.PROD_MNEMONIC || DEFAULT_MNEMONIC,
},
mnemonic: process.env.PROD_MNEMONIC || DEFAULT_MNEMONIC,
},
chainId: 42161,
},
},
Expand Down
57 changes: 34 additions & 23 deletions tasks/deployers/ankrETHOracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,44 @@ import { HardhatRuntimeEnvironment, TaskArguments } from 'hardhat/types'

// ToDo: Change these
const prefilledArgs: Record<string, TaskArguments> = {
'mainnet': {
mainnet: {
ankreth: '0xE95A203B1a91a908F9B9CE46459d101078c2c3cb',
},
'goerli': {
goerli: {
ankreth: '0x63dC5749fa134fF3B752813388a7215460a8aB01',
}
},
}

task('deploy:AnkrETHOracle:prefilled', 'Verifies on etherscan').setAction(
async function (args: TaskArguments, hre) {
console.log('chainId:', hre.network.config.chainId);
console.log('Network:', hre.network.name);
const prefilled = prefilledArgs[hre.network.name];
console.log('chainId:', hre.network.config.chainId)
console.log('Network:', hre.network.name)
const prefilled = prefilledArgs[hre.network.name]
if (!prefilled) {
throw new Error('Network not supported')
}

const { ankreth } = prefilled;
const { ankreth } = prefilled
console.log('ankrETH Address:', ankreth)
await hre.run('deploy:AnkrETHOracle', { ankreth })
},
)

task('deploy:AnkrETHOracle')
.addParam('ankreth', 'the ankrETH token address', undefined, types.string, false)
.addParam(
'ankreth',
'the ankrETH token address',
undefined,
types.string,
false,
)
.setAction(async function (args: TaskArguments, hre) {
const { ankreth } = args;
console.log('Signer', await (await hre.ethers.getSigners())[0].getAddress());
const AnkrETHOracle = await hre.ethers.getContractFactory('AnkrETHOracle');
const ankrETHOracle = await AnkrETHOracle.deploy(ankreth);
await ankrETHOracle.deployed();
console.log(`AnkrETHOracle deployed to ${ankrETHOracle.address}`);
const { ankreth } = args
console.log('Signer', await (await hre.ethers.getSigners())[0].getAddress())
const AnkrETHOracle = await hre.ethers.getContractFactory('AnkrETHOracle')
const ankrETHOracle = await AnkrETHOracle.deploy(ankreth)
await ankrETHOracle.deployed()
console.log(`AnkrETHOracle deployed to ${ankrETHOracle.address}`)

try {
await hre.run('verify:verify', {
Expand All @@ -49,27 +55,32 @@ task('deploy:AnkrETHOracle')
task('verify:AnkrETHOracle:prefilled', 'Verifies on etherscan')
.addParam('address', 'the contract address', undefined, types.string, false)
.setAction(async function (args: TaskArguments, hre) {
console.log('chainId:', hre.network.config.chainId);
console.log('Network:', hre.network.name);
console.log('chainId:', hre.network.config.chainId)
console.log('Network:', hre.network.name)

const prefilled = prefilledArgs[hre.network.name];
const prefilled = prefilledArgs[hre.network.name]
if (!prefilled) {
throw new Error('Network not supported');
throw new Error('Network not supported')
}
const { ankreth } = prefilled;
const { ankreth } = prefilled

const { address } = args;
const { address } = args

await hre.run('verify:verify', {
address,
constructorArguments: [ankreth],
})
},
)
})

task('verify:AnkrETHOracle', 'Verifies on etherscan')
.addParam('address', 'the contract address', undefined, types.string, false)
.addParam('ankreth', 'the ankrETH token address', undefined, types.string, false)
.addParam(
'ankreth',
'the ankrETH token address',
undefined,
types.string,
false,
)
.setAction(async function (args: TaskArguments, hre) {
const { address, ankreth } = args

Expand Down
37 changes: 37 additions & 0 deletions tasks/deployers/api3Oracle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { task, types } from 'hardhat/config'
import { TaskArguments } from 'hardhat/types'

task('deploy:API3Oracle')
.addParam('dapiproxy', 'the address of the underlying DapiProxy contract')
.addParam('stalenessthresholdsecs', 'the number of seconds before refresh')
.setAction(async function (args: TaskArguments, hre) {
const { dapiproxy, stalenessthresholdsecs } = args
const API3Oracle = await hre.ethers.getContractFactory('API3Oracle')
const oracle = await API3Oracle.deploy(dapiproxy, stalenessthresholdsecs)
await oracle.deployed()
const oracleAddress = oracle.address
console.log(`Oracle deployed to ${oracleAddress}`)

try {
await hre.run('verify:API3Oracle', {
address: oracleAddress,
dapiproxy,
stalenessthresholdsecs,
})
} catch (e) {
console.log('Unable to verify on etherscan', e)
}
})

task('verify:API3Oracle')
.addParam('address', 'the contract address', undefined, types.string, false)
.addParam('dapiproxy', 'the address of the underlying DapiProxy contract')
.addParam('stalenessthresholdsecs', 'the number of seconds before refresh')
.setAction(async function (args: TaskArguments, hre) {
const { address, dapiproxy, stalenessthresholdsecs } = args

await hre.run('verify:verify', {
address,
constructorArguments: [dapiproxy, stalenessthresholdsecs],
})
})
40 changes: 0 additions & 40 deletions tasks/deployers/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,43 +93,3 @@ task('deploy:ButtonToken')
console.log('Unable to verify on etherscan', e)
}
})

task('deploy:ChainlinkOracle')
.addParam('aggregator', 'the address of the backing chainlink aggregator')
.addParam('stalenessthresholdsecs', 'the number of seconds before refresh')
.setAction(async function (args: TaskArguments, hre) {
const { aggregator, stalenessthresholdsecs } = args
const ChainlinkOracle = await hre.ethers.getContractFactory(
'ChainlinkOracle',
)
const oracle = await ChainlinkOracle.deploy(
aggregator,
stalenessthresholdsecs,
)
await oracle.deployed()
const oracleAddress = oracle.address
console.log(`Oracle deployed to ${oracleAddress}`)

try {
await hre.run('verify:ChainlinkOracle', {
address: oracleAddress,
aggregator,
stalenessthresholdsecs,
})
} catch (e) {
console.log('Unable to verify on etherscan', e)
}
})

task('verify:ChainlinkOracle')
.addParam('address', 'the contract address', undefined, types.string, false)
.addParam('aggregator', 'the address of the backing chainlink aggregator')
.addParam('stalenessthresholdsecs', 'the number of seconds before refresh')
.setAction(async function (args: TaskArguments, hre) {
const { address, aggregator, stalenessthresholdsecs } = args

await hre.run('verify:verify', {
address,
constructorArguments: [aggregator, stalenessthresholdsecs],
})
})
42 changes: 42 additions & 0 deletions tasks/deployers/chainlinkOracle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { task, types } from 'hardhat/config'
import { TaskArguments } from 'hardhat/types'

task('deploy:ChainlinkOracle')
.addParam('aggregator', 'the address of the backing chainlink aggregator')
.addParam('stalenessthresholdsecs', 'the number of seconds before refresh')
.setAction(async function (args: TaskArguments, hre) {
const { aggregator, stalenessthresholdsecs } = args
const ChainlinkOracle = await hre.ethers.getContractFactory(
'ChainlinkOracle',
)
const oracle = await ChainlinkOracle.deploy(
aggregator,
stalenessthresholdsecs,
)
await oracle.deployed()
const oracleAddress = oracle.address
console.log(`Oracle deployed to ${oracleAddress}`)

try {
await hre.run('verify:ChainlinkOracle', {
address: oracleAddress,
aggregator,
stalenessthresholdsecs,
})
} catch (e) {
console.log('Unable to verify on etherscan', e)
}
})

task('verify:ChainlinkOracle')
.addParam('address', 'the contract address', undefined, types.string, false)
.addParam('aggregator', 'the address of the backing chainlink aggregator')
.addParam('stalenessthresholdsecs', 'the number of seconds before refresh')
.setAction(async function (args: TaskArguments, hre) {
const { address, aggregator, stalenessthresholdsecs } = args

await hre.run('verify:verify', {
address,
constructorArguments: [aggregator, stalenessthresholdsecs],
})
})
Loading

0 comments on commit a4a0092

Please sign in to comment.