Skip to content

Commit

Permalink
Merge branch 'release/v4.6.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
TylerEther committed Apr 14, 2024
2 parents 5a46b3f + f96548e commit a244e5d
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 15 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## v4.6.0
### Accumulators
- Update CompoundV2SBAccumulator: Add support for CEther.
- Add VenusIsolatedSBAccumulator: An accumulator that tracks and accumulates Venus Isolated Pools total supply and borrow amounts.

## v4.5.0
### Accumulators
- Add CompoundV2SBAccumulator: An accumulator that tracks and accumulates Compound v2 total supply and borrow amounts.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Adrastia Core

[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
![7160 out of 7160 tests passing](https://img.shields.io/badge/tests-7160/7160%20passing-brightgreen.svg?style=flat-square)
![7261 out of 7261 tests passing](https://img.shields.io/badge/tests-7261/7261%20passing-brightgreen.svg?style=flat-square)
![test-coverage >99%](https://img.shields.io/badge/test%20coverage-%3E99%25-brightgreen.svg?style=flat-square)

Adrastia Core is a set of Solidity smart contracts for building EVM oracle solutions.
Expand Down
35 changes: 23 additions & 12 deletions contracts/accumulators/proto/compound/CompoundV2SBAccumulator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity =0.8.13;

import "../../LiquidityAccumulator.sol";
import "../../../libraries/SafeCastExt.sol";
import "../../../libraries/EtherAsTokenLibrary.sol";

interface IComptroller {
function allMarkets(uint256 index) external view returns (address);
Expand Down Expand Up @@ -104,25 +105,35 @@ contract CompoundV2SBAccumulator is LiquidityAccumulator {
);
if (success1) {
address cToken = abi.decode(data1, (address));
if (cToken == address(0)) {
// Skip past any empty markets (this should never happen, but just in case)
continue;
}

// Now get the underlying token
(bool success2, bytes memory data2) = cToken.staticcall(
abi.encodeWithSelector(ICToken.underlying.selector)
);
address token;
uint8 tokenDecimals;
if (success2) {
address token = abi.decode(data2, (address));
uint8 tokenDecimals = IERC20Metadata(token).decimals();

if (address(_tokenToCToken[token].cToken) != address(0)) {
revert DuplicateMarket(token, cToken);
}
// CErc20
token = abi.decode(data2, (address));
tokenDecimals = IERC20Metadata(token).decimals();
} else {
// CEther
token = EtherAsTokenLibrary.ETHER_AS_TOKEN;
tokenDecimals = 18;
}

_cTokens.push(cToken);
_tokenToCToken[token] = TokenInfo({cToken: ICToken(cToken), underlyingDecimals: tokenDecimals});
_cTokenToToken[cToken] = token;
++numTokens;
if (address(_tokenToCToken[token].cToken) != address(0)) {
revert DuplicateMarket(token, cToken);
}
// Note: cTokens like cEther don't have an underlying token. Such tokens will be ignored.

_cTokens.push(cToken);
_tokenToCToken[token] = TokenInfo({cToken: ICToken(cToken), underlyingDecimals: tokenDecimals});
_cTokenToToken[cToken] = token;
++numTokens;
} else {
// We've iterated through all markets
break;
Expand Down Expand Up @@ -170,7 +181,7 @@ contract CompoundV2SBAccumulator is LiquidityAccumulator {
function supplyForCToken(ICToken cToken) internal view virtual returns (uint256) {
uint256 cash = cToken.getCash();
uint256 totalReserves = cToken.totalReserves();
uint256 totalBorrows = cToken.totalBorrows();
uint256 totalBorrows = borrowsForCToken(cToken);

return (cash + totalBorrows) - totalReserves;
}
Expand Down
34 changes: 34 additions & 0 deletions contracts/accumulators/proto/venus/VenusIsolatedSBAccumulator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.13;

import "../compound/CompoundV2SBAccumulator.sol";

interface VToken {
function badDebt() external view returns (uint256);
}

contract VenusIsolatedSBAccumulator is CompoundV2SBAccumulator {
constructor(
IAveragingStrategy averagingStrategy_,
address comptroller_,
uint8 decimals_,
uint256 updateTheshold_,
uint256 minUpdateDelay_,
uint256 maxUpdateDelay_
)
CompoundV2SBAccumulator(
averagingStrategy_,
comptroller_,
decimals_,
updateTheshold_,
minUpdateDelay_,
maxUpdateDelay_
)
{}

function borrowsForCToken(ICToken cToken) internal view virtual override returns (uint256) {
uint256 totalBorrows = super.borrowsForCToken(cToken);

return totalBorrows + VToken(address(cToken)).badDebt();
}
}
6 changes: 6 additions & 0 deletions contracts/libraries/EtherAsTokenLibrary.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.5.0 <0.9.0;

library EtherAsTokenLibrary {
address internal constant ETHER_AS_TOKEN = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
}
30 changes: 30 additions & 0 deletions contracts/test/accumulators/VenusIsolatedSBAccumulatorStub.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.8.13;

import "../../accumulators/proto/venus/VenusIsolatedSBAccumulator.sol";

contract VenusIsolatedSBAccumulatorStub is VenusIsolatedSBAccumulator {
constructor(
IAveragingStrategy averagingStrategy_,
address comptroller_,
uint8 decimals_,
uint256 updateTheshold_,
uint256 minUpdateDelay_,
uint256 maxUpdateDelay_
)
VenusIsolatedSBAccumulator(
averagingStrategy_,
comptroller_,
decimals_,
updateTheshold_,
minUpdateDelay_,
maxUpdateDelay_
)
{}

function stubFetchLiquidity(
bytes memory data
) public view returns (uint112 tokenLiquidity, uint112 quoteTokenLiquidity) {
return fetchLiquidity(data);
}
}
9 changes: 9 additions & 0 deletions contracts/test/vendor/ionic/IonicCTokenStub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ contract IonicCTokenStub is IIonicCToken, ICToken {
uint256 public _totalBorrows;
uint256 public _totalReserves;
uint256 public _cash;
uint256 public _badDebt;

bool internal _isCEther;

Expand Down Expand Up @@ -38,6 +39,10 @@ contract IonicCTokenStub is IIonicCToken, ICToken {
_isCEther = isCEther_;
}

function stubSetBadDebt(uint256 badDebt_) external {
_badDebt = badDebt_;
}

function getTotalUnderlyingSupplied() external view override returns (uint256) {
return _totalUnderlyingSupplied;
}
Expand All @@ -61,4 +66,8 @@ contract IonicCTokenStub is IIonicCToken, ICToken {
function getCash() external view override returns (uint256) {
return _cash;
}

function badDebt() external view returns (uint256) {
return _badDebt;
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adrastia-oracle/adrastia-core",
"version": "4.5.0",
"version": "4.6.0",
"main": "index.js",
"author": "TRILEZ SOFTWARE INC.",
"license": "BUSL-1.1",
Expand Down
150 changes: 149 additions & 1 deletion test/liquidity-accumulator/supply-borrow-accumulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,21 @@ describe("CompoundV2SBAccumulator#refreshTokenMappings", function () {
await expect(accumulator.refreshTokenMappings()).to.be.revertedWith("DuplicateMarket");
});

it("Reverts if two different CEther tokens are listed", async function () {
const cEther1 = await cTokenFactory.deploy(WETH);
await cEther1.deployed();
const cEther2 = await cTokenFactory.deploy(WETH);
await cEther2.deployed();

await cEther1.stubSetIsCEther(true);
await cEther2.stubSetIsCEther(true);

await poolStub["stubAddMarket(address)"](cEther1.address);
await poolStub["stubAddMarket(address)"](cEther2.address);

await expect(accumulator.refreshTokenMappings()).to.be.revertedWith("DuplicateMarket");
});

it("Discovers one market - WETH", async function () {
const cToken = await cTokenFactory.deploy(WETH);
await cToken.deployed();
Expand Down Expand Up @@ -403,7 +418,7 @@ describe("CompoundV2SBAccumulator#refreshTokenMappings", function () {
expect(receipt.events.length).to.equal(1);
});

it("CTokens without underlying tokens are ignored", async function () {
it("CTokens without underlying tokens are treated as CEther", async function () {
const cToken = await cTokenFactory.deploy(WETH);
await cToken.deployed();

Expand All @@ -413,6 +428,19 @@ describe("CompoundV2SBAccumulator#refreshTokenMappings", function () {
const refreshTx = await accumulator.refreshTokenMappings();
const receipt = await refreshTx.wait();

expect(refreshTx).to.emit(accumulator, "TokenMappingsRefreshed").withArgs(1, 0);
expect(refreshTx).to.emit(accumulator, "CTokenAdded").withArgs(cToken.address);
expect(receipt.events.length).to.equal(2);
});

it("Skips markets whose cToken is address(0)", async function () {
const cToken = await cTokenFactory.deploy(WETH);
await cToken.deployed();

await poolStub["stubAddMarket(address)"](ethers.constants.AddressZero);
const refreshTx = await accumulator.refreshTokenMappings();
const receipt = await refreshTx.wait();

expect(refreshTx).to.emit(accumulator, "TokenMappingsRefreshed").withArgs(0, 0);
expect(receipt.events.length).to.equal(1);
});
Expand Down Expand Up @@ -487,10 +515,86 @@ function createDescribeCompoundV2FetchLiquidityTests(typicalSupplyCalculation) {
};
}

function createDescribeSpecificVenusIsolatedFetchLiquidityTests() {
return function describeVenusIsolatedFetchLiquidityTests(contractName, deployContracts) {
describe("Venus isolated special cases", function () {
var poolStub;
var accumulator;
var decimals;
var cTokenFactory;

beforeEach(async function () {
const contracts = await deployContracts(WETH, (decimals = DEFAULT_DECIMALS));
poolStub = contracts.poolStub;
accumulator = contracts.accumulator;
cTokenFactory = await ethers.getContractFactory("IonicCTokenStub");
});

const n = 1000;

it("Fuzz testing with " + n + " iterations", async function () {
const token = WETH;
const updateData = ethers.utils.hexZeroPad(token, 32);

const tokenContract = await ethers.getContractAt("IERC20Metadata", token);
const tokenDecimals = await tokenContract.decimals();

for (let i = 0; i < n; i++) {
// Generate a random number of supply, borrow, reserves, and bad debt
const cash = getRandomBigNumber(64);
const borrow = getRandomBigNumber(64);
const reserves = getRandomBigNumber(64);
const badDebt = getRandomBigNumber(64);

const totalSupply = cash.add(borrow).add(badDebt).sub(reserves);
const totalBorrow = borrow.add(badDebt);

if (totalSupply.lt(0)) {
// Invalid total supply. Retry.
--i;

continue;
}

const rawCash = ethers.utils.parseUnits(cash.toString(), tokenDecimals - decimals);
const rawBorrow = ethers.utils.parseUnits(borrow.toString(), tokenDecimals - decimals);
const rawReserves = ethers.utils.parseUnits(reserves.toString(), tokenDecimals - decimals);
const rawBadDebt = ethers.utils.parseUnits(badDebt.toString(), tokenDecimals - decimals);

// Get the cToken
const cTokenAddress = await poolStub.cTokensByUnderlying(token);
const cToken = await cTokenFactory.attach(cTokenAddress);

await cToken.stubSetCash(rawCash);
await cToken.stubSetTotalBorrows(rawBorrow);
await cToken.stubSetTotalReserves(rawReserves);
await cToken.stubSetBadDebt(rawBadDebt);

const result = await accumulator.stubFetchLiquidity(updateData);

// Allow for a 0.00001% difference
expect(result.tokenLiquidity).to.be.closeTo(totalBorrow, totalBorrow.div(10000000));
expect(result.quoteTokenLiquidity).to.be.closeTo(totalSupply, totalSupply.div(10000000));
}
});
});
};
}

function createDescribeIonicFetchLiquidityTests() {
return createDescribeCompoundV2FetchLiquidityTests(false);
}

function createDescribeVenusIsolatedFetchLiquidityTests() {
const stdDescribe = createDescribeCompoundV2FetchLiquidityTests(true);
const venusIsolatedDescribe = createDescribeSpecificVenusIsolatedFetchLiquidityTests();

return (contractName, deployContracts) => {
stdDescribe(contractName, deployContracts);
venusIsolatedDescribe(contractName, deployContracts);
};
}

function describeSBAccumulatorTests(
contractName,
deployContracts,
Expand Down Expand Up @@ -798,6 +902,40 @@ async function deployIonicContracts(baseToken, decimals) {
};
}

async function deployVenusIsolatedContracts(baseToken, decimals) {
const poolStubFactory = await ethers.getContractFactory("IonicStub");
const averagingStrategyFactory = await ethers.getContractFactory("ArithmeticAveraging");
const accumulatorFactory = await ethers.getContractFactory("VenusIsolatedSBAccumulatorStub");
const cTokenFactory = await ethers.getContractFactory("IonicCTokenStub");

const poolStub = await poolStubFactory.deploy();
await poolStub.deployed();

const averagingStrategy = await averagingStrategyFactory.deploy();
await averagingStrategy.deployed();

const accumulator = await accumulatorFactory.deploy(
averagingStrategy.address,
poolStub.address,
decimals,
DEFAULT_UPDATE_THRESHOLD,
DEFAULT_UPDATE_DELAY,
DEFAULT_HEARTBEAT
);

const cToken = await cTokenFactory.deploy(baseToken);
await cToken.deployed();

await poolStub.stubSetCToken(baseToken, cToken.address);

await accumulator.refreshTokenMappings();

return {
poolStub: poolStub,
accumulator: accumulator,
};
}

async function deployCompoundV2Contracts(baseToken, decimals) {
const poolStubFactory = await ethers.getContractFactory("IonicStub");
const averagingStrategyFactory = await ethers.getContractFactory("ArithmeticAveraging");
Expand Down Expand Up @@ -977,3 +1115,13 @@ describeSBAccumulatorTests(
false,
createDescribeCompoundV2FetchLiquidityTests(true)
);
describeSBAccumulatorTests(
"VenusIsolatedSBAccumulator",
deployVenusIsolatedContracts,
ionicSetSupply,
ionicSetBorrow,
[_WETH, _USDC],
true,
false,
createDescribeVenusIsolatedFetchLiquidityTests()
);

0 comments on commit a244e5d

Please sign in to comment.