Skip to content

Commit

Permalink
poc: AUM fees oracle + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
robercano committed Aug 1, 2023
1 parent d8d0be5 commit 59e47af
Show file tree
Hide file tree
Showing 30 changed files with 2,564 additions and 1 deletion.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@
"@domain/adjust-position": "./packages/domain/src/adjust-position.ts",
"@domain/flashloans": "./packages/domain/src/flashloans.ts",
"@domain/position": "./packages/domain/src/position.ts",
"@domain/risk-ratio": "./packages/domain/src/risk-ratio.ts"
"@domain/risk-ratio": "./packages/domain/src/risk-ratio.ts",
"@aum-oracle/typechain-types": "./packages/aum-oracle/typechain-types",
"@aum-oracle/scripts": "./packages/aum-oracle/scripts"
},
"resolutions": {
"hardhat/**/@ethereumjs/tx": "3.5.0"
Expand Down
8 changes: 8 additions & 0 deletions packages/aum-oracle/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ETHERSCAN_API_KEY=
ALCHEMY_NODE=
ALCHEMY_NODE_GOERLI=
TENDERLY_USERNAME=
TENDERLY_PROJECT=
PRIVATE_KEY =
PLOTY_KEY=
PLOTY_USER=
5 changes: 5 additions & 0 deletions packages/aum-oracle/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
.eslintrc.js
contracts/**/*.ts
typechain-types
cache
8 changes: 8 additions & 0 deletions packages/aum-oracle/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
extends: "../../.eslintrc.js",
parserOptions: {
project: "tsconfig.json",
tsconfigRootDir: __dirname,
sourceType: "module",
},
};
24 changes: 24 additions & 0 deletions packages/aum-oracle/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
node_modules
.env
coverage
coverage.json
typechain
typechain-types

# Hardhat files
cache
artifacts


node_modules
.env
coverage
coverage.json
typechain
typechain-types

# Hardhat files
cache
artifacts

docs
23 changes: 23 additions & 0 deletions packages/aum-oracle/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"printWidth": 120,
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"arrowParens": "avoid",
"proseWrap": "always",
"overrides": [
{
"files": "*.sol",
"options": {
"printWidth": 120,
"tabWidth": 4,
"compiler": "0.8.18",
"useTabs": false,
"singleQuote": false,
"bracketSpacing": true,
"plugins": ["prettier-plugin-solidity"]
}
},
{ "files": "*.json", "options": { "tabWidth": 2 } }
]
}
7 changes: 7 additions & 0 deletions packages/aum-oracle/.solhint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "solhint:recommended",
"rules": {
"compiler-version": ["off"],
"func-visibility": ["warn", { "ignoreConstructors": true }]
}
}
5 changes: 5 additions & 0 deletions packages/aum-oracle/.solhintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
contracts/ajna
contracts/chainlink
contracts/tokens
contracts/libs
3 changes: 3 additions & 0 deletions packages/aum-oracle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @oasisdex/aum-oracle

PoC of the AUM aggregated price oracle
4 changes: 4 additions & 0 deletions packages/aum-oracle/bootstrap-env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import * as dotenv from "dotenv";
import * as path from "path";

dotenv.config({ path: path.join(__dirname, "../../.env") });
240 changes: 240 additions & 0 deletions packages/aum-oracle/contracts/AggregatedPriceOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.15;

import "./interfaces/IAggregatedPriceOracle.sol";
import { Ownable} from "@openzeppelin/contracts/access/Ownable.sol";


/**
* @title AggregatedPriceOracle
* @notice Keeps track of aggregated prices for a period of time. Aggregated means the summatory of all daily prices
* for the given period of time. This can be used to calculate for example a daily fee over a period of time.
*/
contract AggregatedPriceOracle is Ownable, IAggregatedPriceOracle {
/// IMMUTABLES
address public immutable token; // Zero address indicates ETH
uint256 public immutable initialTimestamp;

/// CONSTANTS

// The grouping does not follow natural years or months, but rather a fixed number of days/weeks/months/years
// so we can optimize the aggregation of the prices
uint256 public constant SECONDS_IN_DAY = 86400;
uint256 public constant DAYS_IN_WEEK = 7;
uint256 public constant WEEKS_IN_MONTH = 4;
uint256 public constant DAYS_IN_MONTH = DAYS_IN_WEEK * WEEKS_IN_MONTH;
uint256 public constant MONTHS_IN_YEAR = 12;
uint256 public constant DAYS_IN_YEAR = DAYS_IN_MONTH * MONTHS_IN_YEAR;

/// STATE VARIABLES
uint256 public nextDailyPriceIndex; // = 0
int256[7] public dailyPrice;

uint256 public nextWeeklyPriceIndex; // = 0;
int256[4] public weeklyPriceAggregated;

uint256 public nextMonthlyPriceIndex; // = 0;
int256[12] public monthlyPriceAggregated;

int256[] public yearlyPriceAggregated;

/// CONSTRUCTOR
constructor(address token_, int256 initialPrice_) {
token = token_;
initialTimestamp = block.timestamp;

_setDailyPrice(initialPrice_);
}

/// STATE CHANGING FUNCTIONS

/**
* @dev Implementation assumes price is set every day and only once. Edge cases are not
* covered as this is just a PoC to estimate gas costs.
*
* @dev There is no protection against setting the same price twice in a row. There is also no control
* on the timestamp where the price is set, meaning that if it's set twice in a row the contract
* assumes the latest price is for the next day
*/
function setDailyPrice(int256 price) external onlyOwner {
_setDailyPrice(price);

// Weekly price is updated every 7 days
if (nextDailyPriceIndex == 0) {
_updateWeeklyPrice();
}
}

/// VIEW FUNCTIONS

/**
@dev This function is not marked as view to temporarily get gas usage from it in the tests
*/
function getAggregatedPrice(uint256 fromTimestamp) external returns (int256) {
uint256 toTimestamp = block.timestamp;

if (fromTimestamp > toTimestamp) {
revert StartTSAfterEndTS(fromTimestamp, toTimestamp);
}
if (fromTimestamp < initialTimestamp) {
revert StartTSBeforeInitialTS(fromTimestamp, initialTimestamp);
}

uint256 fromDayIndex = (fromTimestamp - initialTimestamp) / SECONDS_IN_DAY;
uint256 toDayIndex = (toTimestamp - initialTimestamp) / SECONDS_IN_DAY;

int256 yearlyAggregatedPrice = _getAggregatedYearlyPrice(fromDayIndex, toDayIndex);
int256 monthlyAggregatedPrice = _getAggregatedMonthlyPrice(fromDayIndex, toDayIndex);
int256 weeklyAggregatedPrice = _getAggregatedWeeklyPrice(fromDayIndex, toDayIndex);
int256 dailyAggregatedPrice = _getAggregatedDailyPrice(fromDayIndex, toDayIndex);

return
yearlyAggregatedPrice + monthlyAggregatedPrice + weeklyAggregatedPrice + dailyAggregatedPrice;
}

function getYearlyPriceAggregatedLength() external view returns (uint256) {
return yearlyPriceAggregated.length;
}

/// INTERNALS
function _getAggregatedYearlyPrice(
uint256 fromDayIndex,
uint256 nowDayIndex
) internal view returns (int256) {
// Using 1 years here because it doesn't matter if it's a leap year or not,
// just want the aggregation in blocks of 365 days
uint256 yearStartIndex = fromDayIndex / DAYS_IN_YEAR;
uint256 yearEndIndex = nowDayIndex / DAYS_IN_YEAR;

if (yearStartIndex >= yearlyPriceAggregated.length) {
return 0;
}

if (yearEndIndex >= yearlyPriceAggregated.length) {
yearEndIndex = yearlyPriceAggregated.length - 1;
}

int256 aggregatedPrice = 0;
for (uint256 i = yearStartIndex; i <= yearEndIndex; i++) {
aggregatedPrice += yearlyPriceAggregated[i];
}
return aggregatedPrice;
}

function _getAggregatedMonthlyPrice(
uint256 fromDayIndex,
uint256 nowDayIndex
) internal view returns (int256) {
uint256 monthStartIndex = fromDayIndex / DAYS_IN_MONTH;
uint256 monthNowIndex = nowDayIndex / DAYS_IN_MONTH;

monthStartIndex = _calculateAdjustedIndex(
monthStartIndex,
monthNowIndex,
monthlyPriceAggregated.length,
MONTHS_IN_YEAR
);

int256 aggregatedPrice = 0;
for (uint256 i = monthStartIndex; i < nextMonthlyPriceIndex; i++) {
aggregatedPrice += monthlyPriceAggregated[i];
}
return aggregatedPrice;
}

function _getAggregatedWeeklyPrice(
uint256 fromDayIndex,
uint256 nowDayIndex
) internal view returns (int256) {
uint256 weeklyStartIndex = fromDayIndex / DAYS_IN_WEEK;
uint256 weeklyNowIndex = nowDayIndex / DAYS_IN_WEEK;

weeklyStartIndex = _calculateAdjustedIndex(
weeklyStartIndex,
weeklyNowIndex,
weeklyPriceAggregated.length,
WEEKS_IN_MONTH
);

int256 aggregatedPrice = 0;
for (uint256 i = weeklyStartIndex; i < nextWeeklyPriceIndex; i++) {
aggregatedPrice += weeklyPriceAggregated[i];
}
return aggregatedPrice;
}

function _getAggregatedDailyPrice(
uint256 fromDayIndex,
uint256 nowDayIndex
) internal view returns (int256) {
fromDayIndex = _calculateAdjustedIndex(
fromDayIndex,
nowDayIndex,
dailyPrice.length,
DAYS_IN_WEEK
);

int256 aggregatedPrice = 0;
for (uint256 i = fromDayIndex; i < nextDailyPriceIndex; i++) {
aggregatedPrice += dailyPrice[i];
}
return aggregatedPrice;
}

function _calculateAdjustedIndex(
uint256 startIndex,
uint256 endIndex,
uint256 currentDataLength,
uint256 maxDataLength
) internal pure returns (uint256) {
if (endIndex - startIndex + 1 > currentDataLength) {
return 0;
} else {
return startIndex % maxDataLength;
}
}

function _setDailyPrice(int256 price) internal {
dailyPrice[nextDailyPriceIndex] = price;
nextDailyPriceIndex = (nextDailyPriceIndex + 1) % DAYS_IN_WEEK;
}

function _updateWeeklyPrice() internal {
int256 sum = 0;
for (uint256 i = 0; i < dailyPrice.length; i++) {
sum += dailyPrice[i];
}

weeklyPriceAggregated[nextWeeklyPriceIndex] = sum;
nextWeeklyPriceIndex = (nextWeeklyPriceIndex + 1) % WEEKS_IN_MONTH;

// Monthly price is updated every 4 weeks
if (nextWeeklyPriceIndex == 0) {
_updateMonthlyPrice();
}
}

function _updateMonthlyPrice() internal {
int256 sum = 0;
for (uint256 i = 0; i < weeklyPriceAggregated.length; i++) {
sum += weeklyPriceAggregated[i];
}

monthlyPriceAggregated[nextMonthlyPriceIndex] = sum;
nextMonthlyPriceIndex = (nextMonthlyPriceIndex + 1) % MONTHS_IN_YEAR;

// Yearly price is updated every 12 months
if (nextMonthlyPriceIndex == 0) {
_updateYearlyPrice();
}
}

function _updateYearlyPrice() internal {
int256 sum = 0;
for (uint256 i = 0; i < monthlyPriceAggregated.length; i++) {
sum += monthlyPriceAggregated[i];
}

yearlyPriceAggregated.push(sum);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.15;

/**
The aggregated price oracle allows ...
*/
interface IAggregatedPriceOracle {
// View
function token() external view returns (address);

function initialTimestamp() external view returns (uint256);

function getAggregatedPrice(uint256 fromTimestamp) external returns (int256);

// State change
function setDailyPrice(int256 price) external;

// Errors
error StartTSAfterEndTS(uint256 startDate, uint256 endDate);
error StartTSBeforeInitialTS(uint256 startDate, uint256 initialTimestamp);
}
Loading

0 comments on commit 59e47af

Please sign in to comment.