/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
import { IAssetModule } from "../../interfaces/IAssetModule.sol";
import { Owned } from "../../../lib/solmate/src/auth/Owned.sol";
/**
* @title Abstract Asset Module
* @author Pragma Labs
* @notice Abstract contract with the minimal implementation of an Asset Module.
* @dev Each different asset class should have its own Oracle Module.
* The Asset Modules will:
* - Implement the pricing logic to calculate the USD value (with 18 decimals precision).
* - Process Deposits and Withdrawals.
* - Manage the risk parameters.
*/
abstract contract AssetModule is Owned, IAssetModule {
/* //////////////////////////////////////////////////////////////
CONSTANTS
////////////////////////////////////////////////////////////// */
// Identifier for the type of the asset.
uint256 public immutable ASSET_TYPE;
// The contract address of the Registry.
address public immutable REGISTRY;
/* //////////////////////////////////////////////////////////////
STORAGE
////////////////////////////////////////////////////////////// */
// Map asset => flag.
mapping(address => bool) public inAssetModule;
/* //////////////////////////////////////////////////////////////
ERRORS
////////////////////////////////////////////////////////////// */
error ExposureNotInLimits();
error OnlyRegistry();
error Overflow();
/* //////////////////////////////////////////////////////////////
MODIFIERS
////////////////////////////////////////////////////////////// */
/**
* @dev Only the Registry can call functions with this modifier.
*/
modifier onlyRegistry() {
if (msg.sender != REGISTRY) revert OnlyRegistry();
_;
}
/* //////////////////////////////////////////////////////////////
CONSTRUCTOR
////////////////////////////////////////////////////////////// */
/**
* @param registry_ The contract address of the Registry.
* @param assetType_ Identifier for the token standard of the asset.
* 0 = Unknown asset.
* 1 = ERC20.
* 2 = ERC721.
* 3 = ERC1155.
*/
constructor(address registry_, uint256 assetType_) Owned(msg.sender) {
REGISTRY = registry_;
ASSET_TYPE = assetType_;
}
/*///////////////////////////////////////////////////////////////
ASSET INFORMATION
///////////////////////////////////////////////////////////////*/
/**
* @notice Checks for a token address and the corresponding id if it is allowed.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @return A boolean, indicating if the asset is allowed.
* @dev For assets without id (ERC20, ERC4626...), the id should be set to 0.
*/
function isAllowed(address asset, uint256 assetId) public view virtual returns (bool);
/**
* @notice Returns the unique identifier of an asset based on the contract address and id.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @return key The unique identifier.
* @dev Unsafe bitshift from uint256 to uint96, use only when the ids of the assets cannot exceed type(uint96).max.
* For asset where the id can be bigger than a uint96, use a mapping of asset and assetId to storage.
* These assets can however NOT be used as underlying assets (processIndirectDeposit() must revert).
*/
function _getKeyFromAsset(address asset, uint256 assetId) internal view virtual returns (bytes32 key) {
assembly {
// Shift the assetId to the left by 20 bytes (160 bits).
// Then OR the result with the address.
key := or(shl(160, assetId), asset)
}
}
/**
* @notice Returns the contract address and id of an asset based on the unique identifier.
* @param key The unique identifier.
* @return asset The contract address of the asset.
* @return assetId The id of the asset.
*/
function _getAssetFromKey(bytes32 key) internal view virtual returns (address asset, uint256 assetId) {
assembly {
// Shift to the right by 20 bytes (160 bits) to extract the uint96 assetId.
assetId := shr(160, key)
// Use bitmask to extract the address from the rightmost 160 bits.
asset := and(key, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
}
}
/*///////////////////////////////////////////////////////////////
PRICING LOGIC
///////////////////////////////////////////////////////////////*/
/**
* @notice Returns the usd value of an asset.
* @param creditor The contract address of the Creditor.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @param assetAmount The amount of assets.
* @return valueInUsd The value of the asset denominated in USD, with 18 Decimals precision.
* @return collateralFactor The collateral factor of the asset for a given Creditor, with 4 decimals precision.
* @return liquidationFactor The liquidation factor of the asset for a given Creditor, with 4 decimals precision.
*/
function getValue(address creditor, address asset, uint256 assetId, uint256 assetAmount)
public
view
virtual
returns (uint256, uint256, uint256);
/*///////////////////////////////////////////////////////////////
RISK VARIABLES MANAGEMENT
///////////////////////////////////////////////////////////////*/
/**
* @notice Returns the risk factors of an asset for a Creditor.
* @param creditor The contract address of the Creditor.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @return collateralFactor The collateral factor of the asset for the Creditor, 4 decimals precision.
* @return liquidationFactor The liquidation factor of the asset for the Creditor, 4 decimals precision.
*/
function getRiskFactors(address creditor, address asset, uint256 assetId)
external
view
virtual
returns (uint16 collateralFactor, uint16 liquidationFactor);
/*///////////////////////////////////////////////////////////////
WITHDRAWALS AND DEPOSITS
///////////////////////////////////////////////////////////////*/
/**
* @notice Increases the exposure to an asset on a direct deposit.
* @param creditor The contract address of the Creditor.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @param amount The amount of tokens.
* @return recursiveCalls The number of calls done to different asset modules to process the deposit/withdrawal of the asset.
*/
function processDirectDeposit(address creditor, address asset, uint256 assetId, uint256 amount)
public
virtual
returns (uint256 recursiveCalls);
/**
* @notice Increases the exposure to an asset on an indirect deposit.
* @param creditor The contract address of the Creditor.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @param exposureUpperAssetToAsset The amount of exposure of the upper asset to the asset of this Asset Module.
* @param deltaExposureUpperAssetToAsset The increase or decrease in exposure of the upper asset to the asset of this Asset Module since last interaction.
* @return recursiveCalls The number of calls done to different asset modules to process the deposit/withdrawal of the asset.
* @return usdExposureUpperAssetToAsset The USD value of the exposure of the upper asset to the asset of this Asset Module, 18 decimals precision.
*/
function processIndirectDeposit(
address creditor,
address asset,
uint256 assetId,
uint256 exposureUpperAssetToAsset,
int256 deltaExposureUpperAssetToAsset
) public virtual returns (uint256 recursiveCalls, uint256 usdExposureUpperAssetToAsset);
/**
* @notice Decreases the exposure to an asset on a direct withdrawal.
* @param creditor The contract address of the Creditor.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @param amount The amount of tokens.
*/
function processDirectWithdrawal(address creditor, address asset, uint256 assetId, uint256 amount) public virtual;
/**
* @notice Decreases the exposure to an asset on an indirect withdrawal.
* @param creditor The contract address of the creditor.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @param exposureUpperAssetToAsset The amount of exposure of the upper asset to the asset of this Asset Module.
* @param deltaExposureUpperAssetToAsset The increase or decrease in exposure of the upper asset to the asset of this Asset Module since last interaction.
* @return usdExposureUpperAssetToAsset The USD value of the exposure of the upper asset to the asset of this Asset Module, 18 decimals precision.
*/
function processIndirectWithdrawal(
address creditor,
address asset,
uint256 assetId,
uint256 exposureUpperAssetToAsset,
int256 deltaExposureUpperAssetToAsset
) public virtual returns (uint256 usdExposureUpperAssetToAsset);
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
import { AssetModule } from "./AbstractAM.sol";
import { FixedPointMathLib } from "../../../lib/solmate/src/utils/FixedPointMathLib.sol";
import { SafeCastLib } from "../../../lib/solmate/src/utils/SafeCastLib.sol";
import { IRegistry } from "../interfaces/IRegistry.sol";
import { AssetValuationLib, AssetValueAndRiskFactors } from "../../libraries/AssetValuationLib.sol";
/**
* @title Derived Asset Module
* @author Pragma Labs
* @notice Abstract contract with the minimal implementation of a Derived Asset Module.
* @dev Derived Assets are assets with underlying assets, the underlying assets can be Primary Assets or also Derived Assets.
* For Derived Assets there are no direct external oracles.
* USD values of assets must be calculated in a recursive manner via the pricing logic of the Underlying Assets.
*/
abstract contract DerivedAM is AssetModule {
using FixedPointMathLib for uint256;
/* //////////////////////////////////////////////////////////////
STORAGE
////////////////////////////////////////////////////////////// */
// Map with the risk parameters of the protocol for each Creditor.
mapping(address creditor => RiskParameters riskParameters) public riskParams;
// Map with the last exposures of each asset for each Creditor.
mapping(address creditor => mapping(bytes32 assetKey => ExposuresPerAsset)) public lastExposuresAsset;
// Map with the last amount of exposure of each underlying asset for each asset for each Creditor.
mapping(address creditor => mapping(bytes32 assetKey => mapping(bytes32 underlyingAssetKey => uint256 exposure)))
public lastExposureAssetToUnderlyingAsset;
// Struct with the risk parameters of the protocol for a specific Creditor.
struct RiskParameters {
// The exposure in USD of the Creditor to the protocol at the last interaction, 18 decimals precision.
uint112 lastUsdExposureProtocol;
// The maximum exposure in USD of the Creditor to the protocol, 18 decimals precision.
uint112 maxUsdExposureProtocol;
// The risk factor of the protocol for a Creditor, 4 decimals precision.
uint16 riskFactor;
}
// Struct with the exposures of a specific asset for a specific Creditor.
struct ExposuresPerAsset {
// The amount of exposure of the Creditor to the asset at the last interaction.
uint112 lastExposureAsset;
// The exposure in USD of the Creditor to the asset at the last interaction, 18 decimals precision.
uint112 lastUsdExposureAsset;
}
/* //////////////////////////////////////////////////////////////
ERRORS
////////////////////////////////////////////////////////////// */
error RiskFactorNotInLimits();
/* //////////////////////////////////////////////////////////////
CONSTRUCTOR
////////////////////////////////////////////////////////////// */
/**
* @param registry_ The contract address of the Registry.
* @param assetType_ Identifier for the token standard of the asset.
* 0 = Unknown asset.
* 1 = ERC20.
* 2 = ERC721.
* 3 = ERC1155.
* ...
*/
constructor(address registry_, uint256 assetType_) AssetModule(registry_, assetType_) { }
/*///////////////////////////////////////////////////////////////
ASSET INFORMATION
///////////////////////////////////////////////////////////////*/
/**
* @notice Returns the unique identifiers of the underlying assets.
* @param assetKey The unique identifier of the asset.
* @return underlyingAssetKeys The unique identifiers of the underlying assets.
*/
function _getUnderlyingAssets(bytes32 assetKey)
internal
view
virtual
returns (bytes32[] memory underlyingAssetKeys);
/**
* @notice Calculates the USD rate of 10**18 underlying assets.
* @param creditor The contract address of the Creditor.
* @param underlyingAssetKeys The unique identifiers of the underlying assets.
* @return rateUnderlyingAssetsToUsd The USD rates of 10**18 tokens of underlying asset, with 18 decimals precision.
* @dev The USD price per 10**18 tokens is used (instead of the USD price per token) to guarantee sufficient precision.
*/
function _getRateUnderlyingAssetsToUsd(address creditor, bytes32[] memory underlyingAssetKeys)
internal
view
virtual
returns (AssetValueAndRiskFactors[] memory rateUnderlyingAssetsToUsd)
{
uint256 length = underlyingAssetKeys.length;
address[] memory underlyingAssets = new address[](length);
uint256[] memory underlyingAssetIds = new uint256[](length);
uint256[] memory amounts = new uint256[](length);
for (uint256 i; i < length; ++i) {
(underlyingAssets[i], underlyingAssetIds[i]) = _getAssetFromKey(underlyingAssetKeys[i]);
// We use the USD price per 10**18 tokens instead of the USD price per token to guarantee
// sufficient precision.
amounts[i] = 1e18;
}
rateUnderlyingAssetsToUsd =
IRegistry(REGISTRY).getValuesInUsdRecursive(creditor, underlyingAssets, underlyingAssetIds, amounts);
}
/**
* @notice Calculates for a given amount of an Asset the corresponding amount(s) of Underlying Asset(s).
* @param creditor The contract address of the Creditor.
* @param assetKey The unique identifier of the asset.
* @param assetAmount The amount of the asset, in the decimal precision of the Asset.
* @param underlyingAssetKeys The unique identifiers of the underlying assets.
* @return underlyingAssetsAmounts The corresponding amount(s) of Underlying Asset(s), in the decimal precision of the Underlying Asset.
* @return rateUnderlyingAssetsToUsd The USD rates of 10**18 tokens of underlying asset, with 18 decimals precision.
* @dev The USD price per 10**18 tokens is used (instead of the USD price per token) to guarantee sufficient precision.
*/
function _getUnderlyingAssetsAmounts(
address creditor,
bytes32 assetKey,
uint256 assetAmount,
bytes32[] memory underlyingAssetKeys
)
internal
view
virtual
returns (uint256[] memory underlyingAssetsAmounts, AssetValueAndRiskFactors[] memory rateUnderlyingAssetsToUsd);
/*///////////////////////////////////////////////////////////////
RISK VARIABLES MANAGEMENT
///////////////////////////////////////////////////////////////*/
/**
* @notice Returns the risk factors of an asset for a Creditor.
* @param creditor The contract address of the Creditor.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @return collateralFactor The collateral factor of the asset for the Creditor, 4 decimals precision.
* @return liquidationFactor The liquidation factor of the asset for the Creditor, 4 decimals precision.
*/
function getRiskFactors(address creditor, address asset, uint256 assetId)
external
view
virtual
override
returns (uint16 collateralFactor, uint16 liquidationFactor);
/**
* @notice Sets the risk parameters of the Protocol for a given Creditor.
* @param creditor The contract address of the Creditor.
* @param maxUsdExposureProtocol_ The maximum USD exposure of the protocol for each Creditor, denominated in USD with 18 decimals precision.
* @param riskFactor The risk factor of the asset for the Creditor, 4 decimals precision.
*/
function setRiskParameters(address creditor, uint112 maxUsdExposureProtocol_, uint16 riskFactor)
external
onlyRegistry
{
if (riskFactor > AssetValuationLib.ONE_4) revert RiskFactorNotInLimits();
riskParams[creditor].maxUsdExposureProtocol = maxUsdExposureProtocol_;
riskParams[creditor].riskFactor = riskFactor;
}
/*///////////////////////////////////////////////////////////////
PRICING LOGIC
///////////////////////////////////////////////////////////////*/
/**
* @notice Returns the USD value of an asset.
* @param creditor The contract address of the Creditor.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @param assetAmount The amount of assets.
* @return valueInUsd The value of the asset denominated in USD, with 18 Decimals precision.
* @return collateralFactor The collateral factor of the asset for a given Creditor, with 4 decimals precision.
* @return liquidationFactor The liquidation factor of the asset for a given Creditor, with 4 decimals precision.
*/
function getValue(address creditor, address asset, uint256 assetId, uint256 assetAmount)
public
view
virtual
override
returns (uint256 valueInUsd, uint256 collateralFactor, uint256 liquidationFactor)
{
bytes32 assetKey = _getKeyFromAsset(asset, assetId);
bytes32[] memory underlyingAssetKeys = _getUnderlyingAssets(assetKey);
(uint256[] memory underlyingAssetsAmounts, AssetValueAndRiskFactors[] memory rateUnderlyingAssetsToUsd) =
_getUnderlyingAssetsAmounts(creditor, assetKey, assetAmount, underlyingAssetKeys);
// Check if rateToUsd for the underlying assets was already calculated in _getUnderlyingAssetsAmounts().
if (rateUnderlyingAssetsToUsd.length == 0) {
// If not, get the USD value of the underlying assets recursively.
rateUnderlyingAssetsToUsd = _getRateUnderlyingAssetsToUsd(creditor, underlyingAssetKeys);
}
(valueInUsd, collateralFactor, liquidationFactor) =
_calculateValueAndRiskFactors(creditor, underlyingAssetsAmounts, rateUnderlyingAssetsToUsd);
}
/**
* @notice Returns the USD value of an asset.
* @param creditor The contract address of the Creditor.
* @param underlyingAssetsAmounts The corresponding amount(s) of Underlying Asset(s), in the decimal precision of the Underlying Asset.
* @param rateUnderlyingAssetsToUsd The USD rates of 10**18 tokens of underlying asset, with 18 decimals precision.
* @return valueInUsd The value of the asset denominated in USD, with 18 Decimals precision.
* @return collateralFactor The collateral factor of the asset for a given Creditor, with 4 decimals precision.
* @return liquidationFactor The liquidation factor of the asset for a given Creditor, with 4 decimals precision.
* @dev We take the most conservative (lowest) risk factor of all underlying assets.
*/
function _calculateValueAndRiskFactors(
address creditor,
uint256[] memory underlyingAssetsAmounts,
AssetValueAndRiskFactors[] memory rateUnderlyingAssetsToUsd
) internal view virtual returns (uint256 valueInUsd, uint256 collateralFactor, uint256 liquidationFactor);
/*///////////////////////////////////////////////////////////////
WITHDRAWALS AND DEPOSITS
///////////////////////////////////////////////////////////////*/
/**
* @notice Increases the exposure to an asset on a direct deposit.
* @param creditor The contract address of the Creditor.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @param amount The amount of tokens.
* @return recursiveCalls The number of calls done to different asset modules to process the deposit/withdrawal of the asset.
*/
function processDirectDeposit(address creditor, address asset, uint256 assetId, uint256 amount)
public
virtual
override
onlyRegistry
returns (uint256 recursiveCalls)
{
bytes32 assetKey = _getKeyFromAsset(asset, assetId);
// Calculate and update the new exposure to Asset.
uint256 exposureAsset = _getAndUpdateExposureAsset(creditor, assetKey, int256(amount));
(uint256 underlyingCalls,) = _processDeposit(exposureAsset, creditor, assetKey);
unchecked {
recursiveCalls = underlyingCalls + 1;
}
}
/**
* @notice Increases the exposure to an asset on an indirect deposit.
* @param creditor The contract address of the Creditor.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @param exposureUpperAssetToAsset The amount of exposure of the upper asset to the asset of this Asset Module.
* @param deltaExposureUpperAssetToAsset The increase or decrease in exposure of the upper asset to the asset of this Asset Module since last interaction.
* @return recursiveCalls The number of calls done to different asset modules to process the deposit/withdrawal of the asset.
* @return usdExposureUpperAssetToAsset The USD value of the exposure of the upper asset to the asset of this Asset Module, 18 decimals precision.
* @dev An indirect deposit, is initiated by a deposit of another derived asset (the upper asset),
* from which the asset of this Asset Module is an underlying asset.
*/
function processIndirectDeposit(
address creditor,
address asset,
uint256 assetId,
uint256 exposureUpperAssetToAsset,
int256 deltaExposureUpperAssetToAsset
) public virtual override onlyRegistry returns (uint256 recursiveCalls, uint256 usdExposureUpperAssetToAsset) {
bytes32 assetKey = _getKeyFromAsset(asset, assetId);
// Calculate and update the new exposure to "Asset".
uint256 exposureAsset = _getAndUpdateExposureAsset(creditor, assetKey, deltaExposureUpperAssetToAsset);
(uint256 underlyingCalls, uint256 usdExposureAsset) = _processDeposit(exposureAsset, creditor, assetKey);
if (exposureAsset == 0 || usdExposureAsset == 0) {
usdExposureUpperAssetToAsset = 0;
} else {
// Calculate the USD value of the exposure of the upper asset to the underlying asset.
usdExposureUpperAssetToAsset = usdExposureAsset.mulDivDown(exposureUpperAssetToAsset, exposureAsset);
}
unchecked {
recursiveCalls = underlyingCalls + 1;
}
}
/**
* @notice Decreases the exposure to an asset on a direct withdrawal.
* @param creditor The contract address of the Creditor.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @param amount The amount of tokens.
*/
function processDirectWithdrawal(address creditor, address asset, uint256 assetId, uint256 amount)
public
virtual
override
onlyRegistry
{
bytes32 assetKey = _getKeyFromAsset(asset, assetId);
// Calculate and update the new exposure to "Asset".
uint256 exposureAsset = _getAndUpdateExposureAsset(creditor, assetKey, -int256(amount));
_processWithdrawal(creditor, assetKey, exposureAsset);
}
/**
* @notice Decreases the exposure to an asset on an indirect withdrawal.
* @param creditor The contract address of the Creditor.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @param exposureUpperAssetToAsset The amount of exposure of the upper asset to the asset of this Asset Module.
* @param deltaExposureUpperAssetToAsset The increase or decrease in exposure of the upper asset to the asset of this Asset Module since last interaction.
* @return usdExposureUpperAssetToAsset The USD value of the exposure of the upper asset to the asset of this Asset Module, 18 decimals precision.
* @dev An indirect withdrawal is initiated by a withdrawal of another Derived Asset (the upper asset),
* from which the asset of this Asset Module is an Underlying Asset.
*/
function processIndirectWithdrawal(
address creditor,
address asset,
uint256 assetId,
uint256 exposureUpperAssetToAsset,
int256 deltaExposureUpperAssetToAsset
) public virtual override onlyRegistry returns (uint256 usdExposureUpperAssetToAsset) {
bytes32 assetKey = _getKeyFromAsset(asset, assetId);
// Calculate and update the new exposure to "Asset".
uint256 exposureAsset = _getAndUpdateExposureAsset(creditor, assetKey, deltaExposureUpperAssetToAsset);
uint256 usdExposureAsset = _processWithdrawal(creditor, assetKey, exposureAsset);
if (exposureAsset == 0 || usdExposureAsset == 0) {
usdExposureUpperAssetToAsset = 0;
} else {
// Calculate the USD value of the exposure of the Upper Asset to the Underlying asset.
usdExposureUpperAssetToAsset = usdExposureAsset.mulDivDown(exposureUpperAssetToAsset, exposureAsset);
}
}
/**
* @notice Update the exposure to an asset and its underlying asset(s) on deposit.
* @param exposureAsset The updated exposure to the asset.
* @param creditor The contract address of the Creditor.
* @param assetKey The unique identifier of the asset.
* @return underlyingCalls The number of calls done to different asset modules to process the deposit/withdrawal of the underlying assets.
* @return usdExposureAsset The USD value of the exposure of the asset, 18 decimals precision.
* @dev The checks on exposures are only done to block deposits that would over-expose a Creditor to a certain asset or protocol.
* Underflows will not revert, but the exposure is instead set to 0.
*/
function _processDeposit(uint256 exposureAsset, address creditor, bytes32 assetKey)
internal
virtual
returns (uint256 underlyingCalls, uint256 usdExposureAsset)
{
uint256 usdExposureProtocol;
{
// Get the unique identifier(s) of the underlying asset(s).
bytes32[] memory underlyingAssetKeys = _getUnderlyingAssets(assetKey);
// Get the exposure to the asset's underlying asset(s) (in the decimal precision of the underlying assets).
(uint256[] memory exposureAssetToUnderlyingAssets,) =
_getUnderlyingAssetsAmounts(creditor, assetKey, exposureAsset, underlyingAssetKeys);
int256 deltaExposureAssetToUnderlyingAsset;
address underlyingAsset;
uint256 underlyingId;
uint256 underlyingCalls_;
uint256 usdExposureToUnderlyingAsset;
for (uint256 i; i < underlyingAssetKeys.length; ++i) {
// Calculate the change in exposure to the underlying assets since last interaction.
deltaExposureAssetToUnderlyingAsset = int256(exposureAssetToUnderlyingAssets[i])
- int256(uint256(lastExposureAssetToUnderlyingAsset[creditor][assetKey][underlyingAssetKeys[i]]));
// Update "lastExposureAssetToUnderlyingAsset".
lastExposureAssetToUnderlyingAsset[creditor][assetKey][underlyingAssetKeys[i]] =
exposureAssetToUnderlyingAssets[i];
// Get the USD Value of the total exposure of "Asset" for its "Underlying Assets" at index "i".
// If the "underlyingAsset" has one or more underlying assets itself, the lower level
// Asset Module(s) will recursively update their respective exposures and return
// the requested USD value to this Asset Module.
(underlyingAsset, underlyingId) = _getAssetFromKey(underlyingAssetKeys[i]);
(underlyingCalls_, usdExposureToUnderlyingAsset) = IRegistry(REGISTRY)
.getUsdValueExposureToUnderlyingAssetAfterDeposit(
creditor,
underlyingAsset,
underlyingId,
exposureAssetToUnderlyingAssets[i],
deltaExposureAssetToUnderlyingAsset
);
usdExposureAsset += usdExposureToUnderlyingAsset;
unchecked {
underlyingCalls += underlyingCalls_;
}
}
// Cache and update lastUsdExposureAsset.
uint256 lastUsdExposureAsset = lastExposuresAsset[creditor][assetKey].lastUsdExposureAsset;
// If usdExposureAsset is bigger than uint112, then check on usdExposureProtocol below will revert.
lastExposuresAsset[creditor][assetKey].lastUsdExposureAsset = uint112(usdExposureAsset);
// Cache lastUsdExposureProtocol.
uint256 lastUsdExposureProtocol = riskParams[creditor].lastUsdExposureProtocol;
// Update lastUsdExposureProtocol.
unchecked {
if (usdExposureAsset >= lastUsdExposureAsset) {
usdExposureProtocol = lastUsdExposureProtocol + (usdExposureAsset - lastUsdExposureAsset);
} else if (lastUsdExposureProtocol > lastUsdExposureAsset - usdExposureAsset) {
usdExposureProtocol = lastUsdExposureProtocol - (lastUsdExposureAsset - usdExposureAsset);
}
// For the else case: (lastUsdExposureProtocol < lastUsdExposureAsset - usdExposureAsset),
// usdExposureProtocol is set to 0, but usdExposureProtocol is already 0.
}
// The exposure must be strictly smaller than the maxExposure, not equal to or smaller than.
// This is to ensure that all deposits revert when maxExposure is set to 0, also deposits with 0 amounts.
if (usdExposureProtocol >= riskParams[creditor].maxUsdExposureProtocol) {
revert AssetModule.ExposureNotInLimits();
}
}
riskParams[creditor].lastUsdExposureProtocol = uint112(usdExposureProtocol);
}
/**
* @notice Update the exposure to an asset and its underlying asset(s) on withdrawal.
* @param creditor The contract address of the Creditor.
* @param assetKey The unique identifier of the asset.
* @param exposureAsset The updated exposure to the asset.
* @return usdExposureAsset The USD value of the exposure of the asset, 18 decimals precision.
* @dev The checks on exposures are only done to block deposits that would over-expose a Creditor to a certain asset or protocol.
* Underflows will not revert, but the exposure is instead set to 0.
* @dev Due to changing usd-prices of underlying assets, or due to changing compositions of upper assets,
* the exposure to a derived asset can increase or decrease over time independent of deposits/withdrawals.
* When derived assets are deposited/withdrawn, these changes in exposure since last interaction are also synced.
* As such the actual exposure on a withdrawal of a derived asset can exceed the maxExposure, but this should never be blocked,
* (the withdrawal actually improves the situation by making the asset less over-exposed).
*/
function _processWithdrawal(address creditor, bytes32 assetKey, uint256 exposureAsset)
internal
virtual
returns (uint256 usdExposureAsset)
{
// Get the unique identifier(s) of the underlying asset(s).
bytes32[] memory underlyingAssetKeys = _getUnderlyingAssets(assetKey);
// Get the exposure to the asset's underlying asset(s) (in the decimal precision of the underlying assets).
(uint256[] memory exposureAssetToUnderlyingAssets,) =
_getUnderlyingAssetsAmounts(creditor, assetKey, exposureAsset, underlyingAssetKeys);
int256 deltaExposureAssetToUnderlyingAsset;
address underlyingAsset;
uint256 underlyingId;
for (uint256 i; i < underlyingAssetKeys.length; ++i) {
// Calculate the change in exposure to the underlying assets since last interaction.
deltaExposureAssetToUnderlyingAsset = int256(exposureAssetToUnderlyingAssets[i])
- int256(uint256(lastExposureAssetToUnderlyingAsset[creditor][assetKey][underlyingAssetKeys[i]]));
// Update "lastExposureAssetToUnderlyingAsset".
lastExposureAssetToUnderlyingAsset[creditor][assetKey][underlyingAssetKeys[i]] =
exposureAssetToUnderlyingAssets[i];
// Get the USD Value of the total exposure of "Asset" for for all of its "Underlying Assets".
// If an "underlyingAsset" has one or more underlying assets itself, the lower level
// Asset Modules will recursively update their respective exposures and return
// the requested USD value to this Asset Module.
(underlyingAsset, underlyingId) = _getAssetFromKey(underlyingAssetKeys[i]);
usdExposureAsset += IRegistry(REGISTRY).getUsdValueExposureToUnderlyingAssetAfterWithdrawal(
creditor,
underlyingAsset,
underlyingId,
exposureAssetToUnderlyingAssets[i],
deltaExposureAssetToUnderlyingAsset
);
}
// Cache and update lastUsdExposureAsset.
uint256 lastUsdExposureAsset = lastExposuresAsset[creditor][assetKey].lastUsdExposureAsset;
// If usdExposureAsset is bigger than uint112, then safecast on usdExposureProtocol below will revert.
lastExposuresAsset[creditor][assetKey].lastUsdExposureAsset = uint112(usdExposureAsset);
// Cache lastUsdExposureProtocol.
uint256 lastUsdExposureProtocol = riskParams[creditor].lastUsdExposureProtocol;
// Update lastUsdExposureProtocol.
uint256 usdExposureProtocol;
unchecked {
if (usdExposureAsset >= lastUsdExposureAsset) {
usdExposureProtocol = lastUsdExposureProtocol + (usdExposureAsset - lastUsdExposureAsset);
} else if (lastUsdExposureProtocol > lastUsdExposureAsset - usdExposureAsset) {
usdExposureProtocol = lastUsdExposureProtocol - (lastUsdExposureAsset - usdExposureAsset);
}
// For the else case: (lastUsdExposureProtocol < lastUsdExposureAsset - usdExposureAsset),
// usdExposureProtocol is set to 0, but usdExposureProtocol is already 0.
}
riskParams[creditor].lastUsdExposureProtocol = SafeCastLib.safeCastTo112(usdExposureProtocol);
}
/**
* @notice Updates the exposure to the asset.
* @param creditor The contract address of the Creditor.
* @param assetKey The unique identifier of the asset.
* @param deltaAsset The increase or decrease in asset amount since the last interaction.
* @return exposureAsset The updated exposure to the asset.
* @dev The checks on exposures are only done to block deposits that would over-expose a Creditor to a certain asset or protocol.
* Underflows will not revert, but the exposure is instead set to 0.
*/
function _getAndUpdateExposureAsset(address creditor, bytes32 assetKey, int256 deltaAsset)
internal
returns (uint256 exposureAsset)
{
// Update exposureAssetLast.
if (deltaAsset > 0) {
exposureAsset = lastExposuresAsset[creditor][assetKey].lastExposureAsset + uint256(deltaAsset);
} else {
uint256 exposureAssetLast = lastExposuresAsset[creditor][assetKey].lastExposureAsset;
exposureAsset = exposureAssetLast > uint256(-deltaAsset) ? exposureAssetLast - uint256(-deltaAsset) : 0;
}
lastExposuresAsset[creditor][assetKey].lastExposureAsset = SafeCastLib.safeCastTo112(exposureAsset);
}
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
import { AssetValuationLib, AssetValueAndRiskFactors } from "../../libraries/AssetValuationLib.sol";
import { ERC20 } from "../../../lib/solmate/src/tokens/ERC20.sol";
import { ERC721 } from "../../../lib/solmate/src/tokens/ERC721.sol";
import { DerivedAM, FixedPointMathLib, IRegistry } from "./AbstractDerivedAM.sol";
import { ReentrancyGuard } from "../../../lib/solmate/src/utils/ReentrancyGuard.sol";
import { SafeCastLib } from "../../../lib/solmate/src/utils/SafeCastLib.sol";
import { SafeTransferLib } from "../../../lib/solmate/src/utils/SafeTransferLib.sol";
import { Strings } from "../../libraries/Strings.sol";
/**
* @title Staking Module
* @author Pragma Labs
* @notice Abstract contract with the minimal implementation of a wrapper contract for Assets staked in an external staking contract.
* @dev The staking Module is an ERC721 contract that does the accounting per Asset (staking token) and per position owner for:
* - The balances of Assets staked through this contract.
* - The balances of reward tokens earned for staking the Assets.
* Next to keeping the accounting of balances, this contract manages the interactions with the external staking contract:
* - Staking Assets.
* - Withdrawing the Assets from staked positions.
* - Claiming reward tokens.
*/
abstract contract StakingAM is DerivedAM, ERC721, ReentrancyGuard {
using FixedPointMathLib for uint256;
using Strings for uint256;
using SafeTransferLib for ERC20;
/* //////////////////////////////////////////////////////////////
CONSTANTS
////////////////////////////////////////////////////////////// */
// The reward token.
ERC20 public immutable REWARD_TOKEN;
/* //////////////////////////////////////////////////////////////
STORAGE
////////////////////////////////////////////////////////////// */
// The id of last minted position.
uint256 internal lastPositionId;
// The baseURI of the ERC721 tokens.
string public baseURI;
// Map Asset to its corresponding struct with global state.
mapping(address asset => AssetState) public assetState;
// Map a position id to its corresponding struct with the position state.
mapping(uint256 position => PositionState) public positionState;
// Struct with the global state per Asset.
struct AssetState {
// The growth of reward tokens per Asset staked, at the last interaction with this contract,
// with 18 decimals precision.
uint128 lastRewardPerTokenGlobal;
// The total amount of Assets staked.
uint128 totalStaked;
// Flag indicating if the asset is allowed.
bool allowed;
}
// Struct with the Position specific state.
struct PositionState {
// The staked Asset.
address asset;
// Total amount of Asset staked for this position.
uint128 amountStaked;
// The growth of reward tokens per Asset staked, at the last interaction of the position owner with this contract,
// with 18 decimals precision.
uint128 lastRewardPerTokenPosition;
// The unclaimed amount of reward tokens of the position owner, at the last interaction of the owner with this contract.
uint128 lastRewardPosition;
}
/* //////////////////////////////////////////////////////////////
EVENTS
////////////////////////////////////////////////////////////// */
event LiquidityDecreased(uint256 indexed positionId, address indexed asset, uint128 amount);
event LiquidityIncreased(uint256 indexed positionId, address indexed asset, uint128 amount);
event RewardPaid(uint256 indexed positionId, address indexed reward, uint128 amount);
/* //////////////////////////////////////////////////////////////
ERRORS
////////////////////////////////////////////////////////////// */
error AssetNotAllowed();
error NotOwner();
error ZeroAmount();
/* //////////////////////////////////////////////////////////////
CONSTRUCTOR
////////////////////////////////////////////////////////////// */
/**
* @param registry The contract address of the Registry.
* @param name_ Name of the Staking Module.
* @param symbol_ Symbol of the Staking Module.
* @dev The ASSET_TYPE, necessary for the deposit and withdraw logic in the Accounts, is "2" for ERC721 tokens.
*/
constructor(address registry, string memory name_, string memory symbol_)
DerivedAM(registry, 2)
ERC721(name_, symbol_)
{ }
/* //////////////////////////////////////////////////////////////
INITIALIZE
////////////////////////////////////////////////////////////// */
/**
* @notice This function will add this contract as an asset in the Registry.
* @dev Will revert if called more than once.
*/
function initialize() external onlyOwner {
inAssetModule[address(this)] = true;
IRegistry(REGISTRY).addAsset(uint96(ASSET_TYPE), address(this));
}
/*///////////////////////////////////////////////////////////////
ASSET MANAGEMENT
///////////////////////////////////////////////////////////////*/
/**
* @notice Adds an asset that can be staked to this contract.
* @param asset The contract address of the Asset.
*/
function _addAsset(address asset) internal {
assetState[asset].allowed = true;
}
/*///////////////////////////////////////////////////////////////
ASSET INFORMATION
///////////////////////////////////////////////////////////////*/
/**
* @notice Checks for a token address and the corresponding id if it is allowed.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @return allowed A boolean, indicating if the asset is allowed.
*/
function isAllowed(address asset, uint256 assetId) public view override returns (bool allowed) {
if (asset == address(this) && assetId <= lastPositionId) allowed = true;
}
/**
* @notice Returns the unique identifiers of the underlying assets.
* @param assetKey The unique identifier of the asset.
* @return underlyingAssetKeys The unique identifiers of the underlying assets.
*/
function _getUnderlyingAssets(bytes32 assetKey)
internal
view
virtual
override
returns (bytes32[] memory underlyingAssetKeys)
{
(, uint256 positionId) = _getAssetFromKey(assetKey);
underlyingAssetKeys = new bytes32[](2);
underlyingAssetKeys[0] = _getKeyFromAsset(positionState[positionId].asset, 0);
underlyingAssetKeys[1] = _getKeyFromAsset(address(REWARD_TOKEN), 0);
}
/**
* @notice Calculates for a given amount of Asset the corresponding amount(s) of underlying asset(s).
* param creditor The contract address of the creditor.
* @param assetKey The unique identifier of the asset.
* @param amount The amount of the Asset, in the decimal precision of the Asset.
* param underlyingAssetKeys The unique identifiers of the underlying assets.
* @return underlyingAssetsAmounts The corresponding amount(s) of Underlying Asset(s), in the decimal precision of the Underlying Asset.
* @return rateUnderlyingAssetsToUsd The usd rates of 10**18 tokens of underlying asset, with 18 decimals precision.
*/
function _getUnderlyingAssetsAmounts(address, bytes32 assetKey, uint256 amount, bytes32[] memory)
internal
view
virtual
override
returns (uint256[] memory underlyingAssetsAmounts, AssetValueAndRiskFactors[] memory rateUnderlyingAssetsToUsd)
{
// Amount of a Staked position in the Asset Module can only be either 0 or 1.
if (amount == 0) return (new uint256[](2), rateUnderlyingAssetsToUsd);
(, uint256 positionId) = _getAssetFromKey(assetKey);
underlyingAssetsAmounts = new uint256[](2);
underlyingAssetsAmounts[0] = positionState[positionId].amountStaked;
underlyingAssetsAmounts[1] = rewardOf(positionId);
return (underlyingAssetsAmounts, rateUnderlyingAssetsToUsd);
}
/*///////////////////////////////////////////////////////////////
PRICING LOGIC
///////////////////////////////////////////////////////////////*/
/**
* @notice Returns the risk factors of an asset for a Creditor.
* @param creditor The contract address of the Creditor.
* @param asset The contract address of the asset.
* @param assetId The id of the asset.
* @return collateralFactor The collateral factor of the asset for the Creditor, 4 decimals precision.
* @return liquidationFactor The liquidation factor of the asset for the Creditor, 4 decimals precision.
*/
function getRiskFactors(address creditor, address asset, uint256 assetId)
external
view
virtual
override
returns (uint16 collateralFactor, uint16 liquidationFactor)
{
bytes32 assetKey = _getKeyFromAsset(asset, assetId);
bytes32[] memory underlyingAssetKeys = _getUnderlyingAssets(assetKey);
uint256[] memory underlyingAssetsAmounts;
(underlyingAssetsAmounts,) = _getUnderlyingAssetsAmounts(creditor, assetKey, 1, underlyingAssetKeys);
AssetValueAndRiskFactors[] memory rateUnderlyingAssetsToUsd =
_getRateUnderlyingAssetsToUsd(creditor, underlyingAssetKeys);
(, uint256 collateralFactor_, uint256 liquidationFactor_) =
_calculateValueAndRiskFactors(creditor, underlyingAssetsAmounts, rateUnderlyingAssetsToUsd);
// Unsafe cast: collateralFactor_ and liquidationFactor_ are smaller than or equal to 1e4.
return (uint16(collateralFactor_), uint16(liquidationFactor_));
}
/**
* @notice Returns the USD value of an asset.
* @param creditor The contract address of the Creditor.
* @param underlyingAssetsAmounts The corresponding amount(s) of Underlying Asset(s), in the decimal precision of the Underlying Asset.
* @param rateUnderlyingAssetsToUsd The USD rates of 10**18 tokens of underlying asset, with 18 decimals precision.
* @return valueInUsd The value of the asset denominated in USD, with 18 Decimals precision.
* @return collateralFactor The collateral factor of the asset for a given Creditor, with 4 decimals precision.
* @return liquidationFactor The liquidation factor of the asset for a given Creditor, with 4 decimals precision.
* @dev We take a weighted risk factor of both underlying assets.
*/
function _calculateValueAndRiskFactors(
address creditor,
uint256[] memory underlyingAssetsAmounts,
AssetValueAndRiskFactors[] memory rateUnderlyingAssetsToUsd
)
internal
view
virtual
override
returns (uint256 valueInUsd, uint256 collateralFactor, uint256 liquidationFactor)
{
// "rateUnderlyingAssetsToUsd" is the USD value with 18 decimals precision for 10**18 tokens of Underlying Asset.
// To get the USD value (also with 18 decimals) of the actual amount of underlying assets, we have to multiply
// the actual amount with the rate for 10**18 tokens, and divide by 10**18.
uint256 valueStakedAsset = underlyingAssetsAmounts[0].mulDivDown(rateUnderlyingAssetsToUsd[0].assetValue, 1e18);
uint256 valueRewardAsset = underlyingAssetsAmounts[1].mulDivDown(rateUnderlyingAssetsToUsd[1].assetValue, 1e18);
valueInUsd = valueStakedAsset + valueRewardAsset;
// Calculate weighted risk factors.
if (valueInUsd > 0) {
unchecked {
collateralFactor = (
valueStakedAsset * rateUnderlyingAssetsToUsd[0].collateralFactor
+ valueRewardAsset * rateUnderlyingAssetsToUsd[1].collateralFactor
) / valueInUsd;
liquidationFactor = (
valueStakedAsset * rateUnderlyingAssetsToUsd[0].liquidationFactor
+ valueRewardAsset * rateUnderlyingAssetsToUsd[1].liquidationFactor
) / valueInUsd;
}
}
// Lower risk factors with the protocol wide risk factor.
uint256 riskFactor = riskParams[creditor].riskFactor;
collateralFactor = riskFactor.mulDivDown(collateralFactor, AssetValuationLib.ONE_4);
liquidationFactor = riskFactor.mulDivDown(liquidationFactor, AssetValuationLib.ONE_4);
}
/*///////////////////////////////////////////////////////////////
STAKING MODULE LOGIC
///////////////////////////////////////////////////////////////*/
/**
* @notice Stakes an amount of Assets in the external staking contract and mints a new position.
* @param asset The contract address of the asset to stake.
* @param amount The amount of Assets to stake.
* @return positionId The id of the minted position.
*/
function mint(address asset, uint128 amount) external virtual nonReentrant returns (uint256 positionId) {
if (amount == 0) revert ZeroAmount();
// Need to transfer before minting or ERC777s could reenter.
ERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
// Cache the old assetState.
AssetState memory assetState_ = assetState[asset];
if (!assetState_.allowed) revert AssetNotAllowed();
// Create a new positionState.
PositionState memory positionState_;
positionState_.asset = asset;
// Calculate the new reward balances.
(assetState_, positionState_) = _getRewardBalances(assetState_, positionState_);
// Calculate the new staked amounts.
assetState_.totalStaked = assetState_.totalStaked + amount;
positionState_.amountStaked = amount;
// Store the new positionState and assetState.
unchecked {
positionId = ++lastPositionId;
}
positionState[positionId] = positionState_;
assetState[asset] = assetState_;
// Stake Asset in external staking contract and claim any pending rewards.
_stakeAndClaim(asset, amount);
// Mint the new position.
_safeMint(msg.sender, positionId);
emit LiquidityIncreased(positionId, asset, amount);
}
/**
* @notice Stakes additional Assets in the external staking contract for an existing position.
* @param positionId The id of the position.
* @param amount The amount of Assets to stake.
*/
function increaseLiquidity(uint256 positionId, uint128 amount) external virtual nonReentrant {
if (amount == 0) revert ZeroAmount();
if (_ownerOf[positionId] != msg.sender) revert NotOwner();
// Cache the old positionState and assetState.
PositionState memory positionState_ = positionState[positionId];
address asset = positionState_.asset;
AssetState memory assetState_ = assetState[asset];
// Need to transfer before increasing liquidity or ERC777s could reenter.
ERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
// Calculate the new reward balances.
(assetState_, positionState_) = _getRewardBalances(assetState_, positionState_);
// Calculate the new staked amounts.
assetState_.totalStaked = assetState_.totalStaked + amount;
positionState_.amountStaked = positionState_.amountStaked + amount;
// Store the new positionState and assetState.
positionState[positionId] = positionState_;
assetState[asset] = assetState_;
// Stake Asset in external staking contract and claim any pending rewards.
_stakeAndClaim(asset, amount);
emit LiquidityIncreased(positionId, asset, amount);
}
/**
* @notice Unstakes, withdraws and claims rewards for total amount staked of Asset in position.
* @param positionId The id of the position to burn.
*/
function burn(uint256 positionId) external virtual {
decreaseLiquidity(positionId, positionState[positionId].amountStaked);
}
/**
* @notice Unstakes and withdraws the asset from the external staking contract.
* @param positionId The id of the position to withdraw from.
* @param amount The amount of Asset to unstake and withdraw.
* @return rewards The amount of reward tokens claimed.
* @dev Also claims and transfers the staking rewards of the position.
*/
function decreaseLiquidity(uint256 positionId, uint128 amount)
public
virtual
nonReentrant
returns (uint256 rewards)
{
if (amount == 0) revert ZeroAmount();
if (_ownerOf[positionId] != msg.sender) revert NotOwner();
// Cache the old positionState and assetState.
PositionState memory positionState_ = positionState[positionId];
address asset = positionState_.asset;
AssetState memory assetState_ = assetState[asset];
// Calculate the new reward balances.
(assetState_, positionState_) = _getRewardBalances(assetState_, positionState_);
// Calculate the new staked amounts.
assetState_.totalStaked = assetState_.totalStaked - amount;
positionState_.amountStaked = positionState_.amountStaked - amount;
// Rewards are paid out to the owner on a decreaseLiquidity.
// -> Reset the balances of the pending rewards.
rewards = positionState_.lastRewardPosition;
positionState_.lastRewardPosition = 0;
// Store the new positionState and assetState.
if (positionState_.amountStaked > 0) {
positionState[positionId] = positionState_;
} else {
delete positionState[positionId];
_burn(positionId);
}
assetState[asset] = assetState_;
// Withdraw the Assets from external staking contract and claim any pending rewards.
_withdrawAndClaim(asset, amount);
// Pay out the rewards to the position owner.
if (rewards > 0) {
// Transfer reward
REWARD_TOKEN.safeTransfer(msg.sender, rewards);
emit RewardPaid(positionId, address(REWARD_TOKEN), uint128(rewards));
}
// Transfer the asset back to the position owner.
ERC20(asset).safeTransfer(msg.sender, amount);
emit LiquidityDecreased(positionId, asset, amount);
}
/**
* @notice Claims and transfers the staking rewards of the position.
* @param positionId The id of the position.
* @return rewards The amount of reward tokens claimed.
*/
function claimReward(uint256 positionId) external virtual nonReentrant returns (uint256 rewards) {
if (_ownerOf[positionId] != msg.sender) revert NotOwner();
// Cache the old positionState and assetState.
PositionState memory positionState_ = positionState[positionId];
address asset = positionState_.asset;
AssetState memory assetState_ = assetState[asset];
// Calculate the new reward balances.
(assetState_, positionState_) = _getRewardBalances(assetState_, positionState_);
// Rewards are paid out to the owner on a claimReward.
// -> Reset the balances of the pending rewards.
rewards = positionState_.lastRewardPosition;
positionState_.lastRewardPosition = 0;
// Store the new positionState and assetState.
positionState[positionId] = positionState_;
assetState[asset] = assetState_;
// Claim the pending rewards from the external staking contract.
_claimReward(asset);
// Pay out the share of the reward owed to the position owner.
if (rewards > 0) {
// Transfer reward
REWARD_TOKEN.safeTransfer(msg.sender, rewards);
emit RewardPaid(positionId, address(REWARD_TOKEN), uint128(rewards));
}
}
/**
* @notice Returns the total amount of Asset staked via this contract.
* @param asset The Asset staked via this contract.
* @return totalStaked_ The total amount of Asset staked via this contract.
*/
function totalStaked(address asset) external view returns (uint256 totalStaked_) {
return assetState[asset].totalStaked;
}
/*///////////////////////////////////////////////////////////////
INTERACTIONS STAKING CONTRACT
///////////////////////////////////////////////////////////////*/
/**
* @notice Stakes an amount of Asset in the external staking contract and claims pending rewards.
* @param asset The Asset to stake.
* @param amount The amount of Asset to stake.
*/
function _stakeAndClaim(address asset, uint256 amount) internal virtual;
/**
* @notice Unstakes and withdraws the Asset from the external contract and claims pending rewards.
* @param asset The Asset to withdraw.
* @param amount The amount of Asset to unstake and withdraw.
*/
function _withdrawAndClaim(address asset, uint256 amount) internal virtual;
/**
* @notice Claims the rewards available for this contract.
* @param asset The Asset for which rewards will be claimed.
*/
function _claimReward(address asset) internal virtual;
/**
* @notice Returns the amount of reward tokens that can be claimed by this contract for a specific Asset.
* @param asset The Asset that is earning rewards from staking.
* @return currentReward The amount of rewards tokens that can be claimed.
*/
function _getCurrentReward(address asset) internal view virtual returns (uint256 currentReward);
/*///////////////////////////////////////////////////////////////
REWARDS VIEW FUNCTIONS
///////////////////////////////////////////////////////////////*/
/**
* @notice Returns the amount of reward tokens claimable by a position.
* @param positionId The id of the position to check the rewards for.
* @return currentRewardClaimable The current amount of reward tokens claimable by the owner of the position.
*/
function rewardOf(uint256 positionId) public view virtual returns (uint256 currentRewardClaimable) {
// Cache the old positionState and assetState.
PositionState memory positionState_ = positionState[positionId];
AssetState memory assetState_ = assetState[positionState_.asset];
// Calculate the new reward balances.
(, positionState_) = _getRewardBalances(assetState_, positionState_);
currentRewardClaimable = positionState_.lastRewardPosition;
}
/**
* @notice Calculates the current global and position specific reward balances.
* @param assetState_ Struct with the old rewards state of the Asset.
* @param positionState_ Struct with the old rewards state of the position.
* @return currentAssetState Struct with the current rewards state of the Asset.
* @return currentPositionState Struct with the current rewards state of the position.
*/
function _getRewardBalances(AssetState memory assetState_, PositionState memory positionState_)
internal
view
returns (AssetState memory, PositionState memory)
{
if (assetState_.totalStaked > 0) {
// Calculate the new assetState
// Fetch the current reward balance from the staking contract and calculate the change in RewardPerToken.
uint256 deltaRewardPerToken =
_getCurrentReward(positionState_.asset).mulDivDown(1e18, assetState_.totalStaked);
// Calculate and update the new RewardPerToken of the asset.
// unchecked: RewardPerToken can overflow, what matters is the delta in RewardPerToken between two interactions.
unchecked {
assetState_.lastRewardPerTokenGlobal =
assetState_.lastRewardPerTokenGlobal + SafeCastLib.safeCastTo128(deltaRewardPerToken);
}
// Calculate the new positionState.
// Calculate the difference in rewardPerToken since the last position interaction.
// unchecked: RewardPerToken can underflow, what matters is the delta in RewardPerToken between two interactions.
unchecked {
deltaRewardPerToken = assetState_.lastRewardPerTokenGlobal - positionState_.lastRewardPerTokenPosition;
}
// Calculate the rewards earned by the position since its last interaction.
// unchecked: deltaRewardPerToken and positionState_.amountStaked are smaller than type(uint128).max.
uint256 deltaReward;
unchecked {
deltaReward = deltaRewardPerToken * positionState_.amountStaked / 1e18;
}
// Update the reward balance of the position.
positionState_.lastRewardPosition =
SafeCastLib.safeCastTo128(positionState_.lastRewardPosition + deltaReward);
}
// Update the RewardPerToken of the position.
positionState_.lastRewardPerTokenPosition = assetState_.lastRewardPerTokenGlobal;
return (assetState_, positionState_);
}
/*///////////////////////////////////////////////////////////////
ERC-721 LOGIC
///////////////////////////////////////////////////////////////*/
/**
* @notice Function that stores a new base URI.
* @param newBaseURI The new base URI to store.
*/
function setBaseURI(string calldata newBaseURI) external virtual onlyOwner {
baseURI = newBaseURI;
}
/**
* @notice Function that returns the token URI as defined in the ERC721 standard.
* @param tokenId The id of the Account.
* @return uri The token URI.
*/
function tokenURI(uint256 tokenId) public view virtual override returns (string memory uri) {
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
// Struct with risk and valuation related information for a certain asset.
struct AssetValueAndRiskFactors {
// The value of the asset.
uint256 assetValue;
// The collateral factor of the asset, for a given creditor.
uint256 collateralFactor;
// The liquidation factor of the asset, for a given creditor.
uint256 liquidationFactor;
}
/**
* @title Asset Valuation Library
* @author Pragma Labs
* @notice The Asset Valuation Library is responsible for calculating the risk weighted values of combinations of assets.
*/
library AssetValuationLib {
/*///////////////////////////////////////////////////////////////
CONSTANTS
///////////////////////////////////////////////////////////////*/
uint256 internal constant ONE_4 = 10_000;
/*///////////////////////////////////////////////////////////////
RISK FACTORS
///////////////////////////////////////////////////////////////*/
/**
* @notice Calculates the collateral value given a combination of asset values and corresponding collateral factors.
* @param valuesAndRiskFactors Array of asset values and corresponding collateral factors.
* @return collateralValue The collateral value of the given assets.
*/
function _calculateCollateralValue(AssetValueAndRiskFactors[] memory valuesAndRiskFactors)
internal
pure
returns (uint256 collateralValue)
{
for (uint256 i; i < valuesAndRiskFactors.length; ++i) {
collateralValue += valuesAndRiskFactors[i].assetValue * valuesAndRiskFactors[i].collateralFactor;
}
collateralValue = collateralValue / ONE_4;
}
/**
* @notice Calculates the liquidation value given a combination of asset values and corresponding liquidation factors.
* @param valuesAndRiskFactors List of asset values and corresponding liquidation factors.
* @return liquidationValue The liquidation value of the given assets.
*/
function _calculateLiquidationValue(AssetValueAndRiskFactors[] memory valuesAndRiskFactors)
internal
pure
returns (uint256 liquidationValue)
{
for (uint256 i; i < valuesAndRiskFactors.length; ++i) {
liquidationValue += valuesAndRiskFactors[i].assetValue * valuesAndRiskFactors[i].liquidationFactor;
}
liquidationValue = liquidationValue / ONE_4;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern, minimalist, and gas efficient ERC-721 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 indexed id);
event Approval(address indexed owner, address indexed spender, uint256 indexed id);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE/LOGIC
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
function tokenURI(uint256 id) public view virtual returns (string memory);
/*//////////////////////////////////////////////////////////////
ERC721 BALANCE/OWNER STORAGE
//////////////////////////////////////////////////////////////*/
mapping(uint256 => address) internal _ownerOf;
mapping(address => uint256) internal _balanceOf;
function ownerOf(uint256 id) public view virtual returns (address owner) {
require((owner = _ownerOf[id]) != address(0), "NOT_MINTED");
}
function balanceOf(address owner) public view virtual returns (uint256) {
require(owner != address(0), "ZERO_ADDRESS");
return _balanceOf[owner];
}
/*//////////////////////////////////////////////////////////////
ERC721 APPROVAL STORAGE
//////////////////////////////////////////////////////////////*/
mapping(uint256 => address) public getApproved;
mapping(address => mapping(address => bool)) public isApprovedForAll;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(string memory _name, string memory _symbol) {
name = _name;
symbol = _symbol;
}
/*//////////////////////////////////////////////////////////////
ERC721 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 id) public virtual {
address owner = _ownerOf[id];
require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");
getApproved[id] = spender;
emit Approval(owner, spender, id);
}
function setApprovalForAll(address operator, bool approved) public virtual {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function transferFrom(
address from,
address to,
uint256 id
) public virtual {
require(from == _ownerOf[id], "WRONG_FROM");
require(to != address(0), "INVALID_RECIPIENT");
require(
msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id],
"NOT_AUTHORIZED"
);
// Underflow of the sender's balance is impossible because we check for
// ownership above and the recipient's balance can't realistically overflow.
unchecked {
_balanceOf[from]--;
_balanceOf[to]++;
}
_ownerOf[id] = to;
delete getApproved[id];
emit Transfer(from, to, id);
}
function safeTransferFrom(
address from,
address to,
uint256 id
) public virtual {
transferFrom(from, to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
function safeTransferFrom(
address from,
address to,
uint256 id,
bytes calldata data
) public virtual {
transferFrom(from, to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
/*//////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return
interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 id) internal virtual {
require(to != address(0), "INVALID_RECIPIENT");
require(_ownerOf[id] == address(0), "ALREADY_MINTED");
// Counter overflow is incredibly unrealistic.
unchecked {
_balanceOf[to]++;
}
_ownerOf[id] = to;
emit Transfer(address(0), to, id);
}
function _burn(uint256 id) internal virtual {
address owner = _ownerOf[id];
require(owner != address(0), "NOT_MINTED");
// Ownership check above ensures no underflow.
unchecked {
_balanceOf[owner]--;
}
delete _ownerOf[id];
delete getApproved[id];
emit Transfer(owner, address(0), id);
}
/*//////////////////////////////////////////////////////////////
INTERNAL SAFE MINT LOGIC
//////////////////////////////////////////////////////////////*/
function _safeMint(address to, uint256 id) internal virtual {
_mint(to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
function _safeMint(
address to,
uint256 id,
bytes memory data
) internal virtual {
_mint(to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
}
/// @notice A generic interface for a contract which properly accepts ERC721 tokens.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721TokenReceiver {
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external virtual returns (bytes4) {
return ERC721TokenReceiver.onERC721Received.selector;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant MAX_UINT256 = 2**256 - 1;
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// Divide x * y by the denominator.
z := div(mul(x, y), denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
revert(0, 0)
}
// If x * y modulo the denominator is strictly greater than 0,
// 1 is added to round up the division of x * y by the denominator.
z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let y := x // We start y at x, which will help us make our initial estimate.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// We check y >= 2^(k + 8) but shift right by k bits
// each branch to ensure that if x >= 256, then y >= 256.
if iszero(lt(y, 0x10000000000000000000000000000000000)) {
y := shr(128, y)
z := shl(64, z)
}
if iszero(lt(y, 0x1000000000000000000)) {
y := shr(64, y)
z := shl(32, z)
}
if iszero(lt(y, 0x10000000000)) {
y := shr(32, y)
z := shl(16, z)
}
if iszero(lt(y, 0x1000000)) {
y := shr(16, y)
z := shl(8, z)
}
// Goal was to get z*z*y within a small factor of x. More iterations could
// get y in a tighter range. Currently, we will have y in [256, 256*2^16).
// We ensured y >= 256 so that the relative difference between y and y+1 is small.
// That's not possible if x < 256 but we can just verify those cases exhaustively.
// Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
// Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
// Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
// For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
// (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
// Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
// sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
// There is no overflow risk here since y < 2^136 after the first branch above.
z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// If x+1 is a perfect square, the Babylonian method cycles between
// floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
// Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
// If you don't care whether the floor or ceil square root is returned, you can remove this statement.
z := sub(z, lt(div(x, z), z))
}
}
function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Mod x by y. Note this will return
// 0 instead of reverting if y is zero.
z := mod(x, y)
}
}
function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
// Divide x by y. Note this will return
// 0 instead of reverting if y is zero.
r := div(x, y)
}
}
function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Add 1 to x * y if x % y > 0. Note this will
// return 0 instead of reverting if y is zero.
z := add(gt(mod(x, y), 0), div(x, y))
}
}
}
/**
* Created by Arcadia Finance
* https://www.arcadia.finance
*
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.22;
interface IAeroGauge {
function deposit(uint256) external;
function withdraw(uint256) external;
function rewardToken() external returns (address);
function earned(address) external view returns (uint256);
function getReward(address) external;
function stakingToken() external returns (address);
}
/**
* Created by Arcadia Finance
* https://www.arcadia.finance
*
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.22;
interface IAeroVoter {
function isGauge(address) external returns (bool);
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.22;
interface IAssetModule {
/**
* @notice Checks for a token address and the corresponding Id if it is allowed.
* @param asset The contract address of the asset.
* @param assetId The Id of the asset.
* @return A boolean, indicating if the asset is allowed.
* @dev For assets without Id (ERC20, ERC4626...), the Id should be set to 0.
*/
function isAllowed(address asset, uint256 assetId) external view returns (bool);
/**
* @notice Returns the usd value of an asset.
* @param creditor The contract address of the creditor.
* @param asset The contract address of the asset.
* @param assetId The Id of the asset.
* @param assetAmount The amount of assets.
* @return valueInUsd The value of the asset denominated in USD, with 18 Decimals precision.
* @return collateralFactor The collateral factor of the asset for a given creditor, with 4 decimals precision.
* @return liquidationFactor The liquidation factor of the asset for a given creditor, with 4 decimals precision.
*/
function getValue(address creditor, address asset, uint256 assetId, uint256 assetAmount)
external
view
returns (uint256, uint256, uint256);
/**
* @notice Returns the risk factors of an asset for a creditor.
* @param creditor The contract address of the creditor.
* @param asset The contract address of the asset.
* @param assetId The Id of the asset.
* @return collateralFactor The collateral factor of the asset for the creditor, 4 decimals precision.
* @return liquidationFactor The liquidation factor of the asset for the creditor, 4 decimals precision.
*/
function getRiskFactors(address creditor, address asset, uint256 assetId) external view returns (uint16, uint16);
/**
* @notice Increases the exposure to an asset on a direct deposit.
* @param creditor The contract address of the creditor.
* @param asset The contract address of the asset.
* @param id The Id of the asset.
* @param amount The amount of tokens.
* @return recursiveCalls The number of calls done to different asset modules to process the deposit/withdrawal of the asset.
*/
function processDirectDeposit(address creditor, address asset, uint256 id, uint256 amount)
external
returns (uint256);
/**
* @notice Increases the exposure to an asset on an indirect deposit.
* @param creditor The contract address of the creditor.
* @param asset The contract address of the asset.
* @param id The Id of the asset.
* @param exposureUpperAssetToAsset The amount of exposure of the upper asset to the asset of this Asset Module.
* @param deltaExposureUpperAssetToAsset The increase or decrease in exposure of the upper asset to the asset of this Asset Module since last interaction.
* @return recursiveCalls The number of calls done to different asset modules to process the deposit/withdrawal of the asset.
* @return usdExposureUpperAssetToAsset The Usd value of the exposure of the upper asset to the asset of this Asset Module, 18 decimals precision.
*/
function processIndirectDeposit(
address creditor,
address asset,
uint256 id,
uint256 exposureUpperAssetToAsset,
int256 deltaExposureUpperAssetToAsset
) external returns (uint256, uint256);
/**
* @notice Decreases the exposure to an asset on a direct withdrawal.
* @param creditor The contract address of the creditor.
* @param asset The contract address of the asset.
* @param id The Id of the asset.
* @param amount The amount of tokens.
*/
function processDirectWithdrawal(address creditor, address asset, uint256 id, uint256 amount) external;
/**
* @notice Decreases the exposure to an asset on an indirect withdrawal.
* @param creditor The contract address of the creditor.
* @param asset The contract address of the asset.
* @param id The Id of the asset.
* @param exposureUpperAssetToAsset The amount of exposure of the upper asset to the asset of this Asset Module.
* @param deltaExposureUpperAssetToAsset The increase or decrease in exposure of the upper asset to the asset of this Asset Module since last interaction.
* @return usdExposureUpperAssetToAsset The Usd value of the exposure of the upper asset to the asset of this Asset Module, 18 decimals precision.
*/
function processIndirectWithdrawal(
address creditor,
address asset,
uint256 id,
uint256 exposureUpperAssetToAsset,
int256 deltaExposureUpperAssetToAsset
) external returns (uint256);
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: MIT
*/
pragma solidity 0.8.22;
import { AssetValueAndRiskFactors } from "../../libraries/AssetValuationLib.sol";
interface IRegistry {
/**
* @notice Checks for a token address and the corresponding Id if it is allowed.
* @param asset The contract address of the asset.
* @param assetId The Id of the asset.
* @return A boolean, indicating if the asset is allowed.
*/
function isAllowed(address asset, uint256 assetId) external view returns (bool);
/**
* @notice Adds a new asset to the Registry.
* @param assetType Identifier for the type of the asset.
* @param asset The contract address of the asset.
*/
function addAsset(uint96 assetType, address asset) external;
/**
* @notice Verifies whether a sequence of oracles complies with a predetermined set of criteria.
* @param oracleSequence The sequence of the oracles to price a certain asset in USD,
* packed in a single bytes32 object.
* @return A boolean, indicating if the sequence complies with the set of criteria.
*/
function checkOracleSequence(bytes32 oracleSequence) external view returns (bool);
/**
* @notice Returns the risk factors per asset for a creditor.
* @param creditor The contract address of the creditor.
* @param assetAddresses Array of the contract addresses of the assets.
* @param assetIds Array of the IDs of the assets.
* @return collateralFactors Array of the collateral factors of the assets for the creditor, 4 decimals precision.
* @return liquidationFactors Array of the liquidation factors of the assets for the creditor, 4 decimals precision.
*/
function getRiskFactors(address creditor, address[] calldata assetAddresses, uint256[] calldata assetIds)
external
view
returns (uint16[] memory, uint16[] memory);
/**
* @notice This function is called by pricing modules of non-primary assets in order to update the exposure of an underlying asset after a deposit.
* @param creditor The contract address of the creditor.
* @param underlyingAsset The underlying asset.
* @param underlyingAssetId The underlying asset ID.
* @param exposureAssetToUnderlyingAsset The amount of exposure of the asset to the underlying asset.
* @param deltaExposureAssetToUnderlyingAsset The increase or decrease in exposure of the asset to the underlying asset since the last interaction.
* @return recursiveCalls The number of calls done to different asset modules to process the deposit/withdrawal of the asset.
* @return usdExposureAssetToUnderlyingAsset The Usd value of the exposure of the asset to the underlying asset, 18 decimals precision.
*/
function getUsdValueExposureToUnderlyingAssetAfterDeposit(
address creditor,
address underlyingAsset,
uint256 underlyingAssetId,
uint256 exposureAssetToUnderlyingAsset,
int256 deltaExposureAssetToUnderlyingAsset
) external returns (uint256, uint256);
/**
* @notice This function is called by pricing modules of non-primary assets in order to update the exposure of an underlying asset after a withdrawal.
* @param creditor The contract address of the creditor.
* @param underlyingAsset The underlying asset.
* @param underlyingAssetId The underlying asset ID.
* @param exposureAssetToUnderlyingAsset The amount of exposure of the asset to the underlying asset.
* @param deltaExposureAssetToUnderlyingAsset The increase or decrease in exposure of the asset to the underlying asset since the last interaction.
* @return usdExposureAssetToUnderlyingAsset The Usd value of the exposure of the asset to the underlying asset, 18 decimals precision.
*/
function getUsdValueExposureToUnderlyingAssetAfterWithdrawal(
address creditor,
address underlyingAsset,
uint256 underlyingAssetId,
uint256 exposureAssetToUnderlyingAsset,
int256 deltaExposureAssetToUnderlyingAsset
) external returns (uint256 usdExposureAssetToUnderlyingAsset);
/**
* @notice Returns the rate of a certain asset in USD.
* @param oracleSequence The sequence of the oracles to price a certain asset in USD,
* packed in a single bytes32 object.
* @return rate The USD rate of an asset with 18 decimals precision.
*/
function getRateInUsd(bytes32 oracleSequence) external view returns (uint256);
/**
* @notice Calculates the USD values of underlying assets.
* @param creditor The contract address of the creditor.
* @param assets Array of the contract addresses of the assets.
* @param assetIds Array of the IDs of the assets.
* @param assetAmounts Array with the amounts of the assets.
* @return valuesAndRiskFactors The value of the asset denominated in USD, with 18 Decimals precision.
*/
function getValuesInUsdRecursive(
address creditor,
address[] calldata assets,
uint256[] calldata assetIds,
uint256[] calldata assetAmounts
) external view returns (AssetValueAndRiskFactors[] memory valuesAndRiskFactors);
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event OwnershipTransferred(address indexed user, address indexed newOwner);
/*//////////////////////////////////////////////////////////////
OWNERSHIP STORAGE
//////////////////////////////////////////////////////////////*/
address public owner;
modifier onlyOwner() virtual {
require(msg.sender == owner, "UNAUTHORIZED");
_;
}
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(address _owner) {
owner = _owner;
emit OwnershipTransferred(address(0), _owner);
}
/*//////////////////////////////////////////////////////////////
OWNERSHIP LOGIC
//////////////////////////////////////////////////////////////*/
function transferOwnership(address newOwner) public virtual onlyOwner {
owner = newOwner;
emit OwnershipTransferred(msg.sender, newOwner);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
uint256 private locked = 1;
modifier nonReentrant() virtual {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Safe unsigned integer casting library that reverts on overflow.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeCastLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeCast.sol)
library SafeCastLib {
function safeCastTo248(uint256 x) internal pure returns (uint248 y) {
require(x < 1 << 248);
y = uint248(x);
}
function safeCastTo240(uint256 x) internal pure returns (uint240 y) {
require(x < 1 << 240);
y = uint240(x);
}
function safeCastTo232(uint256 x) internal pure returns (uint232 y) {
require(x < 1 << 232);
y = uint232(x);
}
function safeCastTo224(uint256 x) internal pure returns (uint224 y) {
require(x < 1 << 224);
y = uint224(x);
}
function safeCastTo216(uint256 x) internal pure returns (uint216 y) {
require(x < 1 << 216);
y = uint216(x);
}
function safeCastTo208(uint256 x) internal pure returns (uint208 y) {
require(x < 1 << 208);
y = uint208(x);
}
function safeCastTo200(uint256 x) internal pure returns (uint200 y) {
require(x < 1 << 200);
y = uint200(x);
}
function safeCastTo192(uint256 x) internal pure returns (uint192 y) {
require(x < 1 << 192);
y = uint192(x);
}
function safeCastTo184(uint256 x) internal pure returns (uint184 y) {
require(x < 1 << 184);
y = uint184(x);
}
function safeCastTo176(uint256 x) internal pure returns (uint176 y) {
require(x < 1 << 176);
y = uint176(x);
}
function safeCastTo168(uint256 x) internal pure returns (uint168 y) {
require(x < 1 << 168);
y = uint168(x);
}
function safeCastTo160(uint256 x) internal pure returns (uint160 y) {
require(x < 1 << 160);
y = uint160(x);
}
function safeCastTo152(uint256 x) internal pure returns (uint152 y) {
require(x < 1 << 152);
y = uint152(x);
}
function safeCastTo144(uint256 x) internal pure returns (uint144 y) {
require(x < 1 << 144);
y = uint144(x);
}
function safeCastTo136(uint256 x) internal pure returns (uint136 y) {
require(x < 1 << 136);
y = uint136(x);
}
function safeCastTo128(uint256 x) internal pure returns (uint128 y) {
require(x < 1 << 128);
y = uint128(x);
}
function safeCastTo120(uint256 x) internal pure returns (uint120 y) {
require(x < 1 << 120);
y = uint120(x);
}
function safeCastTo112(uint256 x) internal pure returns (uint112 y) {
require(x < 1 << 112);
y = uint112(x);
}
function safeCastTo104(uint256 x) internal pure returns (uint104 y) {
require(x < 1 << 104);
y = uint104(x);
}
function safeCastTo96(uint256 x) internal pure returns (uint96 y) {
require(x < 1 << 96);
y = uint96(x);
}
function safeCastTo88(uint256 x) internal pure returns (uint88 y) {
require(x < 1 << 88);
y = uint88(x);
}
function safeCastTo80(uint256 x) internal pure returns (uint80 y) {
require(x < 1 << 80);
y = uint80(x);
}
function safeCastTo72(uint256 x) internal pure returns (uint72 y) {
require(x < 1 << 72);
y = uint72(x);
}
function safeCastTo64(uint256 x) internal pure returns (uint64 y) {
require(x < 1 << 64);
y = uint64(x);
}
function safeCastTo56(uint256 x) internal pure returns (uint56 y) {
require(x < 1 << 56);
y = uint56(x);
}
function safeCastTo48(uint256 x) internal pure returns (uint48 y) {
require(x < 1 << 48);
y = uint48(x);
}
function safeCastTo40(uint256 x) internal pure returns (uint40 y) {
require(x < 1 << 40);
y = uint40(x);
}
function safeCastTo32(uint256 x) internal pure returns (uint32 y) {
require(x < 1 << 32);
y = uint32(x);
}
function safeCastTo24(uint256 x) internal pure returns (uint24 y) {
require(x < 1 << 24);
y = uint24(x);
}
function safeCastTo16(uint256 x) internal pure returns (uint16 y) {
require(x < 1 << 16);
y = uint16(x);
}
function safeCastTo8(uint256 x) internal pure returns (uint8 y) {
require(x < 1 << 8);
y = uint8(x);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "../tokens/ERC20.sol";
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
/**
* Created by Pragma Labs
* SPDX-License-Identifier: BUSL-1.1
*/
pragma solidity 0.8.22;
import { ERC20, IRegistry, StakingAM } from "../abstracts/AbstractStakingAM.sol";
import { IAeroGauge } from "./interfaces/IAeroGauge.sol";
import { IAeroVoter } from "./interfaces/IAeroVoter.sol";
/**
* @title Asset Module for Staked Aerodrome Finance pools
* @author Pragma Labs
* @notice The Staked Aerodrome Finance Asset Module stores pricing logic
* and basic information for Staked Aerodrome Finance LP pools.
*/
contract StakedAerodromeAM is StakingAM {
/* //////////////////////////////////////////////////////////////
CONSTANTS
////////////////////////////////////////////////////////////// */
IAeroVoter public immutable AERO_VOTER;
/* //////////////////////////////////////////////////////////////
STORAGE
////////////////////////////////////////////////////////////// */
// Maps an Aerodrome Finance Pool to its gauge.
mapping(address asset => address gauge) public assetToGauge;
/* //////////////////////////////////////////////////////////////
ERRORS
////////////////////////////////////////////////////////////// */
error AssetAlreadySet();
error GaugeNotValid();
error PoolNotAllowed();
error RewardTokenNotAllowed();
error RewardTokenNotValid();
/* //////////////////////////////////////////////////////////////
CONSTRUCTOR
////////////////////////////////////////////////////////////// */
/**
* @param registry The address of the Registry.
* @param aerodromeVoter The address of the Aerodrome Finance Voter contract.
* @dev The ASSET_TYPE, necessary for the deposit and withdraw logic in the Accounts, is "2" for ERC721 tokens.
*/
constructor(address registry, address aerodromeVoter)
StakingAM(registry, "Arcadia Staked Aerodrome Positions", "aSAEROP")
{
REWARD_TOKEN = ERC20(0x940181a94A35A4569E4529A3CDfB74e38FD98631);
if (!IRegistry(REGISTRY).isAllowed(address(REWARD_TOKEN), 0)) revert RewardTokenNotAllowed();
AERO_VOTER = IAeroVoter(aerodromeVoter);
}
/*///////////////////////////////////////////////////////////////
ASSET MANAGEMENT
///////////////////////////////////////////////////////////////*/
/**
* @notice Adds a new Staked Aerodrome Finance pool to the StakedAerodromeAM.
* @param gauge The contract address of the gauge to stake the Aerodrome Finance LP.
*/
function addAsset(address gauge) external {
if (AERO_VOTER.isGauge(gauge) != true) revert GaugeNotValid();
address pool = IAeroGauge(gauge).stakingToken();
if (!IRegistry(REGISTRY).isAllowed(pool, 0)) revert PoolNotAllowed();
if (assetState[pool].allowed) revert AssetAlreadySet();
if (IAeroGauge(gauge).rewardToken() != address(REWARD_TOKEN)) revert RewardTokenNotValid();
assetToGauge[pool] = gauge;
_addAsset(pool);
}
/*///////////////////////////////////////////////////////////////
INTERACTIONS STAKING CONTRACT
///////////////////////////////////////////////////////////////*/
/**
* @notice Stakes an amount of tokens in the external staking contract.
* @param asset The contract address of the Asset to stake.
* @param amount The amount of Asset to stake.
*/
function _stakeAndClaim(address asset, uint256 amount) internal override {
address gauge = assetToGauge[asset];
// Claim rewards
IAeroGauge(gauge).getReward(address(this));
// Stake asset
ERC20(asset).approve(gauge, amount);
IAeroGauge(gauge).deposit(amount);
}
/**
* @notice Unstakes and withdraws the Asset from the external contract.
* @param asset The contract address of the Asset to unstake and withdraw.
* @param amount The amount of underlying tokens to unstake and withdraw.
*/
function _withdrawAndClaim(address asset, uint256 amount) internal override {
address gauge = assetToGauge[asset];
// Claim rewards
IAeroGauge(gauge).getReward(address(this));
// Withdraw asset
IAeroGauge(gauge).withdraw(amount);
}
/**
* @notice Claims the rewards available for this contract.
* @param asset The contract address of the Asset to claim the rewards for.
* @dev Withdrawing a zero amount will trigger the claim for rewards.
*/
function _claimReward(address asset) internal override {
IAeroGauge(assetToGauge[asset]).getReward(address(this));
}
/**
* @notice Returns the amount of reward tokens that can be claimed by this contract for a specific asset.
* @param asset The Asset to get the current rewards for.
* @return currentReward The amount of reward tokens that can be claimed.
*/
function _getCurrentReward(address asset) internal view override returns (uint256 currentReward) {
currentReward = IAeroGauge(assetToGauge[asset]).earned(address(this));
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.22;
library Strings {
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
}
{
"compilationTarget": {
"src/asset-modules/Aerodrome-Finance/StakedAerodromeAM.sol": "StakedAerodromeAM"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@ensdomains/=lib/slipstream/node_modules/@ensdomains/",
":@nomad-xyz/=lib/slipstream/lib/ExcessivelySafeCall/",
":@openzeppelin/=lib/openzeppelin-contracts/",
":@solidity-parser/=lib/slipstream/node_modules/solhint/node_modules/@solidity-parser/",
":@uniswap/v3-core/contracts/=lib/v3-core/contracts/",
":ExcessivelySafeCall/=lib/slipstream/lib/ExcessivelySafeCall/src/",
":base64-sol/=lib/slipstream/lib/base64/",
":base64/=lib/slipstream/lib/base64/",
":contracts/=lib/slipstream/contracts/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":forge-std/=lib/forge-std/src/",
":hardhat/=lib/slipstream/node_modules/hardhat/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/",
":slipstream/=lib/slipstream/",
":solidity-lib/=lib/slipstream/lib/solidity-lib/contracts/",
":solmate/=lib/solmate/src/",
":swap-router-contracts/=lib/swap-router-contracts/contracts/",
":v3-core/=lib/v3-core/",
":v3-periphery/=lib/v3-periphery/contracts/"
]
}
[{"inputs":[{"internalType":"address","name":"registry","type":"address"},{"internalType":"address","name":"aerodromeVoter","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AssetAlreadySet","type":"error"},{"inputs":[],"name":"AssetNotAllowed","type":"error"},{"inputs":[],"name":"ExposureNotInLimits","type":"error"},{"inputs":[],"name":"GaugeNotValid","type":"error"},{"inputs":[],"name":"NotOwner","type":"error"},{"inputs":[],"name":"OnlyRegistry","type":"error"},{"inputs":[],"name":"Overflow","type":"error"},{"inputs":[],"name":"PoolNotAllowed","type":"error"},{"inputs":[],"name":"RewardTokenNotAllowed","type":"error"},{"inputs":[],"name":"RewardTokenNotValid","type":"error"},{"inputs":[],"name":"RiskFactorNotInLimits","type":"error"},{"inputs":[],"name":"ZeroAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"positionId","type":"uint256"},{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"uint128","name":"amount","type":"uint128"}],"name":"LiquidityDecreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"positionId","type":"uint256"},{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"uint128","name":"amount","type":"uint128"}],"name":"LiquidityIncreased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"positionId","type":"uint256"},{"indexed":true,"internalType":"address","name":"reward","type":"address"},{"indexed":false,"internalType":"uint128","name":"amount","type":"uint128"}],"name":"RewardPaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"AERO_VOTER","outputs":[{"internalType":"contract IAeroVoter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ASSET_TYPE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REGISTRY","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REWARD_TOKEN","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"gauge","type":"address"}],"name":"addAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"assetState","outputs":[{"internalType":"uint128","name":"lastRewardPerTokenGlobal","type":"uint128"},{"internalType":"uint128","name":"totalStaked","type":"uint128"},{"internalType":"bool","name":"allowed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"assetToGauge","outputs":[{"internalType":"address","name":"gauge","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"positionId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"positionId","type":"uint256"}],"name":"claimReward","outputs":[{"internalType":"uint256","name":"rewards","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"positionId","type":"uint256"},{"internalType":"uint128","name":"amount","type":"uint128"}],"name":"decreaseLiquidity","outputs":[{"internalType":"uint256","name":"rewards","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"creditor","type":"address"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"assetId","type":"uint256"}],"name":"getRiskFactors","outputs":[{"internalType":"uint16","name":"collateralFactor","type":"uint16"},{"internalType":"uint16","name":"liquidationFactor","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"creditor","type":"address"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"assetId","type":"uint256"},{"internalType":"uint256","name":"assetAmount","type":"uint256"}],"name":"getValue","outputs":[{"internalType":"uint256","name":"valueInUsd","type":"uint256"},{"internalType":"uint256","name":"collateralFactor","type":"uint256"},{"internalType":"uint256","name":"liquidationFactor","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"inAssetModule","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"positionId","type":"uint256"},{"internalType":"uint128","name":"amount","type":"uint128"}],"name":"increaseLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"assetId","type":"uint256"}],"name":"isAllowed","outputs":[{"internalType":"bool","name":"allowed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"creditor","type":"address"},{"internalType":"bytes32","name":"assetKey","type":"bytes32"},{"internalType":"bytes32","name":"underlyingAssetKey","type":"bytes32"}],"name":"lastExposureAssetToUnderlyingAsset","outputs":[{"internalType":"uint256","name":"exposure","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"creditor","type":"address"},{"internalType":"bytes32","name":"assetKey","type":"bytes32"}],"name":"lastExposuresAsset","outputs":[{"internalType":"uint112","name":"lastExposureAsset","type":"uint112"},{"internalType":"uint112","name":"lastUsdExposureAsset","type":"uint112"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint128","name":"amount","type":"uint128"}],"name":"mint","outputs":[{"internalType":"uint256","name":"positionId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"owner","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"position","type":"uint256"}],"name":"positionState","outputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint128","name":"amountStaked","type":"uint128"},{"internalType":"uint128","name":"lastRewardPerTokenPosition","type":"uint128"},{"internalType":"uint128","name":"lastRewardPosition","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"creditor","type":"address"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"assetId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"processDirectDeposit","outputs":[{"internalType":"uint256","name":"recursiveCalls","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"creditor","type":"address"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"assetId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"processDirectWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"creditor","type":"address"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"assetId","type":"uint256"},{"internalType":"uint256","name":"exposureUpperAssetToAsset","type":"uint256"},{"internalType":"int256","name":"deltaExposureUpperAssetToAsset","type":"int256"}],"name":"processIndirectDeposit","outputs":[{"internalType":"uint256","name":"recursiveCalls","type":"uint256"},{"internalType":"uint256","name":"usdExposureUpperAssetToAsset","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"creditor","type":"address"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"assetId","type":"uint256"},{"internalType":"uint256","name":"exposureUpperAssetToAsset","type":"uint256"},{"internalType":"int256","name":"deltaExposureUpperAssetToAsset","type":"int256"}],"name":"processIndirectWithdrawal","outputs":[{"internalType":"uint256","name":"usdExposureUpperAssetToAsset","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"positionId","type":"uint256"}],"name":"rewardOf","outputs":[{"internalType":"uint256","name":"currentRewardClaimable","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"creditor","type":"address"}],"name":"riskParams","outputs":[{"internalType":"uint112","name":"lastUsdExposureProtocol","type":"uint112"},{"internalType":"uint112","name":"maxUsdExposureProtocol","type":"uint112"},{"internalType":"uint16","name":"riskFactor","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newBaseURI","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"creditor","type":"address"},{"internalType":"uint112","name":"maxUsdExposureProtocol_","type":"uint112"},{"internalType":"uint16","name":"riskFactor","type":"uint16"}],"name":"setRiskParameters","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"uri","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"totalStaked","outputs":[{"internalType":"uint256","name":"totalStaked_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]