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

adds apollox asset #169

Merged
merged 2 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions contracts/Assets/ApolloAsset.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;

// ====================================================================
// ========================== ApolloAsset.sol ===========================
// ====================================================================

/**
* @title Apollo Asset
* @dev Representation of an on-chain investment on ApolloX finance.
*/

import { Stabilizer } from "../Stabilizer/Stabilizer.sol";
import { IApolloX } from "./Interfaces/ApolloX/IApolloX.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";

contract ApolloAsset is Stabilizer {
error UnexpectedAmount();
error CooldownError();

// Variables
IApolloX private constant apollo = IApolloX(0x1b6F2d3844C6ae7D56ceb3C3643b9060ba28FEb0);
IERC20Metadata private constant alp = IERC20Metadata(0x4E47057f45adF24ba41375a175dA0357cB3480E5);
uint256 public investedAt;

// Events
event Invested(uint256 indexed usdxAmount);
event Divested(uint256 indexed usdxAmount);

constructor(
string memory _name,
address _sweep,
address _usdx,
address _oracleUsdx,
address _borrower
) Stabilizer(_name, _sweep, _usdx, _oracleUsdx, _borrower) {
alp.approve(address(apollo), type(uint256).max);
usdx.approve(address(apollo), type(uint256).max);
}

/* ========== Views ========== */

/**
* @notice Asset Value of investment.
* @return the Returns the value of the investment in the USD coin
*/
function assetValue() public view override returns (uint256) {
return _oracleUsdxToUsd(getDepositAmount());
}

function getDepositAmount() public view returns (uint256) {
return apollo.stakeOf(address(this)) * apollo.alpPrice() / 1e8;
}

/* ========== Actions ========== */

/**
* @notice Invest.
* @param usdxAmount Amount of usdx to be swapped for token.
* @param alpAmountOut Minimum amount out of ALP.
*/
function invest(uint256 usdxAmount, uint256 alpAmountOut)
external onlyBorrower whenNotPaused nonReentrant validAmount(usdxAmount)
{
_invest(usdxAmount, 0, alpAmountOut);
}

/**
* @notice Divest.
* @param alpAmount Amount to be divested.
* @param usdxMinOut Minimum amount out of usdx.
*/
function divest(uint256 alpAmount, uint256 usdxMinOut)
external onlyBorrower nonReentrant validAmount(alpAmount)
{
_divest(alpAmount, usdxMinOut);
}

/**
* @notice Liquidate
*/
function liquidate() external nonReentrant {
if(auctionAllowed) revert ActionNotAllowed();
apollo.unStake(apollo.stakeOf(address(this)));
_liquidate(_getToken(), getDebt());
}

function collect() external nonReentrant onlyBorrower {
apollo.claimAllReward();
}

/* ========== Internals ========== */

function _getToken() internal pure override returns (address) {
return address(alp);
}

function _invest(uint256 usdxAmount, uint256, uint256 minAlpOut)
internal override
{
investedAt = block.timestamp;

uint256 usdxBalance = usdx.balanceOf(address(this));
if (usdxBalance < usdxAmount) usdxAmount = usdxBalance;

apollo.mintAlp(address(usdx), usdxAmount, minAlpOut, true);

emit Invested(usdxAmount);
}

function _divest(uint256 alpAmount, uint256 usdxMinOut) internal override {
if(block.timestamp - investedAt < apollo.coolingDuration()) revert CooldownError();

uint256 alpBalance = apollo.stakeOf(address(this));
if(alpAmount > alpBalance) alpAmount = alpBalance;

apollo.unStake(alpAmount);
apollo.burnAlp(address(usdx), alpAmount, usdxMinOut, address(this));

emit Divested(usdxMinOut);
}

}
20 changes: 20 additions & 0 deletions contracts/Assets/Interfaces/ApolloX/IApolloX.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;

// import "@openzeppelin/contracts/interfaces/IERC4626.sol";

interface IApolloX { // } is IERC4626 {

function coolingDuration() external view returns (uint256);

function mintAlp(address tokenIn, uint256 amountIn, uint256 minAlp, bool stake) external;
function burnAlp(address tokenOut, uint256 alpAmount, uint256 minOut, address receiver) external;

function unStake(uint256 _amount) external;
function claimAllReward() external;

function alpPrice() external view returns (uint256);
function stakeOf(address _user) external view returns (uint256);

function pendingApx(address _account) external view returns (uint256);
}
1 change: 1 addition & 0 deletions hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const networks = {
url: alchemyLink,
// blockNumber: 20005467
},
chainId: network.id,
},
localhost: {
allowUnlimitedContractSize: true,
Expand Down
45 changes: 45 additions & 0 deletions scripts/deploy/assets/apollo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const { ethers } = require("hardhat");
const { tokens, network, wallets, protocols, chainlink } = require("../../../utils/constants");
const { ask } = require("../../../utils/helper_functions");

async function main() {
[deployer] = await ethers.getSigners();

const name = 'ApolloX Asset';
const sweep = tokens.sweep;
const usdc = tokens.usdc;
const oracleUsdc = chainlink.usdc_usd;
const borrower = wallets.borrower;

console.log("===========================================");
console.log("APOLLO ASSET DEPLOY");
console.log("===========================================");
console.log("Network:", network.name);
console.log("Deployer:", deployer.address);
console.log("===========================================");
console.log("Asset Name:", name);
console.log("SWEEP:", sweep);
console.log("USDC:", usdc);
console.log("USDC/USD Chainlink Oracle:", oracleUsdc);
console.log("Borrower:", borrower);
console.log("===========================================");
const answer = (await ask("continue? y/n: "));
if(answer !== 'y'){ process.exit(); }
console.log("Deploying...");

const Asset = await ethers.getContractFactory("ApolloAsset");
const asset = await Asset.deploy(
name,
sweep,
usdc,
oracleUsdc,
borrower,
);

console.log("Apollo Asset deployed to:", asset.address);
console.log(`\nnpx hardhat verify --network ${network.name} ${asset.address} "${name}" ${sweep} ${usdc} ${oracleUsdc} ${borrower}`)

}

main();

73 changes: 73 additions & 0 deletions test/assets/apollo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { wallets, tokens, chainlink, protocols } = require("../../utils/constants");
const { impersonate, sendEth, toBN, increaseTime } = require("../../utils/helper_functions");

contract("Apollo Asset", async function () {
before(async () => {
[borrower] = await ethers.getSigners();

depositAmount = toBN("7000", 18);
investAmount = toBN("7000", 18);
divestAmount = toBN("7000", 18);

Token = await ethers.getContractFactory("ERC20");
usdt = await Token.attach(tokens.usdt);

apollo = await ethers.getContractAt("IApolloX", protocols.apollo.apollo);
apx = await Token.attach(protocols.apollo.apx);

ApolloAsset = await ethers.getContractFactory("ApolloAsset");
apollo_asset = await ApolloAsset.deploy(
'Apollo Asset',
tokens.sweep,
tokens.usdt,
chainlink.usdt_usd,
borrower.address,
);

await sendEth(wallets.usdt_holder);
const usdHolder = await impersonate(wallets.usdt_holder);
await usdt.connect(usdHolder).transfer(apollo_asset.address, depositAmount);
});

describe("apollo asset functions", async function () {
it("invests into apollo correctly", async function () {
expect(await apollo_asset.assetValue()).to.equal(0);
expect(await usdt.balanceOf(apollo_asset.address)).to.equal(depositAmount);

const minAlpOut = investAmount.mul(toBN("98",6)).div(await apollo.alpPrice());
await apollo_asset.invest(investAmount, minAlpOut);

const assetValue = await apollo_asset.assetValue();
expect(assetValue).to.greaterThan(7000e6 * 0.98);

expect(await usdt.balanceOf(apollo_asset.address)).to.equal(0);
});

it("collects APX rewards", async function () {
await increaseTime(259200);

expect(await apx.balanceOf(apollo_asset.address)).to.equal(0);

await apollo_asset.collect();

const apxBalance = await apx.balanceOf(apollo_asset.address);
expect(apxBalance).to.be.greaterThan(0);
});

it("divests by reedeming the staked tokens and exiting the pool", async function () {
expect(await usdt.balanceOf(apollo_asset.address)).to.eq(0);
expect(await apollo_asset.currentValue()).to.equal(await apollo_asset.assetValue());

const minUsdtOut = toBN("6800", 18);
const alpAmount = toBN("99999", 18); // big int value
await apollo_asset.divest(alpAmount, minUsdtOut);

expect(await apollo_asset.currentValue()).to.be.greaterThan(await apollo_asset.assetValue());

const usdtValue = await usdt.balanceOf(apollo_asset.address);
expect(usdtValue).to.greaterThan(minUsdtOut);
});
});
});
8 changes: 8 additions & 0 deletions utils/networks/bsc.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ module.exports = {
observationCardinality: 480,
},

protocols: {
apollo: {
apollo: "0x1b6F2d3844C6ae7D56ceb3C3643b9060ba28FEb0",
apx: "0x78f5d389f5cdccfc41594abab4b0ed02f31398b3",
alp: "0x4E47057f45adF24ba41375a175dA0357cB3480E5",
}
},

deployments: {
balancer: '0xa884970F06Dda7BedD86829E14BeCa2c8fEd5220',
treasury: '0x7c9131d7E2bEdb29dA39503DD8Cf809739f047B3',
Expand Down
Loading