Skip to content

Commit

Permalink
feat: create comet with extended asset list
Browse files Browse the repository at this point in the history
  • Loading branch information
MishaShWoof committed Aug 9, 2024
1 parent 071dbbf commit bba7d99
Show file tree
Hide file tree
Showing 14 changed files with 1,748 additions and 11 deletions.
126 changes: 126 additions & 0 deletions contracts/AssetList.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;

import "./CometConfiguration.sol";
import "./IPriceFeed.sol";
import "./IERC20NonStandard.sol";
import "./CometMainInterface.sol";
import "./CometCore.sol";

/**
* @title Compound's Asset List
* @author Compound
*/
contract AssetList {
/// @dev The decimals required for a price feed
uint8 internal constant PRICE_FEED_DECIMALS = 8;

/// @dev The scale for factors
uint64 internal constant FACTOR_SCALE = 1e18;

/// @dev The max value for a collateral factor (1)
uint64 internal constant MAX_COLLATERAL_FACTOR = FACTOR_SCALE;

uint256[] internal assets_a;
uint256[] internal assets_b;

/// @notice The number of assets this contract actually supports
uint8 public immutable numAssets;

constructor(CometConfiguration.AssetConfig[] memory assetConfigs) {
numAssets = uint8(assetConfigs.length);
for (uint i = 0; i < assetConfigs.length; i++) {

Check notice

Code scanning / Semgrep OSS

Semgrep Finding: rules.solidity.performance.array-length-outside-loop Note

Caching the array length outside a loop saves reading it on each iteration, as long as the array's length is not changed during the loop.

Check notice

Code scanning / Semgrep OSS

Semgrep Finding: rules.solidity.performance.unnecessary-checked-arithmetic-in-loop Note

A lot of times there is no risk that the loop counter can overflow.
Using Solidity's unchecked block saves the overflow checks.

Check notice

Code scanning / Semgrep OSS

Semgrep Finding: rules.solidity.performance.use-prefix-increment-not-postfix Note

Consider using the prefix increment expression whenever the return value is not needed.
The prefix increment expression is cheaper in terms of gas.
(uint256 asset_a, uint256 asset_b) = getPackedAssetInternal(assetConfigs, i);
assets_a.push(asset_a);

Check notice

Code scanning / Semgrep OSS

Semgrep Finding: rules.solidity.performance.state-variable-read-in-a-loop Note

Replace state variable reads and writes within loops with local variable reads and writes.
assets_b.push(asset_b);

Check notice

Code scanning / Semgrep OSS

Semgrep Finding: rules.solidity.performance.state-variable-read-in-a-loop Note

Replace state variable reads and writes within loops with local variable reads and writes.
}
}

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: compound.solidity.missing-constructor-sanity-checks Warning

There're no sanity checks for the constructor argument assetConfigs.

Check notice

Code scanning / Semgrep OSS

Semgrep Finding: rules.solidity.performance.non-payable-constructor Note

Consider making costructor payable to save gas.

/**
* @dev Checks and gets the packed asset info for storage
*/
function getPackedAssetInternal(CometConfiguration.AssetConfig[] memory assetConfigs, uint i) internal view returns (uint256, uint256) {
CometConfiguration.AssetConfig memory assetConfig;
if (i < assetConfigs.length) {
assembly {

Check warning on line 45 in contracts/AssetList.sol

View workflow job for this annotation

GitHub Actions / Contract linter

Avoid to use inline assembly. It is acceptable only in rare cases
assetConfig := mload(add(add(assetConfigs, 0x20), mul(i, 0x20)))
}
} else {
return (0, 0);
}
address asset = assetConfig.asset;
address priceFeed = assetConfig.priceFeed;
uint8 decimals_ = assetConfig.decimals;

// Short-circuit if asset is nil
if (asset == address(0)) {
return (0, 0);
}

// Sanity check price feed and asset decimals
if (IPriceFeed(priceFeed).decimals() != PRICE_FEED_DECIMALS) revert CometMainInterface.BadDecimals();
if (IERC20NonStandard(asset).decimals() != decimals_) revert CometMainInterface.BadDecimals();

// Ensure collateral factors are within range
if (assetConfig.borrowCollateralFactor >= assetConfig.liquidateCollateralFactor) revert CometMainInterface.BorrowCFTooLarge();
if (assetConfig.liquidateCollateralFactor > MAX_COLLATERAL_FACTOR) revert CometMainInterface.LiquidateCFTooLarge();

unchecked {
// Keep 4 decimals for each factor
uint64 descale = FACTOR_SCALE / 1e4;
uint16 borrowCollateralFactor = uint16(assetConfig.borrowCollateralFactor / descale);
uint16 liquidateCollateralFactor = uint16(assetConfig.liquidateCollateralFactor / descale);
uint16 liquidationFactor = uint16(assetConfig.liquidationFactor / descale);

// Be nice and check descaled values are still within range
if (borrowCollateralFactor >= liquidateCollateralFactor) revert CometMainInterface.BorrowCFTooLarge();

// Keep whole units of asset for supply cap
uint64 supplyCap = uint64(assetConfig.supplyCap / (10 ** decimals_));

uint256 word_a = (uint160(asset) << 0 |
uint256(borrowCollateralFactor) << 160 |
uint256(liquidateCollateralFactor) << 176 |
uint256(liquidationFactor) << 192);
uint256 word_b = (uint160(priceFeed) << 0 |
uint256(decimals_) << 160 |
uint256(supplyCap) << 168);

return (word_a, word_b);
}
}

/**
* @notice Get the i-th asset info, according to the order they were passed in originally
* @param i The index of the asset info to get
* @return The asset info object
*/
function getAssetInfo(uint8 i) public view returns (CometCore.AssetInfo memory) {
if (i >= numAssets) revert CometMainInterface.BadAsset();

uint256 word_a = assets_a[i];
uint256 word_b = assets_b[i];

address asset = address(uint160(word_a & type(uint160).max));
uint64 rescale = FACTOR_SCALE / 1e4;
uint64 borrowCollateralFactor = uint64(((word_a >> 160) & type(uint16).max) * rescale);
uint64 liquidateCollateralFactor = uint64(((word_a >> 176) & type(uint16).max) * rescale);
uint64 liquidationFactor = uint64(((word_a >> 192) & type(uint16).max) * rescale);

address priceFeed = address(uint160(word_b & type(uint160).max));
uint8 decimals_ = uint8(((word_b >> 160) & type(uint8).max));
uint64 scale = uint64(10 ** decimals_);
uint128 supplyCap = uint128(((word_b >> 168) & type(uint64).max) * scale);

return CometCore.AssetInfo({
offset: i,
asset: asset,
priceFeed: priceFeed,
scale: scale,
borrowCollateralFactor: borrowCollateralFactor,
liquidateCollateralFactor: liquidateCollateralFactor,
liquidationFactor: liquidationFactor,
supplyCap: supplyCap
});
}
}
17 changes: 17 additions & 0 deletions contracts/AssetListFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.15;

import "./AssetList.sol";

/**
* @title Compound's Asset List Factory
* @author Compound
*/
contract AssetListFactory {
event AssetListCreated(address indexed assetList, CometCore.AssetConfig[] assetConfigs);

function createAssetList(CometCore.AssetConfig[] memory assetConfigs) external returns (address assetList) {
assetList = address(new AssetList(assetConfigs));
emit AssetListCreated(assetList, assetConfigs);
}
}
5 changes: 4 additions & 1 deletion contracts/CometExt.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@ contract CometExt is CometExtInterface {
/// @dev The ERC20 symbol for wrapped base token
bytes32 internal immutable symbol32;

address immutable public assetListFactory;

/**
* @notice Construct a new protocol instance
* @param config The mapping of initial/constant parameters
**/
constructor(ExtConfiguration memory config) {
constructor(ExtConfiguration memory config, address assetListFactoryAddress) {
name32 = config.name32;
symbol32 = config.symbol32;
assetListFactory = assetListFactoryAddress;
}

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: compound.solidity.missing-constructor-sanity-checks Warning

There're no sanity checks for the constructor argument assetListFactoryAddress.

Check warning

Code scanning / Semgrep OSS

Semgrep Finding: compound.solidity.missing-constructor-sanity-checks Warning

There're no sanity checks for the constructor argument config.

Check notice

Code scanning / Semgrep OSS

Semgrep Finding: rules.solidity.performance.non-payable-constructor Note

Consider making costructor payable to save gas.

/** External getters for internal constants **/
Expand Down
Loading

0 comments on commit bba7d99

Please sign in to comment.