-
Notifications
You must be signed in to change notification settings - Fork 172
WstETHPriceFeed + tests #600
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ad64b6f
f098e68
902cbe1
e0d5a09
d8f7a10
82af944
6dd5d6e
3aca4cb
9b67bf9
4a97597
97eeff8
c5d8f06
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity 0.8.15; | ||
|
||
import "./vendor/@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; | ||
|
||
interface IWstETH { | ||
function decimals() external view returns (uint8); | ||
function tokensPerStEth() external view returns (uint256); | ||
} | ||
|
||
contract WstETHPriceFeed is AggregatorV3Interface { | ||
/** Custom errors **/ | ||
error InvalidInt256(); | ||
error NotImplemented(); | ||
|
||
string public constant override description = "Custom price feed for wstETH / USD"; | ||
|
||
uint public constant override version = 1; | ||
|
||
/// @notice Scale for returned prices | ||
uint8 public override decimals = 8; | ||
|
||
/// @notice Chainlink stETH / USD price feed | ||
address public immutable stETHtoUSDPriceFeed; | ||
|
||
/// @notice WstETH contract address | ||
address public immutable wstETH; | ||
|
||
/// @notice scale for WstETH contract | ||
uint public immutable wstETHScale; | ||
|
||
constructor(address stETHtoUSDPriceFeed_, address wstETH_) { | ||
stETHtoUSDPriceFeed = stETHtoUSDPriceFeed_; | ||
wstETH = wstETH_; | ||
wstETHScale = 10 ** IWstETH(wstETH).decimals(); | ||
} | ||
|
||
function signed256(uint256 n) internal pure returns (int256) { | ||
if (n > uint256(type(int256).max)) revert InvalidInt256(); | ||
return int256(n); | ||
} | ||
|
||
/** | ||
* @notice Unimplemented function required to fulfill AggregatorV3Interface; always reverts | ||
**/ | ||
function getRoundData(uint80 _roundId) override external view returns ( | ||
uint80 roundId, | ||
int256 answer, | ||
uint256 startedAt, | ||
uint256 updatedAt, | ||
uint80 answeredInRound | ||
) { | ||
revert NotImplemented(); | ||
} | ||
|
||
/** | ||
* @notice WstETH Price for the latest round | ||
* @return roundId Round id from the stETH price feed | ||
* @return answer Latest price for wstETH / USD | ||
* @return startedAt Timestamp when the round was started; passed on from stETH price feed | ||
* @return updatedAt Timestamp when the round was last updated; passed on from stETH price feed | ||
* @return answeredInRound Round id in which the answer was computed; passed on from stETH price feed | ||
**/ | ||
function latestRoundData() override external view returns ( | ||
uint80 roundId, | ||
int256 answer, | ||
uint256 startedAt, | ||
uint256 updatedAt, | ||
uint80 answeredInRound | ||
) { | ||
(uint80 roundId, int256 stETHPrice, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) = AggregatorV3Interface(stETHtoUSDPriceFeed).latestRoundData(); | ||
uint256 tokensPerStEth = IWstETH(wstETH).tokensPerStEth(); | ||
int price = stETHPrice * signed256(wstETHScale) / signed256(tokensPerStEth); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ⛑️ Updated the logic of this line; I believe this is correct, but let me know if you agree. The price is now:
e.g.:
|
||
return (roundId, price, startedAt, updatedAt, answeredInRound); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity 0.8.15; | ||
|
||
contract SimpleWstETH { | ||
uint8 public constant decimals = 18; | ||
|
||
uint public immutable tokensPerStEth; | ||
|
||
constructor(uint tokensPerStEth_) { | ||
tokensPerStEth = tokensPerStEth_; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,7 +64,7 @@ describe('absorb', function () { | |
expect(lU1.numAbsorbed).to.be.equal(0); | ||
expect(lU1.approxSpend).to.be.equal(0); | ||
|
||
const usdcPrice = await priceFeeds['USDC'].price(); | ||
const [_, usdcPrice] = await priceFeeds['USDC'].latestRoundData(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So we were accessing the stored price directly before? I guess There was a problem hiding this comment. Choose a reason for hiding 5D40 this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, i had made all the variables are now internal and you have to access them via |
||
const baseScale = await comet.baseScale(); | ||
expect(event(a0, 0)).to.be.deep.equal({ | ||
AbsorbDebt: { | ||
|
@@ -136,7 +136,7 @@ describe('absorb', function () { | |
//expect(lA1.approxSpend).to.be.equal(459757131288n); | ||
expect(lA1.approxSpend).to.be.lt(a0.receipt.gasUsed.mul(a0.receipt.effectiveGasPrice)); | ||
|
||
const usdcPrice = await priceFeeds['USDC'].price(); | ||
const [_, usdcPrice] = await priceFeeds['USDC'].latestRoundData(); | ||
const baseScale = await comet.baseScale(); | ||
expect(event(a0, 0)).to.be.deep.equal({ | ||
AbsorbDebt: { | ||
|
@@ -247,10 +247,10 @@ describe('absorb', function () { | |
//expect(lA1.approxSpend).to.be.equal(130651238630n); | ||
expect(lA1.approxSpend).to.be.lt(a0.receipt.gasUsed.mul(a0.receipt.effectiveGasPrice)); | ||
|
||
const usdcPrice = await priceFeeds['USDC'].price(); | ||
const compPrice = await priceFeeds['COMP'].price(); | ||
const wbtcPrice = await priceFeeds['WBTC'].price(); | ||
const wethPrice = await priceFeeds['WETH'].price(); | ||
const [_a, usdcPrice] = await priceFeeds['USDC'].latestRoundData(); | ||
const [_b, compPrice] = await priceFeeds['COMP'].latestRoundData(); | ||
const [_c, wbtcPrice] = await priceFeeds['WBTC'].latestRoundData(); | ||
const [_d, wethPrice] = await priceFeeds['WETH'].latestRoundData(); | ||
const baseScale = await comet.baseScale(); | ||
const compScale = exp(1, await COMP.decimals()); | ||
const wbtcScale = exp(1, await WBTC.decimals()); | ||
|
@@ -406,10 +406,10 @@ describe('absorb', function () { | |
//expect(lA1.approxSpend).to.be.equal(1672498842684n); | ||
expect(lA1.approxSpend).to.be.lt(a0.receipt.gasUsed.mul(a0.receipt.effectiveGasPrice)); | ||
|
||
const usdcPrice = await priceFeeds['USDC'].price(); | ||
const compPrice = await priceFeeds['COMP'].price(); | ||
const wbtcPrice = await priceFeeds['WBTC'].price(); | ||
const wethPrice = await priceFeeds['WETH'].price(); | ||
const [_a, usdcPrice] = await priceFeeds['USDC'].latestRoundData(); | ||
const [_b, compPrice] = await priceFeeds['COMP'].latestRoundData(); | ||
const [_c, wbtcPrice] = await priceFeeds['WBTC'].latestRoundData(); | ||
const [_d, wethPrice] = await priceFeeds['WETH'].latestRoundData(); | ||
const baseScale = await comet.baseScale(); | ||
const compScale = exp(1, await COMP.decimals()); | ||
const wbtcScale = exp(1, await WBTC.decimals()); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { ethers, exp, expect } from './helpers'; | ||
import { | ||
SimplePriceFeed__factory, | ||
SimpleWstETH__factory, | ||
WstETHPriceFeed__factory | ||
} from '../build/types'; | ||
|
||
export async function makeWstETH({ stEthPrice, tokensPerStEth }) { | ||
const SimplePriceFeedFactory = (await ethers.getContractFactory('SimplePriceFeed')) as SimplePriceFeed__factory; | ||
const stETHPriceFeed = await SimplePriceFeedFactory.deploy(stEthPrice, 8); | ||
|
||
const SimpleWstETHFactory = (await ethers.getContractFactory('SimpleWstETH')) as SimpleWstETH__factory; | ||
const simpleWstETH = await SimpleWstETHFactory.deploy(tokensPerStEth); | ||
|
||
const wstETHPriceFeedFactory = (await ethers.getContractFactory('WstETHPriceFeed')) as WstETHPriceFeed__factory; | ||
const wstETHPriceFeed = await wstETHPriceFeedFactory.deploy( | ||
stETHPriceFeed.address, | ||
simpleWstETH.address | ||
); | ||
await wstETHPriceFeed.deployed(); | ||
|
||
return { | ||
simpleWstETH, | ||
stETHPriceFeed, | ||
wstETHPriceFeed | ||
}; | ||
} | ||
|
||
const testCases = [ | ||
{ | ||
stEthPrice: exp(1300, 8), | ||
tokensPerStEth: exp(.9, 18), | ||
result: 144444444444n | ||
}, | ||
{ | ||
stEthPrice: exp(1000, 8), | ||
scott-silver marked this conversation as resolved.
Show resolved
Hide resolved
|
||
tokensPerStEth: exp(.9, 18), | ||
result: 111111111111n | ||
}, | ||
{ | ||
stEthPrice: exp(1000, 8), | ||
tokensPerStEth: exp(.2, 18), | ||
result: exp(5000, 8) | ||
}, | ||
{ | ||
stEthPrice: exp(1000, 8), | ||
tokensPerStEth: exp(.5, 18), | ||
result: exp(2000, 8) | ||
}, | ||
{ | ||
stEthPrice: exp(1000, 8), | ||
tokensPerStEth: exp(.8, 18), | ||
result: exp(1250, 8) | ||
}, | ||
]; | ||
|
||
describe('wstETH price feed', function () { | ||
describe('latestRoundData', function () { | ||
for (const { stEthPrice, tokensPerStEth, result } of testCases) { | ||
it(`stEthPrice (${stEthPrice}), tokensPerStEth (${tokensPerStEth}) -> ${result}`, async () => { | ||
const { wstETHPriceFeed } = await makeWstETH({ stEthPrice, tokensPerStEth }); | ||
const latestRoundData = await wstETHPriceFeed.latestRoundData(); | ||
const price = latestRoundData.answer.toBigInt(); | ||
|
||
expect(price).to.eq(result); | ||
scott-silver marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}); | ||
} | ||
|
||
it('passes along roundId, startedAt, updatedAt and answeredInRound values from stETH price feed', async () => { | ||
const { stETHPriceFeed, wstETHPriceFeed } = await makeWstETH({ | ||
stEthPrice: exp(1000, 8), | ||
tokensPerStEth: exp(.8, 18), | ||
}); | ||
|
||
await stETHPriceFeed.setRoundData( | ||
exp(15, 18), // roundId_, | ||
1, // answer_, | ||
exp(16, 8), // startedAt_, | ||
exp(17, 8), // updatedAt_, | ||
exp(18, 18) // answeredInRound_ | ||
); | ||
|
||
const { | ||
roundId, | ||
startedAt, | ||
updatedAt, | ||
answeredInRound | ||
} = await wstETHPriceFeed.latestRoundData(); | ||
|
||
expect(roundId.toBigInt()).to.eq(exp(15, 18)); | ||
expect(startedAt.toBigInt()).to.eq(exp(16, 8)); | ||
expect(updatedAt.toBigInt()).to.eq(exp(17, 8)); | ||
expect(answeredInRound.toBigInt()).to.eq(exp(18, 18)); | ||
}); | ||
}); | ||
|
||
it(`getRoundData > always reverts`, async () => { | ||
const { wstETHPriceFeed } = await makeWstETH({ | ||
stEthPrice: exp(1000, 8), | ||
tokensPerStEth: exp(.2, 18) | ||
}); | ||
|
||
await expect( | ||
wstETHPriceFeed.getRoundData(1) | ||
).to.be.revertedWith("custom error 'NotImplemented()'"); | ||
}); | ||
}); |
Uh oh!
There was an error while loading. Please reload this page.