Skip to content
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

StakedTokenV1 rate provider #7

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,11 @@ cache/
dist/

# typechain artifacts
typechain/
typechain/

# Mac
.DS_Store
contracts/.DS_Store

# scripts
scripts/
32 changes: 32 additions & 0 deletions contracts/StakedTokenV1RateProvider.sol
Original file line number Diff line number Diff line change
@@ -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
mkflow27 marked this conversation as resolved.
Show resolved Hide resolved
* 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();
}
}
18 changes: 18 additions & 0 deletions contracts/interfaces/IStakedTokenV1.sol
Original file line number Diff line number Diff line change
@@ -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);
}
18 changes: 18 additions & 0 deletions contracts/test/MockStakedTokenV1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract MockStakedTokenV1 {
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;
}
}


36 changes: 36 additions & 0 deletions test/cbEthRateProvider.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ethers } from 'hardhat';
import { expect } from 'chai';
import { Contract } from 'ethers';

describe('StakedTokenV1RateProvider', function () {
let mockStakedTokenV1: Contract;
let stakedTokenV1RateProvider: Contract;
let tempAddress;

before('setup eoas', async () => {
console.log('Starting test from block:', await ethers.provider.getBlockNumber());
});

beforeEach('deploy mock & rate provider', async () => {
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 mockStakedTokenV1.exchangeRate()).to.equal(ethers.utils.parseUnits('1', 18));
});
it('can set rate', async () => {
await mockStakedTokenV1.setExchangeRate(2);
expect(await mockStakedTokenV1.exchangeRate()).to.equal(ethers.utils.parseUnits('2', 18));
});
it('gets rate from rateProvider', async () => {
expect(await stakedTokenV1RateProvider.getRate()).to.equal(ethers.utils.parseUnits('1', 18));
});
it('gets correct rate from rateProvider after underlying rate update', async () => {
await mockStakedTokenV1.setExchangeRate(2);
expect(await stakedTokenV1RateProvider.getRate()).to.equal(ethers.utils.parseUnits('2', 18));
});
});
29 changes: 29 additions & 0 deletions test/cbEthRateProviderMainnetFork.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ethers } from 'hardhat';
import { expect } from 'chai';
import { BigNumber, Contract } from 'ethers';

describe('Coinbase Eth rate provider - mainnet fork', function () {
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 StakedTokenV1RateProvider = await ethers.getContractFactory('StakedTokenV1RateProvider');
stakedTokenV1RateProvider = await StakedTokenV1RateProvider.deploy(stakedTokenV1Address);
});
it('checks if proxy = asset in rate provider', async () => {
expect(await stakedTokenV1RateProvider.stakedTokenV1()).to.equal(stakedTokenV1Address);
});
it('greater than 18 decimals scale - scaled', async () => {
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 stakedTokenV1RateProvider.getRate();
expect(currentRate.lt(BigNumber.from(10).pow(19))).to.be.true;
});
});