Skip to content

Commit

Permalink
Allow for choosing different series
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomasdenH committed May 2, 2022
1 parent 3b85a11 commit 1a2ad24
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 77 deletions.
40 changes: 30 additions & 10 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import ladleAbi from "./abi/Ladle.json";
import {
Balances,
emptyVaults,
loadVaults,
SeriesDefinition,
Vaults,
VaultsAndBalances,
} from "./objects/Vault";
Expand All @@ -23,16 +25,17 @@ import { ContractContext as Ladle } from "./abi/Ladle";
import yieldLeverAbi from "./generated/abi/YieldLever.json";
import yieldLeverDeployed from "./generated/deployment.json";
import { ExternalProvider } from "@ethersproject/providers";
import {
SeriesResponse as Series
} from "./abi/Cauldron";

const YIELD_LEVER_CONTRACT_ADDRESS: string = yieldLeverDeployed.deployedTo;
const USDC_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const POOL_CONTRACT = "0xEf82611C6120185D3BF6e020D1993B49471E7da0";
const CAULDRON_CONTRACT = "0xc88191F8cb8e6D4a668B047c1C8503432c3Ca867";
const LADLE_CONTRACT = "0x6cB18fF2A33e981D1e38A663Ca056c0a5265066A";

const YEARN_STRATEGY = "0xa354F35829Ae975e850e23e9615b11Da1B3dC4DE";

export const SERIES_ID = "0x303230360000";
export const ILK_ID = "0x303900000000";

type YearnApiJson = { address: string; apy: { net_apy: number } }[];
Expand All @@ -43,12 +46,14 @@ interface State {
usdcBalance?: BigNumber;
vaults: VaultsAndBalances;
yearn_apy?: number;
series: SeriesDefinition[];
seriesInfo: { [seriesId: string]: Series };
}

export interface Contracts {
usdcContract: ERC20;
yieldLeverContract: YieldLever;
poolContract: Pool;
poolContracts: { [poolAddress: string]: Pool };
cauldronContract: Cauldron;
ladleContract: Ladle;
}
Expand All @@ -70,6 +75,8 @@ export class App extends React.Component<Record<string, never>, State> {
selectedAddress: undefined,
usdcBalance: undefined,
vaults: emptyVaults(),
series: [],
seriesInfo: {}
};
this.state = this.initialState;
}
Expand Down Expand Up @@ -114,6 +121,8 @@ export class App extends React.Component<Record<string, never>, State> {
contracts={this.contracts}
account={this.state.selectedAddress}
yearnApi={this.state.yearn_apy}
seriesDefinitions={this.state.series}
seriesInfo={this.state.seriesInfo}
/>,
...vaultIds.map((vaultId) => (
<VaultComponent
Expand Down Expand Up @@ -218,11 +227,7 @@ export class App extends React.Component<Record<string, never>, State> {
yieldLeverAbi.abi,
this._provider.getSigner(0)
) as any as YieldLever,
poolContract: new ethers.Contract(
POOL_CONTRACT,
poolAbi,
this._provider.getSigner(0)
) as any as Pool,
poolContracts: Object.create(null) as { [poolAddress: string]: Pool },
cauldronContract: new ethers.Contract(
CAULDRON_CONTRACT,
cauldronAbi,
Expand All @@ -235,8 +240,8 @@ export class App extends React.Component<Record<string, never>, State> {
) as any as Ladle,
};

// if (this.state.selectedAddress !== undefined)
// loadVaults(this.contracts.cauldronContract, this.state.selectedAddress, this._provider);
if (this.state.selectedAddress !== undefined)
void loadVaults(this.contracts.cauldronContract, this.state.selectedAddress, this._provider, (vaultId) => void this.addVault(vaultId), (series) => void this.addSeries(series));

const vaultsBuiltFilter =
this.contracts.cauldronContract.filters.VaultBuilt(
Expand Down Expand Up @@ -271,6 +276,21 @@ export class App extends React.Component<Record<string, never>, State> {
return error.message;
}

private async addSeries(series: SeriesDefinition) {
if (this.contracts === undefined || this._provider === undefined)
throw new Error('Race condition');
const seriesInfo = await this.contracts.cauldronContract.series(series.seriesId);
this.contracts.poolContracts[series.seriesId] = new ethers.Contract(
series.poolAddress,
poolAbi,
this._provider.getSigner(0)
) as any as Pool;
this.setState({
series: [...this.state.series, series],
seriesInfo: {...this.state.seriesInfo, [series.seriesId]: seriesInfo }
})
}

// This method resets the state
resetState() {
this.setState(this.initialState);
Expand Down
94 changes: 72 additions & 22 deletions frontend/src/components/Invest.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BigNumber, utils } from "ethers";
import React from "react";
import { Contracts, ILK_ID, SERIES_ID } from "../App";
import { Contracts, ILK_ID } from "../App";
import "./Invest.scss";
import Slippage, { addSlippage, SLIPPAGE_OPTIONS } from "./Slippage";
import UsdcInput from "./UsdcInput";
Expand All @@ -10,6 +10,7 @@ import {
SeriesResponse as Series,
ContractContext as Cauldron,
} from "../abi/Cauldron";
import { SeriesDefinition } from '../objects/Vault';

const UNITS_USDC = 6;
const UNITS_LEVERAGE = 2;
Expand All @@ -20,6 +21,8 @@ interface Properties {
label: string;
contracts: Readonly<Contracts>;
yearnApi?: number;
seriesDefinitions: SeriesDefinition[];
seriesInfo: { [seriesId: string]: Series };
}

enum ApprovalState {
Expand All @@ -37,13 +40,14 @@ interface State {
approvalState: ApprovalState;
fyTokens?: BigNumber;
slippage: number;
selectedSeriesId?: string;
interest?: number;
seriesInterest: { [seriesId: string]: number }
}

export default class Invest extends React.Component<Properties, State> {
private readonly contracts: Readonly<Contracts>;
private readonly account: string;
private series?: Promise<Series>;

constructor(props: Properties) {
super(props);
Expand All @@ -55,6 +59,7 @@ export default class Invest extends React.Component<Properties, State> {
leverage: BigNumber.from(300),
approvalState: ApprovalState.Loading,
slippage: SLIPPAGE_OPTIONS[1].value,
seriesInterest: {},
};
}

Expand Down Expand Up @@ -125,6 +130,21 @@ export default class Invest extends React.Component<Properties, State> {
valueType={ValueType.Usdc}
value={this.state.usdcBalance}
/>
<select
value={this.state.selectedSeriesId}
onSelect={(e) => this.setState({ selectedSeriesId: e.currentTarget.value })}
>{
this.props.seriesDefinitions.map(
(seriesInterface: SeriesDefinition) =>
(<option
key={seriesInterface.seriesId}
value={seriesInterface.seriesId}
disabled={Invest.isPastMaturity(this.props.seriesInfo[seriesInterface.seriesId])}
>
{this.state.seriesInterest[seriesInterface.seriesId] === undefined ? `${seriesInterface.seriesId}` : `${seriesInterface.seriesId} (${this.state.seriesInterest[seriesInterface.seriesId]}%)`}
</option>))
}
</select>
<label htmlFor="invest_amount">Amount to invest:</label>
<UsdcInput
max={this.state.usdcBalance}
Expand Down Expand Up @@ -171,10 +191,10 @@ export default class Invest extends React.Component<Properties, State> {
/>
</>
)}
{this.state.interest !== undefined ? (
{this.state.selectedSeriesId !== undefined && this.state.seriesInterest[this.state.selectedSeriesId] !== undefined ? (
<ValueDisplay
label="Yield interest:"
value={`${this.state.interest} % APY`}
value={`${this.state.seriesInterest[this.state.selectedSeriesId]} % APY`}
valueType={ValueType.Literal}
/>
) : null}
Expand Down Expand Up @@ -225,12 +245,27 @@ export default class Invest extends React.Component<Properties, State> {
}

private async checkApprovalState() {
let seriesId: string;
if (this.state.selectedSeriesId === undefined) {
const series = this.props.seriesDefinitions.find((ser) => !Invest.isPastMaturity(this.props.seriesInfo[ser.seriesId]));
if (series === undefined) {
return;
} else {
seriesId = series.seriesId;
this.setState({ selectedSeriesId: series.seriesId });
}
} else {
seriesId = this.state.selectedSeriesId;
}

void this.computeSeriesInterests();

// First, set to loading
this.setState({
approvalState: ApprovalState.Loading,
});

const series = await this.loadSeries();
const series = this.props.seriesInfo[seriesId];
const allowance: BigNumber = await this.contracts.usdcContract.allowance(
this.account,
this.contracts.yieldLeverContract.address
Expand Down Expand Up @@ -271,23 +306,36 @@ export default class Invest extends React.Component<Properties, State> {
approvalState: ApprovalState.Undercollateralized,
});
} else {
const interest = await this.computeInterest();
const toBorrow = this.totalToInvest().sub(this.state.usdcToInvest);
const interest = await this.computeInterest(seriesId, toBorrow);
if (allowance.lt(this.state.usdcToInvest)) {
this.setState({
fyTokens,
approvalState: ApprovalState.ApprovalRequired,
interest,
interest
});
} else {
this.setState({
fyTokens,
approvalState: ApprovalState.Transactable,
interest,
interest
});
}
}
}

private async computeSeriesInterests() {
for (let i = 0; i < this.props.seriesDefinitions.length; i++) {
const series = this.props.seriesDefinitions[i];
if (!Invest.isPastMaturity(this.props.seriesInfo[series.seriesId])) {
console.log(series.seriesId);
const toBorrow = BigNumber.from(100_000_000);
const interest = await this.computeInterest(series.seriesId, toBorrow);
this.setState({ seriesInterest: { ...this.state.seriesInterest, [series.seriesId]: interest } });
}
}
}

private collateralizationRatio(fyTokens: BigNumber): BigNumber {
return this.totalToInvest().div(fyTokens.div(1_000_000));
}
Expand All @@ -313,44 +361,42 @@ export default class Invest extends React.Component<Properties, State> {
* @returns
*/
private async fyTokens(): Promise<BigNumber> {
if (this.totalToInvest().eq(0)) return BigNumber.from(0);
if (this.totalToInvest().eq(0) || this.state.selectedSeriesId === undefined) return BigNumber.from(0);
const leverage = this.totalToInvest().sub(this.state.usdcToInvest);
const poolContract = this.props.contracts.poolContracts[this.state.selectedSeriesId];
return addSlippage(
await this.contracts.poolContract.buyBasePreview(leverage),
await poolContract.buyBasePreview(leverage),
this.state.slippage
);
}

private async transact() {
if (this.state.selectedSeriesId === undefined) {
return;
}
const leverage = this.totalToInvest().sub(this.state.usdcToInvest);
const maxFy = await this.fyTokens();
console.log(
this.state.usdcToInvest.toString(),
leverage.toString(),
maxFy.toString(),
SERIES_ID
this.state.selectedSeriesId
);
const tx = await this.contracts.yieldLeverContract.invest(
this.state.usdcToInvest,
leverage,
maxFy,
SERIES_ID
this.state.selectedSeriesId
);
await tx.wait();
}

private async loadSeries(): Promise<Series> {
if (this.series === undefined)
this.series = this.contracts.cauldronContract.series(SERIES_ID);
return this.series;
}

private async computeInterest(): Promise<number> {
const series = await this.loadSeries();
private async computeInterest(seriesId: string, toBorrow: BigNumber): Promise<number> {
const series = this.props.seriesInfo[seriesId];
const poolContract = this.props.contracts.poolContracts[seriesId];
const currentTime = Date.now() / 1000;
const maturityTime = series.maturity;
const toBorrow = this.totalToInvest().sub(this.state.usdcToInvest);
const fyTokens = await this.contracts.poolContract.buyBasePreview(toBorrow);
const fyTokens = await poolContract.buyBasePreview(toBorrow);
const year = 356.2425 * 24 * 60 * 60;
const result_in_period =
toBorrow.mul(1_000_000).div(fyTokens).toNumber() / 1_000_000;
Expand All @@ -360,4 +406,8 @@ export default class Invest extends React.Component<Properties, State> {
);
return Math.round(10000 * (1 - interest_per_year)) / 100;
}

private static isPastMaturity(seriesInfo: Series): boolean {
return seriesInfo.maturity <= Date.now() / 1000;
}
}
14 changes: 7 additions & 7 deletions frontend/src/components/Vault.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BigNumber, utils } from "ethers";
import React from "react";
import { Contracts, SERIES_ID } from "../App";
import { Contracts } from "../App";
import { Balance, Vault as VaultI } from "../objects/Vault";
import Slippage, { addSlippage, SLIPPAGE_OPTIONS } from "./Slippage";
import ValueDisplay, { ValueType } from "./ValueDisplay";
Expand Down Expand Up @@ -46,7 +46,7 @@ export default class Vault extends React.Component<Properties, State> {
/>
<ValueDisplay
label="Debt:"
valueType={ValueType.FyUsdc}
valueType={ValueType.Usdc}
value={this.props.balance.art}
/>
<Slippage
Expand Down Expand Up @@ -91,14 +91,14 @@ export default class Vault extends React.Component<Properties, State> {
try {
console.log(
`Expected FY:\t${utils.formatUnits(
await this.props.contracts.poolContract.buyFYTokenPreview(
await this.props.contracts.poolContracts[this.props.vault.seriesId].buyFYTokenPreview(
balance.art
),
6
)} USDC`
);
return addSlippage(
await this.props.contracts.poolContract.buyFYTokenPreview(balance.art),
await this.props.contracts.poolContracts[this.props.vault.seriesId].buyFYTokenPreview(balance.art),
this.state.slippage
);
} catch (e) {
Expand All @@ -111,7 +111,7 @@ export default class Vault extends React.Component<Properties, State> {

private async unwind() {
const [poolAddress, balances] = await Promise.all([
this.props.contracts.ladleContract.pools(SERIES_ID),
this.props.contracts.ladleContract.pools(this.props.vault.seriesId),
this.props.contracts.cauldronContract.balances(this.props.vaultId),
]);
// Sanity check
Expand All @@ -130,15 +130,15 @@ export default class Vault extends React.Component<Properties, State> {
poolAddress,
balances.ink,
balances.art,
SERIES_ID
this.props.vault.seriesId
);
const tx = await this.props.contracts.yieldLeverContract.unwind(
this.props.vaultId,
maxFy,
poolAddress,
balances.ink,
balances.art,
SERIES_ID
this.props.vault.seriesId
);
await tx.wait();
await Promise.all([this.props.pollData(), this.updateToBorrow()]);
Expand Down
Loading

0 comments on commit 1a2ad24

Please sign in to comment.