Skip to content

Commit abd819f

Browse files
(feat): Add SetTokenRateViewer contract (#124)
1 parent db4eaa5 commit abd819f

File tree

8 files changed

+192
-8
lines changed

8 files changed

+192
-8
lines changed

.circleci/config.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ jobs:
7979
background: true
8080
- run:
8181
name: Hardhat Test
82-
command: yarn test:integration:polygon
82+
command: yarn run test:integration:polygon
8383

8484
test_integration_optimism:
8585
docker:
@@ -102,7 +102,7 @@ jobs:
102102
background: true
103103
- run:
104104
name: Hardhat Test
105-
command: yarn test:integration:optimism
105+
command: yarn run test:integration:optimism
106106

107107
test_integration_ethereum:
108108
docker:
@@ -125,7 +125,7 @@ jobs:
125125
background: true
126126
- run:
127127
name: Hardhat Test
128-
command: yarn test:integration:ethereum
128+
command: yarn run test:integration:ethereum
129129

130130
coverage:
131131
docker:
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
Copyright 2023 Index Cooperative
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License.
13+
14+
SPDX-License-Identifier: Apache License, Version 2.0
15+
*/
16+
17+
pragma solidity 0.6.10;
18+
pragma experimental ABIEncoderV2;
19+
20+
/**
21+
* https://github.com/balancer-labs/metastable-rate-providers/blob/master/contracts/interfaces/IRateProvider.sol
22+
**/
23+
interface IRateProvider {
24+
function getRate() external view returns (uint256);
25+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
Copyright 2023 IndexCoop.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache License, Version 2.0
17+
*/
18+
pragma solidity 0.6.10;
19+
pragma experimental ABIEncoderV2;
20+
21+
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
22+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
23+
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
24+
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
25+
26+
import { IRateProvider } from "../interfaces/external/IRateProvider.sol";
27+
import { ISetToken } from "../interfaces/ISetToken.sol";
28+
29+
/**
30+
* @title SetTokenRateReviewer
31+
* @author FlattestWhite
32+
*
33+
* A RateProvider contract that provides the amount of component per set token through the getRate function.
34+
* Used by balancer in their liquidity pools.
35+
* https://github.com/balancer-labs/metastable-rate-providers/blob/master/contracts/WstETHRateProvider.sol
36+
*/
37+
contract SetTokenRateViewer is IRateProvider {
38+
39+
using Address for address;
40+
using SafeMath for uint256;
41+
using SafeCast for int256;
42+
43+
ISetToken public immutable setToken;
44+
IERC20 public immutable component;
45+
46+
constructor(ISetToken _setToken, IERC20 _component) public {
47+
setToken = _setToken;
48+
component = _component;
49+
}
50+
51+
/* =========== External Functions ============ */
52+
53+
/**
54+
* @return the amount of component per set token.
55+
*/
56+
function getRate() external view override returns (uint256) {
57+
return setToken.getTotalComponentRealUnits(address(component)).toUint256();
58+
}
59+
}

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@
4040
"rename-extensions": "for f in typechain/*.d.ts; do mv -- \"$f\" \"${f%.d.ts}.ts\"; done",
4141
"semantic-release": "semantic-release --branches master",
4242
"test": "npx hardhat test --network localhost",
43+
"test:integration": "INTEGRATIONTEST=true VERBOSE=true yarn run test",
4344
"test:fast": "TS_NODE_TRANSPILE_ONLY=1 npx hardhat test --network localhost --no-compile",
4445
"test:fast:compile": "TS_NODE_TRANSPILE_ONLY=1 npx hardhat test --network localhost",
45-
"test:integration:polygon": "INTEGRATIONTEST=true VERBOSE=true yarn run test test/integration/polygon/**",
46-
"test:integration:optimism": "INTEGRATIONTEST=true VERBOSE=true yarn run test test/integration/optimism/**",
47-
"test:integration:ethereum": "INTEGRATIONTEST=true VERBOSE=true yarn run test test/integration/ethereum/**",
46+
"test:integration:polygon": "find test/integration/polygon -type f -name '*.spec.ts' | xargs yarn run test:integration",
47+
"test:integration:optimism": "find test/integration/optimism -type f -name '*.spec.ts' | xargs yarn run test:integration",
48+
"test:integration:ethereum": "find test/integration/ethereum -type f -name '*.spec.ts' | xargs yarn run test:integration",
4849
"test:clean": "yarn clean && yarn build && yarn test",
4950
"transpile": "tsc",
5051
"transpile-dist": "tsc -p tsconfig.dist.json",

test/integration/ethereum/addresses.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const PRODUCTION_ADDRESSES = {
77
dai: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
88
weth: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
99
icEth: "0x7C07F7aBe10CE8e33DC6C5aD68FE033085256A84",
10+
aSTETH: "0x1982b2F5814301d4e9a8b0201555376e62F82428",
1011
ETH2xFli: "0xAa6E8127831c9DE45ae56bB1b0d4D4Da6e5665BD",
1112
cEther: "0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5",
1213
USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",

test/integration/ethereum/flashMintLeveragedCompIntegration.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,8 @@ if (process.env.INTEGRATIONTEST) {
322322
inputSpent = inputSpent.sub(gasCost);
323323
}
324324
const quotedInputAmount = await subjectQuote();
325-
expect(quotedInputAmount).to.gt(preciseMul(inputSpent, ether(0.97)));
326-
expect(quotedInputAmount).to.lt(preciseMul(inputSpent, ether(1.03)));
325+
expect(quotedInputAmount).to.gt(preciseMul(inputSpent, ether(0.96)));
326+
expect(quotedInputAmount).to.lt(preciseMul(inputSpent, ether(1.04)));
327327
});
328328
},
329329
);
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import "module-alias/register";
2+
import { ethers, network } from "hardhat";
3+
import { Account } from "@utils/types";
4+
import DeployHelper from "@utils/deploys";
5+
import { getAccounts, getWaffleExpect, ether } from "@utils/index";
6+
7+
import { IERC20, ISetToken, SetTokenRateViewer } from "../../../../typechain";
8+
import { PRODUCTION_ADDRESSES, STAGING_ADDRESSES } from "../addresses";
9+
10+
const expect = getWaffleExpect();
11+
12+
if (process.env.INTEGRATIONTEST) {
13+
describe("SetTokenRateViewer - Integration Test", async () => {
14+
const addresses = process.env.USE_STAGING_ADDRESSES ? STAGING_ADDRESSES : PRODUCTION_ADDRESSES;
15+
let owner: Account;
16+
let deployer: DeployHelper;
17+
18+
let snapshotId: number;
19+
let viewer: SetTokenRateViewer;
20+
let setToken: ISetToken;
21+
let component: IERC20;
22+
23+
before(async () => {
24+
[owner] = await getAccounts();
25+
deployer = new DeployHelper(owner.wallet);
26+
});
27+
28+
beforeEach(async () => {
29+
snapshotId = await network.provider.send("evm_snapshot", []);
30+
});
31+
32+
async function subject() {
33+
viewer = await deployer.viewers.deploySetTokenRateViewer(setToken.address, component.address);
34+
}
35+
36+
afterEach(async () => {
37+
await network.provider.send("evm_revert", [snapshotId]);
38+
});
39+
40+
context("wsEth2", async () => {
41+
it("should get the amount of sETH2 per wsETH2 token", async () => {
42+
setToken = (await ethers.getContractAt("ISetToken", addresses.tokens.wsETH2)) as ISetToken;
43+
component = (await ethers.getContractAt("IERC20", addresses.tokens.sETH2)) as IERC20;
44+
45+
await subject();
46+
47+
const rate = await viewer.getRate();
48+
expect(rate).to.eq(ether(1));
49+
});
50+
51+
it("should get the amount of rETH2 per wsETh2 token", async () => {
52+
setToken = (await ethers.getContractAt("ISetToken", addresses.tokens.wsETH2)) as ISetToken;
53+
component = (await ethers.getContractAt("IERC20", addresses.tokens.rETH2)) as IERC20;
54+
55+
await subject();
56+
57+
const rate = await viewer.getRate();
58+
expect(rate).to.eq(0);
59+
});
60+
});
61+
62+
context("iceth", async () => {
63+
it("should revert on weth debt", async () => {
64+
setToken = (await ethers.getContractAt("ISetToken", addresses.tokens.icEth)) as ISetToken;
65+
66+
component = (await ethers.getContractAt("IERC20", addresses.tokens.weth)) as IERC20;
67+
68+
await subject();
69+
70+
await expect(viewer.getRate()).to.be.revertedWith("SafeCast: value must be positive");
71+
});
72+
73+
it("should get the amount of aSTETH per token", async () => {
74+
setToken = (await ethers.getContractAt("ISetToken", addresses.tokens.icEth)) as ISetToken;
75+
76+
component = (await ethers.getContractAt("IERC20", addresses.tokens.aSTETH)) as IERC20;
77+
78+
await subject();
79+
80+
const rate = await viewer.getRate();
81+
expect(rate).to.gt(ether(2.7));
82+
expect(rate).to.lt(ether(2.8));
83+
});
84+
});
85+
});
86+
}

utils/deploys/deployViewers.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { Signer } from "ethers";
22
import { Address } from "../types";
33
import { FLIRebalanceViewer } from "../contracts";
44

5+
import { SetTokenRateViewer } from "../../typechain/SetTokenRateViewer";
56
import { FLIRebalanceViewer__factory } from "../../typechain/factories/FLIRebalanceViewer__factory";
7+
import { SetTokenRateViewer__factory } from "../../typechain/factories/SetTokenRateViewer__factory";
68

79
export default class DeployViewers {
810
private _deployerSigner: Signer;
@@ -26,4 +28,14 @@ export default class DeployViewers {
2628
uniswapV2Name
2729
);
2830
}
31+
32+
public async deploySetTokenRateViewer(
33+
setToken: Address,
34+
component: Address
35+
): Promise<SetTokenRateViewer> {
36+
return await new SetTokenRateViewer__factory(this._deployerSigner).deploy(
37+
setToken,
38+
component
39+
);
40+
}
2941
}

0 commit comments

Comments
 (0)