-
Notifications
You must be signed in to change notification settings - Fork 111
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Taylor Brent <[email protected]>
- Loading branch information
1 parent
c5b08d9
commit 0c04a2f
Showing
18 changed files
with
760 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// SPDX-License-Identifier: BlueOak-1.0.0 | ||
pragma solidity 0.8.19; | ||
|
||
import "@openzeppelin/contracts/utils/math/Math.sol"; | ||
import "../../../libraries/Fixed.sol"; | ||
import "../AppreciatingFiatCollateral.sol"; | ||
import "../OracleLib.sol"; | ||
import "./vendor/IApxETH.sol"; | ||
|
||
/** | ||
* @title apxETH Collateral | ||
* @notice Collateral plugin for Dinero apxETH (Pirex-ETH) | ||
* tok = apxETH | ||
* ref = pxETH (pegged to ETH 1:1) | ||
* tar = ETH | ||
* UoA = USD | ||
*/ | ||
contract ApxEthCollateral is AppreciatingFiatCollateral { | ||
using OracleLib for AggregatorV3Interface; | ||
using FixLib for uint192; | ||
|
||
AggregatorV3Interface public immutable targetPerTokChainlinkFeed; | ||
uint48 public immutable targetPerTokChainlinkTimeout; | ||
|
||
/// @param config.chainlinkFeed {UoA/target} price of ETH in USD terms | ||
/// @param _targetPerTokChainlinkFeed {target/tok} price of apxETH in ETH terms | ||
constructor( | ||
CollateralConfig memory config, | ||
uint192 revenueHiding, | ||
AggregatorV3Interface _targetPerTokChainlinkFeed, | ||
uint48 _targetPerTokChainlinkTimeout | ||
) AppreciatingFiatCollateral(config, revenueHiding) { | ||
require(config.defaultThreshold != 0, "defaultThreshold zero"); | ||
require(address(_targetPerTokChainlinkFeed) != address(0), "missing targetPerTok feed"); | ||
require(_targetPerTokChainlinkTimeout != 0, "targetPerTokChainlinkTimeout zero"); | ||
|
||
targetPerTokChainlinkFeed = _targetPerTokChainlinkFeed; | ||
targetPerTokChainlinkTimeout = _targetPerTokChainlinkTimeout; | ||
maxOracleTimeout = uint48(Math.max(maxOracleTimeout, _targetPerTokChainlinkTimeout)); | ||
} | ||
|
||
/// Can revert, used by other contract functions in order to catch errors | ||
/// @return low {UoA/tok} The low price estimate | ||
/// @return high {UoA/tok} The high price estimate | ||
/// @return pegPrice {target/ref} The actual price observed in the peg | ||
function tryPrice() | ||
external | ||
view | ||
override | ||
returns ( | ||
uint192 low, | ||
uint192 high, | ||
uint192 pegPrice | ||
) | ||
{ | ||
uint192 targetPerTok = targetPerTokChainlinkFeed.price(targetPerTokChainlinkTimeout); | ||
|
||
// {UoA/tok} = {UoA/target} * {target/tok} | ||
uint192 p = chainlinkFeed.price(oracleTimeout).mul(targetPerTok); | ||
uint192 err = p.mul(oracleError, CEIL); | ||
|
||
high = p + err; | ||
low = p - err; | ||
// assert(low <= high); obviously true just by inspection | ||
|
||
// {target/ref} = {target/tok} / {ref/tok} | ||
pegPrice = targetPerTok.div(underlyingRefPerTok()); | ||
} | ||
|
||
/// @return {ref/tok} Quantity of whole reference units per whole collateral tokens | ||
function underlyingRefPerTok() public view override returns (uint192) { | ||
return _safeWrap(IApxETH(address(erc20)).assetsPerShare()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Pirex apxETH (pxETH) Collateral Plugin | ||
|
||
## Summary | ||
|
||
This plugin allows `apxETH` holders use their tokens as collateral in the Reserve Protocol. | ||
|
||
As described in the [Dinero Site](https://dineroismoney.com/docs/pirex-eth-overview), Pirex ETH is an Ethereum liquid staking solution that consists of two tokens, `pxETH` and `apxETH`. | ||
|
||
Upon depositing ETH into the Dinero protocol through Pirex ETH, users receive `pxETH` - a liquid wrapper for staked ETH. However, the pxETH token itself does not earn any rewards. Users can deposit to Dinero's auto-compounding vaults to obtain `apxETH`, which is focused on maximizing their staking yields. Each `apxETH` benefits from staking rewards from more than one staked ETH, amplifying the yield for apxETH users. | ||
|
||
`apxETH` will accrue revenue from **staking rewards** into itself by **increasing** the exchange rate of `pxETH` per `apxETH`. | ||
|
||
`pxETH` contract: <https://etherscan.io/address/0x04C154b66CB340F3Ae24111CC767e0184Ed00Cc6#code> | ||
|
||
`apxETH` contract: <https://etherscan.io/address/0x9Ba021B0a9b958B5E75cE9f6dff97C7eE52cb3E6#code> | ||
|
||
## Implementation | ||
|
||
### Units | ||
|
||
| tok | ref | target | UoA | | ||
| ------ | ----- | ------ | --- | | ||
| apxETH | pxETH | ETH | USD | | ||
|
||
### Functions | ||
|
||
#### refPerTok {ref/tok} | ||
|
||
This function returns rate of `pxETH/apxETH`, getting from [assetsPerShare()](https://etherscan.io/token/0x9Ba021B0a9b958B5E75cE9f6dff97C7eE52cb3E6#readContract) function in wstETH contract. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// SPDX-License-Identifier: BlueOak-1.0.0 | ||
pragma solidity 0.8.19; | ||
|
||
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||
|
||
// External interface for apxETH | ||
interface IApxETH is IERC20Metadata { | ||
function assetsPerShare() external view returns (uint256); | ||
|
||
function setWithdrawalPenalty(uint256 penalty) external; | ||
|
||
function notifyRewardAmount() external; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// SPDX-License-Identifier: BlueOak-1.0.0 | ||
pragma solidity 0.8.19; | ||
|
||
import "./ERC20Mock.sol"; | ||
|
||
contract ApxEthMock is ERC20Mock { | ||
uint256 public assetsPerShare; | ||
|
||
constructor() ERC20Mock("Mock ApxETH", "ApxEth") {} | ||
|
||
function setAssetsPerShare(uint256 mockValue) external { | ||
assetsPerShare = mockValue; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
scripts/deployment/phase2-assets/collaterals/deploy_apxeth.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import fs from 'fs' | ||
import hre from 'hardhat' | ||
import { getChainId } from '../../../../common/blockchain-utils' | ||
import { networkConfig } from '../../../../common/configuration' | ||
import { fp } from '../../../../common/numbers' | ||
import { expect } from 'chai' | ||
import { CollateralStatus } from '../../../../common/constants' | ||
import { | ||
getDeploymentFile, | ||
getAssetCollDeploymentFilename, | ||
IAssetCollDeployments, | ||
getDeploymentFilename, | ||
fileExists, | ||
} from '../../common' | ||
import { priceTimeout, combinedError } from '../../utils' | ||
import { ApxEthCollateral } from '../../../../typechain' | ||
import { | ||
ETH_ORACLE_ERROR, | ||
ETH_ORACLE_TIMEOUT, | ||
APXETH_ORACLE_ERROR, | ||
APXETH_ORACLE_TIMEOUT, | ||
DELAY_UNTIL_DEFAULT, | ||
} from '../../../../test/plugins/individual-collateral/pirex-eth/constants' | ||
import { ContractFactory } from 'ethers' | ||
|
||
async function main() { | ||
// ==== Read Configuration ==== | ||
const [deployer] = await hre.ethers.getSigners() | ||
|
||
const chainId = await getChainId(hre) | ||
|
||
console.log(`Deploying Collateral to network ${hre.network.name} (${chainId}) | ||
with burner account: ${deployer.address}`) | ||
|
||
if (!networkConfig[chainId]) { | ||
throw new Error(`Missing network configuration for ${hre.network.name}`) | ||
} | ||
|
||
// Get phase1 deployment | ||
const phase1File = getDeploymentFilename(chainId) | ||
if (!fileExists(phase1File)) { | ||
throw new Error(`${phase1File} doesn't exist yet. Run phase 1`) | ||
} | ||
// Check previous step completed | ||
const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) | ||
const assetCollDeployments = <IAssetCollDeployments>getDeploymentFile(assetCollDeploymentFilename) | ||
|
||
const deployedCollateral: string[] = [] | ||
|
||
/******** Deploy ApxETH Collateral - apxETH **************************/ | ||
|
||
const ApxEthCollateralFactoryCollateralFactory: ContractFactory = | ||
await hre.ethers.getContractFactory('ApxEthCollateral') | ||
|
||
const oracleError = combinedError(ETH_ORACLE_ERROR, APXETH_ORACLE_ERROR) // 0.5% & 1% | ||
|
||
const collateral = <ApxEthCollateral>await ApxEthCollateralFactoryCollateralFactory.connect( | ||
deployer | ||
).deploy( | ||
{ | ||
priceTimeout: priceTimeout.toString(), | ||
chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH, | ||
oracleError: oracleError.toString(), | ||
erc20: networkConfig[chainId].tokens.apxETH, | ||
maxTradeVolume: fp('1e6').toString(), // $1m, | ||
oracleTimeout: ETH_ORACLE_TIMEOUT.toString(), // 1 hr, | ||
targetName: hre.ethers.utils.formatBytes32String('ETH'), | ||
defaultThreshold: fp('0.02').add(APXETH_ORACLE_ERROR).toString(), // 3% | ||
delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), // 72h | ||
}, | ||
fp('1e-4').toString(), // revenueHiding = 0.01% | ||
networkConfig[chainId].chainlinkFeeds.apxETH, // targetPerTokChainlinkFeed | ||
APXETH_ORACLE_TIMEOUT.toString() // targetPerTokChainlinkTimeout - 24h | ||
) | ||
await collateral.deployed() | ||
await (await collateral.refresh()).wait() | ||
expect(await collateral.status()).to.equal(CollateralStatus.SOUND) | ||
|
||
console.log(`Deployed ApxETH to ${hre.network.name} (${chainId}): ${collateral.address}`) | ||
|
||
assetCollDeployments.collateral.apxETH = collateral.address | ||
assetCollDeployments.erc20s.apxETH = networkConfig[chainId].tokens.apxETH | ||
deployedCollateral.push(collateral.address.toString()) | ||
|
||
fs.writeFileSync(assetCollDeploymentFilename, JSON.stringify(assetCollDeployments, null, 2)) | ||
|
||
console.log(`Deployed collateral to ${hre.network.name} (${chainId}) | ||
New deployments: ${deployedCollateral} | ||
Deployment file: ${assetCollDeploymentFilename}`) | ||
} | ||
|
||
main().catch((error) => { | ||
console.error(error) | ||
process.exitCode = 1 | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import hre from 'hardhat' | ||
import { getChainId } from '../../../common/blockchain-utils' | ||
import { developmentChains, networkConfig } from '../../../common/configuration' | ||
import { fp } from '../../../common/numbers' | ||
import { | ||
getDeploymentFile, | ||
getAssetCollDeploymentFilename, | ||
IAssetCollDeployments, | ||
} from '../../deployment/common' | ||
import { | ||
ETH_ORACLE_TIMEOUT, | ||
ETH_ORACLE_ERROR, | ||
DELAY_UNTIL_DEFAULT, | ||
APXETH_ORACLE_ERROR, | ||
APXETH_ORACLE_TIMEOUT, | ||
} from '../../../test/plugins/individual-collateral/pirex-eth/constants' | ||
import { priceTimeout, verifyContract, combinedError } from '../../deployment/utils' | ||
|
||
let deployments: IAssetCollDeployments | ||
|
||
async function main() { | ||
// ********** Read config ********** | ||
const chainId = await getChainId(hre) | ||
if (!networkConfig[chainId]) { | ||
throw new Error(`Missing network configuration for ${hre.network.name}`) | ||
} | ||
|
||
if (developmentChains.includes(hre.network.name)) { | ||
throw new Error(`Cannot verify contracts for development chain ${hre.network.name}`) | ||
} | ||
|
||
const assetCollDeploymentFilename = getAssetCollDeploymentFilename(chainId) | ||
deployments = <IAssetCollDeployments>getDeploymentFile(assetCollDeploymentFilename) | ||
|
||
/******** Verify ApxETH - apxETH **************************/ | ||
const oracleError = combinedError(ETH_ORACLE_ERROR, APXETH_ORACLE_ERROR) // 0.5% & 1% | ||
await verifyContract( | ||
chainId, | ||
deployments.collateral.apxETH, | ||
[ | ||
{ | ||
priceTimeout: priceTimeout.toString(), | ||
chainlinkFeed: networkConfig[chainId].chainlinkFeeds.ETH, | ||
oracleError: oracleError.toString(), // 0.5% & 1%, | ||
erc20: networkConfig[chainId].tokens.apxETH, | ||
maxTradeVolume: fp('1e6').toString(), // $1m, | ||
oracleTimeout: ETH_ORACLE_TIMEOUT.toString(), // 1 hr, | ||
targetName: hre.ethers.utils.formatBytes32String('ETH'), | ||
defaultThreshold: fp('0.02').add(APXETH_ORACLE_ERROR).toString(), // 3% | ||
delayUntilDefault: DELAY_UNTIL_DEFAULT.toString(), // 72h | ||
}, | ||
fp('1e-4'), // revenueHiding = 0.01% | ||
networkConfig[chainId].chainlinkFeeds.apxETH, // targetPerTokChainlinkFeed | ||
APXETH_ORACLE_TIMEOUT.toString(), // targetPerTokChainlinkTimeout | ||
], | ||
'contracts/plugins/assets/pirex-eth/ApxEthCollateral.sol:ApxEthCollateral' | ||
) | ||
} | ||
|
||
main().catch((error) => { | ||
console.error(error) | ||
process.exitCode = 1 | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.