From 359cb8830b75b69f44b1e37787639aca6e2e6a85 Mon Sep 17 00:00:00 2001 From: mkflow27 <35266877+mkflow27@users.noreply.github.com> Date: Wed, 28 Sep 2022 10:15:39 +0200 Subject: [PATCH 01/11] added cbEthRateProvider and tests --- contracts/CBETHRateProvider.sol | 28 ++++++++++++++++ contracts/interfaces/IcbETH.sol | 18 ++++++++++ contracts/test/MockCBEth.sol | 18 ++++++++++ test/cbEthRateProvider.test.ts | 40 +++++++++++++++++++++++ test/chEthRateProviderMainnetFork.test.ts | 32 ++++++++++++++++++ 5 files changed, 136 insertions(+) create mode 100644 contracts/CBETHRateProvider.sol create mode 100644 contracts/interfaces/IcbETH.sol create mode 100644 contracts/test/MockCBEth.sol create mode 100644 test/cbEthRateProvider.test.ts create mode 100644 test/chEthRateProviderMainnetFork.test.ts diff --git a/contracts/CBETHRateProvider.sol b/contracts/CBETHRateProvider.sol new file mode 100644 index 0000000..40f3737 --- /dev/null +++ b/contracts/CBETHRateProvider.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.4; + +import "./interfaces/IcbETH.sol"; +import "./interfaces/IRateProvider.sol"; + + +/** + * @title Coinbase wrapped staked Eth Rate Provider + * @notice Returns value of cbEth in terms of Eth + */ +contract CbEthRateProvider is IRateProvider { + IcbETH public immutable cbETH; + address public origin; + + constructor(IcbETH _cbETH) { + cbETH = _cbETH; + + } + + /** + * @return value of cbETH in terms of Eth scaled by 10**18 + */ + function getRate() public view override returns (uint256) { + return cbETH.exchangeRate(); + } +} \ No newline at end of file diff --git a/contracts/interfaces/IcbETH.sol b/contracts/interfaces/IcbETH.sol new file mode 100644 index 0000000..e0bc1e4 --- /dev/null +++ b/contracts/interfaces/IcbETH.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.4; + + + +/** + * @title Coinbase Staked ETH interface to return exchangeRate + * @dev + * + */ +interface IcbETH { + /** + * @notice get exchange rate + * @return Returns the current exchange rate scaled by by 10**18 + */ + function exchangeRate() external view returns (uint256); +} diff --git a/contracts/test/MockCBEth.sol b/contracts/test/MockCBEth.sol new file mode 100644 index 0000000..c347f99 --- /dev/null +++ b/contracts/test/MockCBEth.sol @@ -0,0 +1,18 @@ +//SPDX-License-Identifier: MIT + +pragma solidity^0.8.4; + +contract MockCBEth { + uint256 private _exchangeRate = 1; + + function exchangeRate() external view returns(uint256){ + return _exchangeRate*1e18; + } + + // anyone can set the rate in this mock example + function setExchangeRate(uint256 newRate) external { + _exchangeRate = newRate; + } +} + + diff --git a/test/cbEthRateProvider.test.ts b/test/cbEthRateProvider.test.ts new file mode 100644 index 0000000..ebd5c36 --- /dev/null +++ b/test/cbEthRateProvider.test.ts @@ -0,0 +1,40 @@ +import { ethers } from 'hardhat'; +import { expect } from 'chai'; +import { Contract } from 'ethers'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'; + + +describe('Coinbase Eth rate provider', function() { + let mockCBEth: Contract; + let cBEthRateProvider: Contract; + let signer: SignerWithAddress; + let tempAddress; + + before('setup eoas', async () => { + [signer, ] = await ethers.getSigners(); + }) + + beforeEach('deploy mock & rate provider', async () =>{ + const MockCBEth = await ethers.getContractFactory('MockCBEth'); + mockCBEth = await MockCBEth.deploy(); + tempAddress = mockCBEth.address; + const CBEthRateProvider = await ethers.getContractFactory('CbEthRateProvider'); + cBEthRateProvider = await CBEthRateProvider.deploy(tempAddress); + + }) + + it('returns rate scaled correctly', async () => { + expect(await mockCBEth.exchangeRate()).to.equal(ethers.utils.parseUnits('1', 18)); + }) + it('can set rate', async () => { + await mockCBEth.setExchangeRate(2) + expect(await mockCBEth.exchangeRate()).to.equal(ethers.utils.parseUnits('2', 18)); + }) + it('gets rate from rateProvider',async () => { + expect(await cBEthRateProvider.getRate()).to.equal(ethers.utils.parseUnits('1', 18)); + }) + it('gets correct rate from rateProvider after underlying rate update',async () => { + await mockCBEth.setExchangeRate(2); + expect(await cBEthRateProvider.getRate()).to.equal(ethers.utils.parseUnits('2', 18)); + }) +}) \ No newline at end of file diff --git a/test/chEthRateProviderMainnetFork.test.ts b/test/chEthRateProviderMainnetFork.test.ts new file mode 100644 index 0000000..608c286 --- /dev/null +++ b/test/chEthRateProviderMainnetFork.test.ts @@ -0,0 +1,32 @@ +import { ethers } from 'hardhat'; +import { expect, assert } from 'chai'; +import { BigNumber, Contract } from 'ethers'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'; + + + +describe('Coinbase Eth rate provider', function() { + let cBEthRateProvider: Contract; + let signer: SignerWithAddress; + let cbEthAddres = '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704'; //mainnet + + before('setup eoas', async () => { + [signer, ] = await ethers.getSigners(); + }) + + beforeEach('deploy rate provider', async () =>{ + const CBEthRateProvider = await ethers.getContractFactory('CbEthRateProvider'); + cBEthRateProvider = await CBEthRateProvider.deploy(cbEthAddres); + }) + it('checks if proxy = asset in rate provider',async () => { + expect(await cBEthRateProvider.cbETH()).to.equal(cbEthAddres) + }) + it('greater than 18 decimals scale - scaled',async () => { + let currentRate = await cBEthRateProvider.getRate(); + expect(currentRate.gt(BigNumber.from(10).pow(18))).to.be.true; + }) + it('is lower than 19 decimals - scaled',async () => { + let currentRate = await cBEthRateProvider.getRate(); + expect(currentRate.lt(BigNumber.from(10).pow(19))).to.be.true; + }) +}) \ No newline at end of file From e7a186c4a07cac85428b0a392405b35fd672792b Mon Sep 17 00:00:00 2001 From: mkflow27 <35266877+mkflow27@users.noreply.github.com> Date: Thu, 29 Sep 2022 10:39:15 +0200 Subject: [PATCH 02/11] removed vars and changed naming in tests --- test/cbEthRateProvider.test.ts | 3 +-- ...k.test.ts => cbEthRateProviderMainnetFork.test.ts} | 11 +++++------ 2 files changed, 6 insertions(+), 8 deletions(-) rename test/{chEthRateProviderMainnetFork.test.ts => cbEthRateProviderMainnetFork.test.ts} (80%) diff --git a/test/cbEthRateProvider.test.ts b/test/cbEthRateProvider.test.ts index ebd5c36..cd65e2b 100644 --- a/test/cbEthRateProvider.test.ts +++ b/test/cbEthRateProvider.test.ts @@ -7,11 +7,10 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-wit describe('Coinbase Eth rate provider', function() { let mockCBEth: Contract; let cBEthRateProvider: Contract; - let signer: SignerWithAddress; let tempAddress; before('setup eoas', async () => { - [signer, ] = await ethers.getSigners(); + console.log('Starting test from block:', await ethers.provider.getBlockNumber()) }) beforeEach('deploy mock & rate provider', async () =>{ diff --git a/test/chEthRateProviderMainnetFork.test.ts b/test/cbEthRateProviderMainnetFork.test.ts similarity index 80% rename from test/chEthRateProviderMainnetFork.test.ts rename to test/cbEthRateProviderMainnetFork.test.ts index 608c286..4dfc7b7 100644 --- a/test/chEthRateProviderMainnetFork.test.ts +++ b/test/cbEthRateProviderMainnetFork.test.ts @@ -5,21 +5,20 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-wit -describe('Coinbase Eth rate provider', function() { +describe('Coinbase Eth rate provider - mainnet fork', function() { let cBEthRateProvider: Contract; - let signer: SignerWithAddress; - let cbEthAddres = '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704'; //mainnet + let cbEthAddress = '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704'; //mainnet before('setup eoas', async () => { - [signer, ] = await ethers.getSigners(); + console.log('Starting test from block:', await ethers.provider.getBlockNumber()) }) beforeEach('deploy rate provider', async () =>{ const CBEthRateProvider = await ethers.getContractFactory('CbEthRateProvider'); - cBEthRateProvider = await CBEthRateProvider.deploy(cbEthAddres); + cBEthRateProvider = await CBEthRateProvider.deploy(cbEthAddress); }) it('checks if proxy = asset in rate provider',async () => { - expect(await cBEthRateProvider.cbETH()).to.equal(cbEthAddres) + expect(await cBEthRateProvider.cbETH()).to.equal(cbEthAddress) }) it('greater than 18 decimals scale - scaled',async () => { let currentRate = await cBEthRateProvider.getRate(); From ae2b4e642f181566dfbde93a5a1bc13d302c19fd Mon Sep 17 00:00:00 2001 From: mkflow27 <35266877+mkflow27@users.noreply.github.com> Date: Tue, 22 Nov 2022 09:12:12 +0100 Subject: [PATCH 03/11] cleanup --- .gitignore | 12 ++++- contracts/CBETHRateProvider.sol | 5 +- contracts/interfaces/IcbETH.sol | 4 -- test/cbEthRateProvider.test.ts | 61 +++++++++++------------ test/cbEthRateProviderMainnetFork.test.ts | 49 +++++++++--------- 5 files changed, 64 insertions(+), 67 deletions(-) diff --git a/.gitignore b/.gitignore index ffdc7e1..ad3e29a 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,14 @@ cache/ dist/ # typechain artifacts -typechain/ \ No newline at end of file +typechain/ + +# Mac +.DS_Store +contracts/.DS_Store + +# tests +test/ + +# scripts +scripts/ diff --git a/contracts/CBETHRateProvider.sol b/contracts/CBETHRateProvider.sol index 40f3737..d3094f0 100644 --- a/contracts/CBETHRateProvider.sol +++ b/contracts/CBETHRateProvider.sol @@ -5,18 +5,15 @@ pragma solidity ^0.8.4; import "./interfaces/IcbETH.sol"; import "./interfaces/IRateProvider.sol"; - /** * @title Coinbase wrapped staked Eth Rate Provider * @notice Returns value of cbEth in terms of Eth */ contract CbEthRateProvider is IRateProvider { IcbETH public immutable cbETH; - address public origin; constructor(IcbETH _cbETH) { cbETH = _cbETH; - } /** @@ -25,4 +22,4 @@ contract CbEthRateProvider is IRateProvider { function getRate() public view override returns (uint256) { return cbETH.exchangeRate(); } -} \ No newline at end of file +} diff --git a/contracts/interfaces/IcbETH.sol b/contracts/interfaces/IcbETH.sol index e0bc1e4..e7c1c54 100644 --- a/contracts/interfaces/IcbETH.sol +++ b/contracts/interfaces/IcbETH.sol @@ -2,12 +2,8 @@ pragma solidity ^0.8.4; - - /** * @title Coinbase Staked ETH interface to return exchangeRate - * @dev - * */ interface IcbETH { /** diff --git a/test/cbEthRateProvider.test.ts b/test/cbEthRateProvider.test.ts index cd65e2b..fa9f4e8 100644 --- a/test/cbEthRateProvider.test.ts +++ b/test/cbEthRateProvider.test.ts @@ -1,39 +1,36 @@ import { ethers } from 'hardhat'; import { expect } from 'chai'; import { Contract } from 'ethers'; -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'; +describe('Coinbase Eth rate provider', function () { + let mockCBEth: Contract; + let cBEthRateProvider: Contract; + let tempAddress; -describe('Coinbase Eth rate provider', function() { - let mockCBEth: Contract; - let cBEthRateProvider: Contract; - let tempAddress; + before('setup eoas', async () => { + console.log('Starting test from block:', await ethers.provider.getBlockNumber()); + }); - before('setup eoas', async () => { - console.log('Starting test from block:', await ethers.provider.getBlockNumber()) - }) + beforeEach('deploy mock & rate provider', async () => { + const MockCBEth = await ethers.getContractFactory('MockCBEth'); + mockCBEth = await MockCBEth.deploy(); + tempAddress = mockCBEth.address; + const CBEthRateProvider = await ethers.getContractFactory('CbEthRateProvider'); + cBEthRateProvider = await CBEthRateProvider.deploy(tempAddress); + }); - beforeEach('deploy mock & rate provider', async () =>{ - const MockCBEth = await ethers.getContractFactory('MockCBEth'); - mockCBEth = await MockCBEth.deploy(); - tempAddress = mockCBEth.address; - const CBEthRateProvider = await ethers.getContractFactory('CbEthRateProvider'); - cBEthRateProvider = await CBEthRateProvider.deploy(tempAddress); - - }) - - it('returns rate scaled correctly', async () => { - expect(await mockCBEth.exchangeRate()).to.equal(ethers.utils.parseUnits('1', 18)); - }) - it('can set rate', async () => { - await mockCBEth.setExchangeRate(2) - expect(await mockCBEth.exchangeRate()).to.equal(ethers.utils.parseUnits('2', 18)); - }) - it('gets rate from rateProvider',async () => { - expect(await cBEthRateProvider.getRate()).to.equal(ethers.utils.parseUnits('1', 18)); - }) - it('gets correct rate from rateProvider after underlying rate update',async () => { - await mockCBEth.setExchangeRate(2); - expect(await cBEthRateProvider.getRate()).to.equal(ethers.utils.parseUnits('2', 18)); - }) -}) \ No newline at end of file + it('returns rate scaled correctly', async () => { + expect(await mockCBEth.exchangeRate()).to.equal(ethers.utils.parseUnits('1', 18)); + }); + it('can set rate', async () => { + await mockCBEth.setExchangeRate(2); + expect(await mockCBEth.exchangeRate()).to.equal(ethers.utils.parseUnits('2', 18)); + }); + it('gets rate from rateProvider', async () => { + expect(await cBEthRateProvider.getRate()).to.equal(ethers.utils.parseUnits('1', 18)); + }); + it('gets correct rate from rateProvider after underlying rate update', async () => { + await mockCBEth.setExchangeRate(2); + expect(await cBEthRateProvider.getRate()).to.equal(ethers.utils.parseUnits('2', 18)); + }); +}); diff --git a/test/cbEthRateProviderMainnetFork.test.ts b/test/cbEthRateProviderMainnetFork.test.ts index 4dfc7b7..ee77502 100644 --- a/test/cbEthRateProviderMainnetFork.test.ts +++ b/test/cbEthRateProviderMainnetFork.test.ts @@ -1,31 +1,28 @@ import { ethers } from 'hardhat'; -import { expect, assert } from 'chai'; +import { expect } from 'chai'; import { BigNumber, Contract } from 'ethers'; -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'; +describe('Coinbase Eth rate provider - mainnet fork', function () { + let cBEthRateProvider: Contract; + const cbEthAddress = '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704'; //mainnet + before('setup eoas', async () => { + console.log('Starting test from block:', await ethers.provider.getBlockNumber()); + }); -describe('Coinbase Eth rate provider - mainnet fork', function() { - let cBEthRateProvider: Contract; - let cbEthAddress = '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704'; //mainnet - - before('setup eoas', async () => { - console.log('Starting test from block:', await ethers.provider.getBlockNumber()) - }) - - beforeEach('deploy rate provider', async () =>{ - const CBEthRateProvider = await ethers.getContractFactory('CbEthRateProvider'); - cBEthRateProvider = await CBEthRateProvider.deploy(cbEthAddress); - }) - it('checks if proxy = asset in rate provider',async () => { - expect(await cBEthRateProvider.cbETH()).to.equal(cbEthAddress) - }) - it('greater than 18 decimals scale - scaled',async () => { - let currentRate = await cBEthRateProvider.getRate(); - expect(currentRate.gt(BigNumber.from(10).pow(18))).to.be.true; - }) - it('is lower than 19 decimals - scaled',async () => { - let currentRate = await cBEthRateProvider.getRate(); - expect(currentRate.lt(BigNumber.from(10).pow(19))).to.be.true; - }) -}) \ No newline at end of file + beforeEach('deploy rate provider', async () => { + const CBEthRateProvider = await ethers.getContractFactory('CbEthRateProvider'); + cBEthRateProvider = await CBEthRateProvider.deploy(cbEthAddress); + }); + it('checks if proxy = asset in rate provider', async () => { + expect(await cBEthRateProvider.cbETH()).to.equal(cbEthAddress); + }); + it('greater than 18 decimals scale - scaled', async () => { + const currentRate = await cBEthRateProvider.getRate(); + expect(currentRate.gt(BigNumber.from(10).pow(18))).to.be.true; + }); + it('is lower than 19 decimals - scaled', async () => { + const currentRate = await cBEthRateProvider.getRate(); + expect(currentRate.lt(BigNumber.from(10).pow(19))).to.be.true; + }); +}); From 16c8818056d09454fd9ee79d4c800ea0cb606ed9 Mon Sep 17 00:00:00 2001 From: mkflow27 <35266877+mkflow27@users.noreply.github.com> Date: Tue, 22 Nov 2022 09:18:25 +0100 Subject: [PATCH 04/11] Delete cbEthRateProvider.test.ts --- test/cbEthRateProvider.test.ts | 36 ---------------------------------- 1 file changed, 36 deletions(-) delete mode 100644 test/cbEthRateProvider.test.ts diff --git a/test/cbEthRateProvider.test.ts b/test/cbEthRateProvider.test.ts deleted file mode 100644 index fa9f4e8..0000000 --- a/test/cbEthRateProvider.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { ethers } from 'hardhat'; -import { expect } from 'chai'; -import { Contract } from 'ethers'; - -describe('Coinbase Eth rate provider', function () { - let mockCBEth: Contract; - let cBEthRateProvider: Contract; - let tempAddress; - - before('setup eoas', async () => { - console.log('Starting test from block:', await ethers.provider.getBlockNumber()); - }); - - beforeEach('deploy mock & rate provider', async () => { - const MockCBEth = await ethers.getContractFactory('MockCBEth'); - mockCBEth = await MockCBEth.deploy(); - tempAddress = mockCBEth.address; - const CBEthRateProvider = await ethers.getContractFactory('CbEthRateProvider'); - cBEthRateProvider = await CBEthRateProvider.deploy(tempAddress); - }); - - it('returns rate scaled correctly', async () => { - expect(await mockCBEth.exchangeRate()).to.equal(ethers.utils.parseUnits('1', 18)); - }); - it('can set rate', async () => { - await mockCBEth.setExchangeRate(2); - expect(await mockCBEth.exchangeRate()).to.equal(ethers.utils.parseUnits('2', 18)); - }); - it('gets rate from rateProvider', async () => { - expect(await cBEthRateProvider.getRate()).to.equal(ethers.utils.parseUnits('1', 18)); - }); - it('gets correct rate from rateProvider after underlying rate update', async () => { - await mockCBEth.setExchangeRate(2); - expect(await cBEthRateProvider.getRate()).to.equal(ethers.utils.parseUnits('2', 18)); - }); -}); From 8af959e77e430d7e9f417335043d01e7c134a8fa Mon Sep 17 00:00:00 2001 From: mkflow27 <35266877+mkflow27@users.noreply.github.com> Date: Tue, 22 Nov 2022 09:18:38 +0100 Subject: [PATCH 05/11] Delete cbEthRateProviderMainnetFork.test.ts --- test/cbEthRateProviderMainnetFork.test.ts | 28 ----------------------- 1 file changed, 28 deletions(-) delete mode 100644 test/cbEthRateProviderMainnetFork.test.ts diff --git a/test/cbEthRateProviderMainnetFork.test.ts b/test/cbEthRateProviderMainnetFork.test.ts deleted file mode 100644 index ee77502..0000000 --- a/test/cbEthRateProviderMainnetFork.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ethers } from 'hardhat'; -import { expect } from 'chai'; -import { BigNumber, Contract } from 'ethers'; - -describe('Coinbase Eth rate provider - mainnet fork', function () { - let cBEthRateProvider: Contract; - const cbEthAddress = '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704'; //mainnet - - before('setup eoas', async () => { - console.log('Starting test from block:', await ethers.provider.getBlockNumber()); - }); - - beforeEach('deploy rate provider', async () => { - const CBEthRateProvider = await ethers.getContractFactory('CbEthRateProvider'); - cBEthRateProvider = await CBEthRateProvider.deploy(cbEthAddress); - }); - it('checks if proxy = asset in rate provider', async () => { - expect(await cBEthRateProvider.cbETH()).to.equal(cbEthAddress); - }); - it('greater than 18 decimals scale - scaled', async () => { - const currentRate = await cBEthRateProvider.getRate(); - expect(currentRate.gt(BigNumber.from(10).pow(18))).to.be.true; - }); - it('is lower than 19 decimals - scaled', async () => { - const currentRate = await cBEthRateProvider.getRate(); - expect(currentRate.lt(BigNumber.from(10).pow(19))).to.be.true; - }); -}); From be65049ce958815250de47f35f6ab1b00126e040 Mon Sep 17 00:00:00 2001 From: mkflow27 <35266877+mkflow27@users.noreply.github.com> Date: Tue, 22 Nov 2022 09:46:34 +0100 Subject: [PATCH 06/11] Update comments --- contracts/CBETHRateProvider.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/CBETHRateProvider.sol b/contracts/CBETHRateProvider.sol index d3094f0..dad5810 100644 --- a/contracts/CBETHRateProvider.sol +++ b/contracts/CBETHRateProvider.sol @@ -7,7 +7,12 @@ import "./interfaces/IRateProvider.sol"; /** * @title Coinbase wrapped staked Eth Rate Provider - * @notice Returns value of cbEth in terms of Eth + * @notice Returns value of cbEth in terms of Eth. + * cbEth is built on Coinbase's wrapped token contract. + * https://github.com/coinbase/wrapped-tokens-os. Coinbase + * controls the oracle's address and updates exchangeRate + * every 24 hours at 4pm UTC. This update cadende may change + * in the future. */ contract CbEthRateProvider is IRateProvider { IcbETH public immutable cbETH; From 91344d3bd5bda0a6ed798fb11d535a2c50afe923 Mon Sep 17 00:00:00 2001 From: mkflow27 <35266877+mkflow27@users.noreply.github.com> Date: Tue, 22 Nov 2022 17:33:06 +0100 Subject: [PATCH 07/11] remove space and update gitignore --- .gitignore | 3 --- contracts/test/MockCBEth.sol | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index ad3e29a..668e8e3 100644 --- a/.gitignore +++ b/.gitignore @@ -31,8 +31,5 @@ typechain/ .DS_Store contracts/.DS_Store -# tests -test/ - # scripts scripts/ diff --git a/contracts/test/MockCBEth.sol b/contracts/test/MockCBEth.sol index c347f99..26989ac 100644 --- a/contracts/test/MockCBEth.sol +++ b/contracts/test/MockCBEth.sol @@ -1,6 +1,6 @@ //SPDX-License-Identifier: MIT -pragma solidity^0.8.4; +pragma solidity ^0.8.4; contract MockCBEth { uint256 private _exchangeRate = 1; From 09eead4af4b285104b92d3d4b7200931101fc965 Mon Sep 17 00:00:00 2001 From: mkflow27 <35266877+mkflow27@users.noreply.github.com> Date: Tue, 22 Nov 2022 17:34:18 +0100 Subject: [PATCH 08/11] change version pragma --- contracts/CBETHRateProvider.sol | 2 +- contracts/interfaces/IcbETH.sol | 2 +- contracts/test/MockCBEth.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/CBETHRateProvider.sol b/contracts/CBETHRateProvider.sol index d3094f0..26661a9 100644 --- a/contracts/CBETHRateProvider.sol +++ b/contracts/CBETHRateProvider.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.0; import "./interfaces/IcbETH.sol"; import "./interfaces/IRateProvider.sol"; diff --git a/contracts/interfaces/IcbETH.sol b/contracts/interfaces/IcbETH.sol index e7c1c54..deb6afc 100644 --- a/contracts/interfaces/IcbETH.sol +++ b/contracts/interfaces/IcbETH.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.0; /** * @title Coinbase Staked ETH interface to return exchangeRate diff --git a/contracts/test/MockCBEth.sol b/contracts/test/MockCBEth.sol index 26989ac..c3bc469 100644 --- a/contracts/test/MockCBEth.sol +++ b/contracts/test/MockCBEth.sol @@ -1,6 +1,6 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.0; contract MockCBEth { uint256 private _exchangeRate = 1; From b24e313fce31a9ad8c18568c245e9a430492555c Mon Sep 17 00:00:00 2001 From: mkflow27 <35266877+mkflow27@users.noreply.github.com> Date: Tue, 22 Nov 2022 17:52:25 +0100 Subject: [PATCH 09/11] add tests --- contracts/CBETHRateProvider.sol | 2 +- test/cbEthRateProvider.test.ts | 36 +++++++++++++++++++++++ test/cbEthRateProviderMainnetFork.test.ts | 28 ++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 test/cbEthRateProvider.test.ts create mode 100644 test/cbEthRateProviderMainnetFork.test.ts diff --git a/contracts/CBETHRateProvider.sol b/contracts/CBETHRateProvider.sol index 9d0d4db..116e137 100644 --- a/contracts/CBETHRateProvider.sol +++ b/contracts/CBETHRateProvider.sol @@ -7,7 +7,7 @@ import "./interfaces/IRateProvider.sol"; /** * @title Coinbase wrapped staked Eth Rate Provider - * @notice Returns value of cbEth in terms of Eth. + * @notice Returns value of cbEth in terms of Eth. * cbEth is built on Coinbase's wrapped token contract. * https://github.com/coinbase/wrapped-tokens-os. Coinbase * controls the oracle's address and updates exchangeRate diff --git a/test/cbEthRateProvider.test.ts b/test/cbEthRateProvider.test.ts new file mode 100644 index 0000000..fa9f4e8 --- /dev/null +++ b/test/cbEthRateProvider.test.ts @@ -0,0 +1,36 @@ +import { ethers } from 'hardhat'; +import { expect } from 'chai'; +import { Contract } from 'ethers'; + +describe('Coinbase Eth rate provider', function () { + let mockCBEth: Contract; + let cBEthRateProvider: Contract; + let tempAddress; + + before('setup eoas', async () => { + console.log('Starting test from block:', await ethers.provider.getBlockNumber()); + }); + + beforeEach('deploy mock & rate provider', async () => { + const MockCBEth = await ethers.getContractFactory('MockCBEth'); + mockCBEth = await MockCBEth.deploy(); + tempAddress = mockCBEth.address; + const CBEthRateProvider = await ethers.getContractFactory('CbEthRateProvider'); + cBEthRateProvider = await CBEthRateProvider.deploy(tempAddress); + }); + + it('returns rate scaled correctly', async () => { + expect(await mockCBEth.exchangeRate()).to.equal(ethers.utils.parseUnits('1', 18)); + }); + it('can set rate', async () => { + await mockCBEth.setExchangeRate(2); + expect(await mockCBEth.exchangeRate()).to.equal(ethers.utils.parseUnits('2', 18)); + }); + it('gets rate from rateProvider', async () => { + expect(await cBEthRateProvider.getRate()).to.equal(ethers.utils.parseUnits('1', 18)); + }); + it('gets correct rate from rateProvider after underlying rate update', async () => { + await mockCBEth.setExchangeRate(2); + expect(await cBEthRateProvider.getRate()).to.equal(ethers.utils.parseUnits('2', 18)); + }); +}); diff --git a/test/cbEthRateProviderMainnetFork.test.ts b/test/cbEthRateProviderMainnetFork.test.ts new file mode 100644 index 0000000..ee77502 --- /dev/null +++ b/test/cbEthRateProviderMainnetFork.test.ts @@ -0,0 +1,28 @@ +import { ethers } from 'hardhat'; +import { expect } from 'chai'; +import { BigNumber, Contract } from 'ethers'; + +describe('Coinbase Eth rate provider - mainnet fork', function () { + let cBEthRateProvider: Contract; + const cbEthAddress = '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704'; //mainnet + + before('setup eoas', async () => { + console.log('Starting test from block:', await ethers.provider.getBlockNumber()); + }); + + beforeEach('deploy rate provider', async () => { + const CBEthRateProvider = await ethers.getContractFactory('CbEthRateProvider'); + cBEthRateProvider = await CBEthRateProvider.deploy(cbEthAddress); + }); + it('checks if proxy = asset in rate provider', async () => { + expect(await cBEthRateProvider.cbETH()).to.equal(cbEthAddress); + }); + it('greater than 18 decimals scale - scaled', async () => { + const currentRate = await cBEthRateProvider.getRate(); + expect(currentRate.gt(BigNumber.from(10).pow(18))).to.be.true; + }); + it('is lower than 19 decimals - scaled', async () => { + const currentRate = await cBEthRateProvider.getRate(); + expect(currentRate.lt(BigNumber.from(10).pow(19))).to.be.true; + }); +}); From 147019604db246689f57e9354c63a6fe8d4c4a6c Mon Sep 17 00:00:00 2001 From: mkflow27 <35266877+mkflow27@users.noreply.github.com> Date: Tue, 29 Nov 2022 12:08:16 +0100 Subject: [PATCH 10/11] rework to generic `StakedTokenV1` rateProvider --- contracts/CBETHRateProvider.sol | 30 ----------------- contracts/StakedTokenV1RateProvider.sol | 32 +++++++++++++++++++ contracts/interfaces/IStakedTokenV1.sol | 18 +++++++++++ contracts/interfaces/IcbETH.sol | 14 -------- .../{MockCBEth.sol => MockStakedTokenV1.sol} | 2 +- test/cbEthRateProvider.test.ts | 28 ++++++++-------- test/cbEthRateProviderMainnetFork.test.ts | 15 +++++---- 7 files changed, 73 insertions(+), 66 deletions(-) delete mode 100644 contracts/CBETHRateProvider.sol create mode 100644 contracts/StakedTokenV1RateProvider.sol create mode 100644 contracts/interfaces/IStakedTokenV1.sol delete mode 100644 contracts/interfaces/IcbETH.sol rename contracts/test/{MockCBEth.sol => MockStakedTokenV1.sol} (92%) diff --git a/contracts/CBETHRateProvider.sol b/contracts/CBETHRateProvider.sol deleted file mode 100644 index 116e137..0000000 --- a/contracts/CBETHRateProvider.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./interfaces/IcbETH.sol"; -import "./interfaces/IRateProvider.sol"; - -/** - * @title Coinbase wrapped staked Eth Rate Provider - * @notice Returns value of cbEth in terms of Eth. - * cbEth is built on Coinbase's wrapped token contract. - * https://github.com/coinbase/wrapped-tokens-os. Coinbase - * controls the oracle's address and updates exchangeRate - * every 24 hours at 4pm UTC. This update cadende may change - * in the future. - */ -contract CbEthRateProvider is IRateProvider { - IcbETH public immutable cbETH; - - constructor(IcbETH _cbETH) { - cbETH = _cbETH; - } - - /** - * @return value of cbETH in terms of Eth scaled by 10**18 - */ - function getRate() public view override returns (uint256) { - return cbETH.exchangeRate(); - } -} diff --git a/contracts/StakedTokenV1RateProvider.sol b/contracts/StakedTokenV1RateProvider.sol new file mode 100644 index 0000000..3157f77 --- /dev/null +++ b/contracts/StakedTokenV1RateProvider.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./interfaces/IStakedTokenV1.sol"; +import "./interfaces/IRateProvider.sol"; + +/** + * @title Coinbase Staked Token interface to return exchangeRate + * @notice Coinbase Staked token is an ERC20 token backed by staked cryptocurrency reserves + * `StakedTokenV1` inherits and extends centre's `FiatTokenV2_1`. It is issued to represent + * the corresponding staked wrapped asset. `StakedTokenV1` implements an additional + * `exchangeRate` parameter to improve composability. StakedTokenV1 is built on Coinbase's StakedTokenV1. + * https://github.com/coinbase/wrapped-tokens-os/blob/main/contracts/wrapped-tokens/staking/StakedTokenV1.sol. + * Coinbase controls the oracle's address and updates exchangeRate + * every 24 hours at 4pm UTC. This update cadende may change + * in the future. + */ +contract StakedTokenV1RateProvider is IRateProvider { + IStakedTokenV1 public immutable stakedTokenV1; + + constructor(IStakedTokenV1 _stakedTokenV1) { + stakedTokenV1 = _stakedTokenV1; + } + + /** + * @return value of StakedTokenV1 exchangeRate in terms of it's underlying scaled by 10**18 + */ + function getRate() public view override returns (uint256) { + return stakedTokenV1.exchangeRate(); + } +} diff --git a/contracts/interfaces/IStakedTokenV1.sol b/contracts/interfaces/IStakedTokenV1.sol new file mode 100644 index 0000000..7bd3e97 --- /dev/null +++ b/contracts/interfaces/IStakedTokenV1.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @title Coinbase Staked Token interface to return exchangeRate + * @notice Coinbase Staked token is an ERC20 token backed by staked cryptocurrency reserves + * `StakedTokenV1` inherits and extends centre's `FiatTokenV2_1`. It is issued to represent + * the corresponding staked wrapped asset. `StakedTokenV1` implements an additional + * `exchangeRate` parameter to improve composability. + */ +interface IStakedTokenV1 { + /** + * @notice get exchange rate + * @return returns the current exchange rate scaled by by 10**18 + */ + function exchangeRate() external view returns (uint256); +} diff --git a/contracts/interfaces/IcbETH.sol b/contracts/interfaces/IcbETH.sol deleted file mode 100644 index deb6afc..0000000 --- a/contracts/interfaces/IcbETH.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -/** - * @title Coinbase Staked ETH interface to return exchangeRate - */ -interface IcbETH { - /** - * @notice get exchange rate - * @return Returns the current exchange rate scaled by by 10**18 - */ - function exchangeRate() external view returns (uint256); -} diff --git a/contracts/test/MockCBEth.sol b/contracts/test/MockStakedTokenV1.sol similarity index 92% rename from contracts/test/MockCBEth.sol rename to contracts/test/MockStakedTokenV1.sol index c3bc469..509cc7c 100644 --- a/contracts/test/MockCBEth.sol +++ b/contracts/test/MockStakedTokenV1.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -contract MockCBEth { +contract MockStakedTokenV1 { uint256 private _exchangeRate = 1; function exchangeRate() external view returns(uint256){ diff --git a/test/cbEthRateProvider.test.ts b/test/cbEthRateProvider.test.ts index fa9f4e8..c661bfa 100644 --- a/test/cbEthRateProvider.test.ts +++ b/test/cbEthRateProvider.test.ts @@ -2,9 +2,9 @@ import { ethers } from 'hardhat'; import { expect } from 'chai'; import { Contract } from 'ethers'; -describe('Coinbase Eth rate provider', function () { - let mockCBEth: Contract; - let cBEthRateProvider: Contract; +describe('StakedTokenV1RateProvider', function () { + let mockStakedTokenV1: Contract; + let stakedTokenV1RateProvider: Contract; let tempAddress; before('setup eoas', async () => { @@ -12,25 +12,25 @@ describe('Coinbase Eth rate provider', function () { }); beforeEach('deploy mock & rate provider', async () => { - const MockCBEth = await ethers.getContractFactory('MockCBEth'); - mockCBEth = await MockCBEth.deploy(); - tempAddress = mockCBEth.address; - const CBEthRateProvider = await ethers.getContractFactory('CbEthRateProvider'); - cBEthRateProvider = await CBEthRateProvider.deploy(tempAddress); + const MockStakedTokenV1 = await ethers.getContractFactory('MockStakedTokenV1'); + mockStakedTokenV1 = await MockStakedTokenV1.deploy(); + tempAddress = mockStakedTokenV1.address; + const StakedTokenV1RateProvider = await ethers.getContractFactory('StakedTokenV1RateProvider'); + stakedTokenV1RateProvider = await StakedTokenV1RateProvider.deploy(tempAddress); }); it('returns rate scaled correctly', async () => { - expect(await mockCBEth.exchangeRate()).to.equal(ethers.utils.parseUnits('1', 18)); + expect(await mockStakedTokenV1.exchangeRate()).to.equal(ethers.utils.parseUnits('1', 18)); }); it('can set rate', async () => { - await mockCBEth.setExchangeRate(2); - expect(await mockCBEth.exchangeRate()).to.equal(ethers.utils.parseUnits('2', 18)); + await mockStakedTokenV1.setExchangeRate(2); + expect(await mockStakedTokenV1.exchangeRate()).to.equal(ethers.utils.parseUnits('2', 18)); }); it('gets rate from rateProvider', async () => { - expect(await cBEthRateProvider.getRate()).to.equal(ethers.utils.parseUnits('1', 18)); + expect(await stakedTokenV1RateProvider.getRate()).to.equal(ethers.utils.parseUnits('1', 18)); }); it('gets correct rate from rateProvider after underlying rate update', async () => { - await mockCBEth.setExchangeRate(2); - expect(await cBEthRateProvider.getRate()).to.equal(ethers.utils.parseUnits('2', 18)); + await mockStakedTokenV1.setExchangeRate(2); + expect(await stakedTokenV1RateProvider.getRate()).to.equal(ethers.utils.parseUnits('2', 18)); }); }); diff --git a/test/cbEthRateProviderMainnetFork.test.ts b/test/cbEthRateProviderMainnetFork.test.ts index ee77502..ff5d5ab 100644 --- a/test/cbEthRateProviderMainnetFork.test.ts +++ b/test/cbEthRateProviderMainnetFork.test.ts @@ -3,26 +3,27 @@ import { expect } from 'chai'; import { BigNumber, Contract } from 'ethers'; describe('Coinbase Eth rate provider - mainnet fork', function () { - let cBEthRateProvider: Contract; - const cbEthAddress = '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704'; //mainnet + let stakedTokenV1RateProvider: Contract; + // mainnet coinbase staked ether + const stakedTokenV1Address = '0xBe9895146f7AF43049ca1c1AE358B0541Ea49704'; before('setup eoas', async () => { console.log('Starting test from block:', await ethers.provider.getBlockNumber()); }); beforeEach('deploy rate provider', async () => { - const CBEthRateProvider = await ethers.getContractFactory('CbEthRateProvider'); - cBEthRateProvider = await CBEthRateProvider.deploy(cbEthAddress); + const StakedTokenV1RateProvider = await ethers.getContractFactory('StakedTokenV1RateProvider'); + stakedTokenV1RateProvider = await StakedTokenV1RateProvider.deploy(stakedTokenV1Address); }); it('checks if proxy = asset in rate provider', async () => { - expect(await cBEthRateProvider.cbETH()).to.equal(cbEthAddress); + expect(await stakedTokenV1RateProvider.stakedTokenV1()).to.equal(stakedTokenV1Address); }); it('greater than 18 decimals scale - scaled', async () => { - const currentRate = await cBEthRateProvider.getRate(); + const currentRate = await stakedTokenV1RateProvider.getRate(); expect(currentRate.gt(BigNumber.from(10).pow(18))).to.be.true; }); it('is lower than 19 decimals - scaled', async () => { - const currentRate = await cBEthRateProvider.getRate(); + const currentRate = await stakedTokenV1RateProvider.getRate(); expect(currentRate.lt(BigNumber.from(10).pow(19))).to.be.true; }); }); From 139202d7e640cfc787441aca786bbf1756f8c699 Mon Sep 17 00:00:00 2001 From: mkflow27 <35266877+mkflow27@users.noreply.github.com> Date: Wed, 11 Jan 2023 09:49:35 +0100 Subject: [PATCH 11/11] Update contracts/StakedTokenV1RateProvider.sol spelling --- contracts/StakedTokenV1RateProvider.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/StakedTokenV1RateProvider.sol b/contracts/StakedTokenV1RateProvider.sol index 3157f77..34c99b5 100644 --- a/contracts/StakedTokenV1RateProvider.sol +++ b/contracts/StakedTokenV1RateProvider.sol @@ -13,7 +13,7 @@ import "./interfaces/IRateProvider.sol"; * `exchangeRate` parameter to improve composability. StakedTokenV1 is built on Coinbase's StakedTokenV1. * https://github.com/coinbase/wrapped-tokens-os/blob/main/contracts/wrapped-tokens/staking/StakedTokenV1.sol. * Coinbase controls the oracle's address and updates exchangeRate - * every 24 hours at 4pm UTC. This update cadende may change + * every 24 hours at 4pm UTC. This update cadence may change * in the future. */ contract StakedTokenV1RateProvider is IRateProvider {