Platform: Code4rena
Start Date: 07/04/2022
Pot Size: $100,000 USDC
Total HM: 20
Participants: 62
Period: 7 days
Judge: LSDan
Total Solo HM: 11
Id: 107
League: ETH
Rank: 12/62
Findings: 2
Award: $1,361.64
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Dravee
Also found by: 0x1f8b, 0xDjango, 0xkatana, AuditsAreUS, Cityscape, Foundation, Funen, Hawkeye, IllIllI, JC, JMukesh, Jujic, Kthere, PPrieditis, Picodes, Ruhum, TerrierLover, TrungOre, WatchPug, berndartmueller, catchup, cccz, cmichel, delfin454000, dy, ellahi, hickuphh3, horsefacts, hubble, hyh, ilan, jayjonah8, kebabsec, kenta, minhquanym, pauliax, rayn, reassor, rfa, robee, samruna
888.6651 USDC - $888.67
address(0x0)
when assigning values to address
state variablesnftAddress = _nftAddress;
collateralAsset = _collateralAsset;
__gap[50]
storage variable to allow for new storage variables in later versionsSee this link for a description of this storage variable. While some contracts may not currently be sub-classed, adding the variable now protects against forgetting to add it in the future.
contract CryptoPunksHelper is NFTEscrow, OwnableUpgradeable {
contract EtherRocksHelper is NFTEscrow, OwnableUpgradeable {
public
functions not called by the contract should be declared external
insteadContracts are allowed to override their parents' functions and change the visibility from external
to public
.
function withdraw(IERC20 _token, uint256 _amount) public {
function setFarmingPool(address _farm) public onlyOwner {
constant
s should be defined rather than using magic numbersbytes1(0xff),
decimals > 18
? uint256(answer) / 10**(decimals - 18)
: uint256(answer) * 10**(18 - decimals);
uint256 interestPerSec = interestPerYear / 365 days;
decimals > 18
? uint256(answer) / 10**(decimals - 18)
: uint256(answer) * 10**(18 - decimals);
return (balance() * 1e18) / supply;
require(_curveConfig.pusdIndex < 4, "INVALID_PUSD_CURVE_INDEX");
require(_curveConfig.usdcIndex < 4, "INVALID_USDC_CURVE_INDEX");
abi.encodePacked(weth, uint24(500), usdc),
10**12;
uint256[4] memory liquidityAmounts = [uint256(0), 0, 0, 0];
1e36;
newAccRewardsPerShare = accRewardPerShare + newRewards * 1e36 / totalStaked;
(accRewardPerShare - userLastAccRewardPerShare[account])) / 1e36;
1e36 *
1e36;
1e36 *
1e36;
lockTime = 365 days;
Use a solidity version of at least 0.8.4 to get bytes.concat()
instead of abi.encodePacked(<bytes>,<bytes>)
Use a solidity version of at least 0.8.12 to get string.concat()
instead of abi.encodePacked(<str>,<str>)
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
1e18
) rather than exponentiation (e.g. 10**18
)10**12;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
// SPDX-License-Identifier: GPL-3.0
/// @dev Virtual function, should return the `payload` to use in {FlashEscrow}'s constructor /// @param _idx The index of the NFT that's going to be sent to the {FlashEscrow} instance function _encodeFlashEscrowPayload(uint256 _idx) internal view virtual returns (bytes memory);
Missing: @return
/// @param _feeAddress The address to send fees to constructor(address _jpeg, address _feeAddress) {
Missing: @param _jpeg
/// @param _token The token managed by this vault /// @param _controller The JPEG'd strategies controller constructor( address _token, address _controller, Rate memory _availableTokensRate ) ERC20( string( abi.encodePacked("JPEG\xE2\x80\x99d ", ERC20(_token).name()) ), string(abi.encodePacked("JPEGD", ERC20(_token).symbol())) )
Missing: @param _availableTokensRate
/// @dev Updates `account`'s claimable rewards by adding pending rewards /// @param account The account to update function _withdrawReward(address account) internal returns (uint256) {
Missing: @return
/// @dev Normalizes `blockNumber` to fit within the bounds of an epoch. /// This is done to ensure that no rewards are distributed for staking outside of an epoch without modifying the reward logic. /// For example: /// `blockNumber` is 1100, the epoch's `endBlock` is 1000. In this case the function would return 1000. If this value were to be used /// in the {_updatePool} function, where the pool's `lastRewardBlock` is 990, only the rewards from block 990 to block 1000 would be distributed /// @return Normalized `blockNumber` function _normalizeBlockNumber(uint256 blockNumber) internal view returns (uint256)
Missing: @param blockNumber
/// @dev Updates `msg.sender`'s claimable rewards by adding pending rewards from `_pid` /// @param _pid The pool to withdraw rewards from function _withdrawReward(uint256 _pid) internal returns (uint256) {
Missing: @return
/// @dev The {transferPunk} function is used as the escrow's payload. /// @param _idx The index of the punk that's going to be transferred using {NFTEscrow} function _encodeFlashEscrowPayload(uint256 _idx) internal view override returns (bytes memory)
Missing: @return
/// @dev The {giftRock} function is used as the escrow's payload. /// @param _idx The index of the rock that's going to be transferred using {NFTEscrow} function _encodeFlashEscrowPayload(uint256 _idx) internal view override returns (bytes memory)
Missing: @return
indexed
fieldsEach event
should use three indexed
fields if there are three or more fields
event Borrowed( address indexed owner, uint256 indexed index, uint256 amount );
event Repaid(address indexed owner, uint256 indexed index, uint256 amount);
event Deposit(address indexed user, uint256 depositAmount);
event Borrow(address indexed user, uint256 borrowAmount);
event Repay(address indexed user, uint256 repayAmount);
event Withdraw(address indexed user, uint256 withdrawAmount);
event Deposit(address indexed depositor, uint256 wantAmount);
event Withdrawal(address indexed withdrawer, uint256 wantAmount);
event Harvested(uint256 wantEarned);
event Stake(address indexed user, uint256 amount);
event Unstake(address indexed user, uint256 amount);
event Deposit(address indexed user, uint256 amount);
event Withdraw(address indexed user, uint256 amount);
event Claim(address indexed user, uint256 rewards);
event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
event Claim(address indexed user, uint256 indexed pid, uint256 amount);
event ClaimAll(address indexed user, uint256 amount);
event Lock(address indexed user, uint256 indexed nftIndex, uint256 amount);
event Unlock(address indexed user, uint256 indexed nftIndex, uint256 amount);
nonReentrant
modifier
should occur before all other modifiersThis is a best-practice to protect against reentrancy in other modifiers
) external validNFTIndex(_nftIndex) nonReentrant {
nonReentrant
nonReentrant
function borrow(uint256 amount) external onlyRole(WHITELISTED_ROLE) nonReentrant {
function repay(uint256 amount) external onlyRole(WHITELISTED_ROLE) nonReentrant {
function withdraw(uint256 amount) external onlyRole(WHITELISTED_ROLE) nonReentrant {
) external onlyOwner nonReentrant {
/// This is an alternative to the classic "reservation" method, which requires users to call 3 functions in a specifc order (making the process non atomic)
specifc
//The standard OZ ERC721 implementation of ownerOf reverts on a non existing nft isntead of returning address(0)
isntead
/// @notice Returns data relative to a postition, existing or not
postition
/// @param _useInsurance Whereter to open an insured position. In case the position has already been opened previously,
Whereter
/// instead of fetching it everytime to save gas
everytime
/// @notice Allows members of the `STRATEGIST_ROLE` to withdraw tokens stuck in this constract
constract
🌟 Selected for report: Dravee
Also found by: 0v3rf10w, 0x1f8b, 0xDjango, 0xNazgul, 0xkatana, Cityscape, Cr4ckM3, FSchmoede, Foundation, Funen, Hawkeye, IllIllI, JMukesh, Meta0xNull, PPrieditis, Picodes, TerrierLover, Tomio, WatchPug, berndartmueller, catchup, delfin454000, dirk_y, ellahi, hickuphh3, ilan, kebabsec, kenta, nahnah, rayn, rfa, robee, rokinot, saian, securerodd, slywaters, sorrynotsorry
472.9678 USDC - $472.97
return (salt, predictedAddress);
return (salt, predictedAddress);
require()
/revert()
strings longer than 32 bytes cost extra gasrequire( _liquidationLimitRate.numerator * _creditLimitRate.denominator > _creditLimitRate.numerator * _liquidationLimitRate.denominator, "credit_rate_exceeds_or_equals_liquidation_rate" );
require( hasRole(MINTER_ROLE, _msgSender()), "StableCoin: must have minter role to mint" );
require( hasRole(PAUSER_ROLE, _msgSender()), "StableCoin: must have pauser role to pause" );
require( hasRole(PAUSER_ROLE, _msgSender()), "StableCoin: must have pauser role to unpause" );
require( hasRole(MINTER_ROLE, _msgSender()), "JPEG: must have minter role to mint" );
Use a solidity version of at least 0.8.2 to get compiler automatic inlining
Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads
Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require()
strings
Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
pragma solidity ^0.8.0;
bool
s for storage incurs overhead// Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled.
bool public daoFloorOverride;
bool public useFallbackOracle;
mapping(IERC20 => mapping(IStrategy => bool)) public approvedStrategies;
mapping(address => bool) public whitelistedContracts;
mapping(address => bool) public whitelistedContracts;
mapping(address => bool) public whitelistedContracts;
delete
rather than assigning zero to a mapping to get a gas refundpendingNFTValueETH[_nftIndex] = 0;
userPendingRewards[msg.sender] = 0;
userRewards[msg.sender] = 0;
userRewards[msg.sender] = 0;
> 0
costs more gas than != 0
when used on a uint
in a require()
statementrequire(_newFloor > 0, "Invalid floor");
require(pendingValue > 0, "no_pending_value");
require(_amount > 0, "invalid_amount");
require(_amount > 0, "invalid_amount");
require(debtAmount > 0, "position_not_borrowed");
require(position.liquidatedAt > 0, "not_liquidated");
require(position.liquidatedAt > 0, "not_liquidated");
require(amount > 0, "invalid_amount");
require(amount > 0, "invalid_amount");
require(amount > 0, "invalid_amount");
require(_amount > 0, "INVALID_AMOUNT");
require(_shares > 0, "INVALID_AMOUNT");
require(supply > 0, "NO_TOKENS_DEPOSITED");
require(wethBalance > 0, "NOOP");
require(_amount > 0, "invalid_amount");
require(_amount > 0, "invalid_amount");
require(_amount > 0, "invalid_amount");
require(rewards > 0, "no_reward");
require(_rewardPerBlock > 0, "Invalid reward per block");
require(_amount > 0, "invalid_amount");
require(_amount > 0, "invalid_amount");
require(rewards > 0, "no_reward");
require(rewards > 0, "no_reward");
require(_newTime > 0, "Invalid lock time");
<array>.length
should not be looked up in every loop of a for
-loopEven memory arrays incur the overhead of bit tests and bit shifts to calculate the array length
for (uint256 i = 0; i < _typeInitializers.length; i++) {
for (uint256 j = 0; j < initializer.nfts.length; j++) {
for (uint256 i = 0; i < _strategyConfig.rewardTokens.length; i++) {
for (uint256 i = 0; i < rewardTokens.length; i++) {
for (uint256 i = 0; i < poolInfo.length; i++) {
for (uint256 i = 0; i < _typeInitializers.length; i++) {
for (uint256 j = 0; j < initializer.nfts.length; j++) {
for (uint256 i = 0; i < _strategyConfig.rewardTokens.length; i++) {
for (uint256 i = 0; i < length; i++) {
for (uint256 i = 0; i < rewardTokens.length; i++) {
for (uint256 pid = 0; pid < length; ++pid) {
for (uint256 i = 0; i < poolInfo.length; i++) {
The instances below point to the second access of a state variable within a function. Less obvious optimizations include having local storage variables of mappings within state variable mappings or mappings within state variable structs, having local storage variables of structs within mappings, or having local caches of state variable contracts/addresses.
nftContract.safeTransferFrom(address(this), msg.sender, _nftIndex);
uint256 interestPerYear = (totalDebtAmount *
totalDebtAmount;
uint256 plusPortion = (totalDebtPortion * _amount) /
settings.creditLimitRate = _creditLimitRate;
settings.liquidationLimitRate = _liquidationLimitRate;
settings.creditLimitRate.denominator) *
settings.creditLimitRate.denominator;
settings.liquidationLimitRate.denominator;
settings.debtInterestApr.denominator;
settings.organizationFeeRate.numerator) /
settings.insuranceLiquidationPenaltyRate.numerator) /
uint256 principal = positions[_nftIndex].debtPrincipal;
uint256 debtAmount = positions[_nftIndex].liquidatedAt > 0
address(0) == positionOwner[_nftIndex],
IERC20Upgradeable(collateralAsset).safeTransferFrom(
IERC20Upgradeable(collateralAsset).safeTransfer(msg.sender, amount);
uint8 decimals = oracle.decimals();
creditLimitRate.denominator;
uint256 creditLimit = getCreditLimit(collateralAmount - amount);
amount = amount > debtAmount ? debtAmount : amount;
controller.balanceOf(address(token));
controller.earn(address(token), _bal);
controller.withdraw(address(token), toWithdraw);
controller.earn(address(token), _bal);
controller.withdrawJPEG(address(token), farm);
availableTokensRate.denominator;
want.safeIncreaseAllowance(address(convex.booster), balance);
uint256 balance = want.balanceOf(address(this));
balance = want.balanceOf(address(this));
if (address(jpeg) == extraReward.rewardToken()) {
jpeg.safeTransfer(_to, jpeg.balanceOf(address(this)));
abi.encodePacked(weth, uint24(500), usdc),
uint256 usdcBalance = usdc.balanceOf(address(this));
StrategyConfig memory strategy = strategyConfig;
performanceFee.denominator;
jpeg.safeTransfer(msg.sender, rewards);
userLastAccRewardPerShare[account] = accRewardPerShare;
jpeg.safeTransferFrom(
(epoch.endBlock - _blockNumber());
if (blockNumber < epoch.startBlock) return epoch.startBlock;
poolInfo[_pid].allocPoint = _allocPoint;
user.lastAccRewardPerShare = poolInfo[_pid].accRewardPerShare;
totalAllocPoint = totalAllocPoint + _allocPoint;
totalAllocPoint = totalAllocPoint - prevAllocPoint + _allocPoint;
calldata
instead of memory
for read-only arguments in external
functions saves gasNFTCategoryInitializer[] memory _typeInitializers,
VaultSettings memory _settings
function setDebtInterestApr(Rate memory _debtInterestApr)
function setValueIncreaseLockRate(Rate memory _valueIncreaseLockRate)
function setCreditLimitRate(Rate memory _creditLimitRate)
function setLiquidationLimitRate(Rate memory _liquidationLimitRate)
function setOrganizationFeeRate(Rate memory _organizationFeeRate)
function setInsurancePurchaseRate(Rate memory _insurancePurchaseRate)
Rate memory _insuranceLiquidationPenaltyRate
Rate memory _creditLimitRate
++i
/i++
should be unchecked{++i}
/unchecked{++i}
when it is not possible for them to overflow, as is the case when used in for
- and while
-loopsfor (uint256 i = 0; i < _typeInitializers.length; i++) {
for (uint256 j = 0; j < initializer.nfts.length; j++) {
for (uint256 i = 0; i < _strategyConfig.rewardTokens.length; i++) {
for (uint256 i = 0; i < length; i++) {
for (uint256 i = 0; i < rewardTokens.length; i++) {
for (uint256 pid = 0; pid < length; ++pid) {
for (uint256 i = 0; i < poolInfo.length; i++) {
++i
costs less gas than ++i
, especially when it's used in for
-loops (--i
/i--
too)for (uint256 i = 0; i < _typeInitializers.length; i++) {
for (uint256 j = 0; j < initializer.nfts.length; j++) {
for (uint256 i = 0; i < _strategyConfig.rewardTokens.length; i++) {
for (uint256 i = 0; i < length; i++) {
for (uint256 i = 0; i < rewardTokens.length; i++) {
for (uint256 i = 0; i < poolInfo.length; i++) {
require()
statements that use &&
saves gasSee this issue for an example
require( _owner != address(this) && _owner != address(0), "NFTEscrow: invalid_owner" );
require( rate.denominator > 0 && rate.denominator >= rate.numerator, "invalid_rate" );
require( _creditLimitRate.denominator > 0 && //denominator can be equal to the numerator in some cases (stablecoins used as collateral) _creditLimitRate.denominator >= _creditLimitRate.numerator, "invalid_rate" );
require(amount > 0 && amount <= collateralAmount, "invalid_amount");
require( _rate.numerator > 0 && _rate.denominator >= _rate.numerator, "INVALID_RATE" );
require( _performanceFee.denominator > 0 && _performanceFee.denominator >= _performanceFee.numerator, "INVALID_RATE" );
require( _amount > 0 && _amount <= balanceOf(msg.sender), "invalid_amount" );
uints
/ints
smaller than 32 bytes (256 bits) incurs overheadWhen using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed
uint128 numerator;
uint128 denominator;
uint8 decimals = aggregator.decimals();
uint128 numerator;
uint128 denominator;
uint8 decimals = oracle.decimals();
uint128 numerator;
uint128 denominator;
function decimals() public view virtual override returns (uint8) {
uint128 numerator;
uint128 denominator;
abi.encode()
is less efficient than abi.encodePacked()
abi.encode(nftAddress, _encodeFlashEscrowPayload(_idx))
keccak256()
, should use immutable
rather than constant
See this issue for a detail description of the issue
bytes32 public constant DAO_ROLE = keccak256("DAO_ROLE");
bytes32 public constant LIQUIDATOR_ROLE = keccak256("LIQUIDATOR_ROLE");
bytes32 public constant CUSTOM_NFT_HASH = keccak256("CUSTOM");
bytes32 public constant WHITELISTED_ROLE = keccak256("WHITELISTED_ROLE");
bytes32 public constant STRATEGIST_ROLE = keccak256("STRATEGIST_ROLE");
bytes32 public constant STRATEGIST_ROLE = keccak256("STRATEGIST_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
private
rather than public
for constants, saves gasIf needed, the value can be read from the verified contract source code
bytes32 public constant DAO_ROLE = keccak256("DAO_ROLE");
bytes32 public constant LIQUIDATOR_ROLE = keccak256("LIQUIDATOR_ROLE");
bytes32 public constant CUSTOM_NFT_HASH = keccak256("CUSTOM");
bytes32 public constant WHITELISTED_ROLE = keccak256("WHITELISTED_ROLE");
bytes32 public constant STRATEGIST_ROLE = keccak256("STRATEGIST_ROLE");
bytes32 public constant STRATEGIST_ROLE = keccak256("STRATEGIST_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
if (<x> == true)
=> if (<x>)
, if (<x> == false)
=> if (!<x>)
approvedStrategies[_token][_strategy] == true,
require()
/revert()
checks should be refactored to a modifier or functionrequire(_amount > 0, "invalid_amount");
require(position.liquidatedAt == 0, "liquidated");
require(msg.sender == positionOwner[_nftIndex], "unauthorized");
require(position.liquidatedAt > 0, "not_liquidated");
require(amount > 0, "invalid_amount");
require(address(_token) != address(0), "INVALID_TOKEN");
require(address(_strategy) != address(0), "INVALID_STRATEGY");
require(msg.sender == vaults[_token], "NOT_VAULT");
require(vault != address(0), "ZERO_VAULT"); // additional protection so we don't burn the funds
require(_amount > 0, "invalid_amount");
require(_amount > 0, "invalid_amount");
require(rewards > 0, "no_reward");
If the variable is only accessed once, it's cheaper to use the state variable directly that one time
uint256 staked = totalStaked;
immutable
DexConfig public dexConfig;
CurveConfig public curveConfig;
ConvexConfig public convexConfig;
StrategyConfig public strategyConfig;
require()
or revert()
statements that check input arguments should be at the top of the functionChecks that involve constants should come before checks that involve state variables
require(_amount > 0, "invalid_amount");
require(_amount > 0, "invalid_amount");
require(_vault != address(0), "INVALID_VAULT");
require(user.amount >= _amount, "insufficient_amount");
payable
If a function modifier such as onlyOwner
is used, the function will revert if a normal user tries to pay the function. Marking the function as payable
will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided.
function setBorrowAmountCap(uint256 _borrowAmountCap) external onlyRole(DAO_ROLE)
function setDebtInterestApr(Rate memory _debtInterestApr) external onlyRole(DAO_ROLE)
function setValueIncreaseLockRate(Rate memory _valueIncreaseLockRate) external onlyRole(DAO_ROLE)
function setCreditLimitRate(Rate memory _creditLimitRate) external onlyRole(DAO_ROLE)
function setLiquidationLimitRate(Rate memory _liquidationLimitRate) external onlyRole(DAO_ROLE)
function toggleFallbackOracle(bool _useFallback) external onlyRole(DAO_ROLE)
function setJPEGLockTime(uint256 _newLockTime) external onlyRole(DAO_ROLE) {
function overrideFloor(uint256 _newFloor) external onlyRole(DAO_ROLE) {
function disableFloorOverride() external onlyRole(DAO_ROLE) {
function setOrganizationFeeRate(Rate memory _organizationFeeRate) external onlyRole(DAO_ROLE)
function setInsurancePurchaseRate(Rate memory _insurancePurchaseRate) external onlyRole(DAO_ROLE)
function setInsuranceLiquidationPenaltyRate( Rate memory _insuranceLiquidationPenaltyRate ) external onlyRole(DAO_ROLE) {
function setNFTType(uint256 _nftIndex, bytes32 _type) external validNFTIndex(_nftIndex) onlyRole(DAO_ROLE)
function setNFTTypeValueETH(bytes32 _type, uint256 _amountETH) external onlyRole(DAO_ROLE)
function setPendingNFTValueETH(uint256 _nftIndex, uint256 _amountETH) external validNFTIndex(_nftIndex) onlyRole(DAO_ROLE)
function liquidate(uint256 _nftIndex) external onlyRole(LIQUIDATOR_ROLE) validNFTIndex(_nftIndex) nonReentrant
function collect() external nonReentrant onlyRole(DAO_ROLE) {
function setCreditLimitRate(Rate memory _creditLimitRate) public onlyRole(DEFAULT_ADMIN_ROLE) {
function deposit(uint256 amount) external payable onlyRole(WHITELISTED_ROLE) {
function borrow(uint256 amount) external onlyRole(WHITELISTED_ROLE) nonReentrant {
function repay(uint256 amount) external onlyRole(WHITELISTED_ROLE) nonReentrant {
function withdraw(uint256 amount) external onlyRole(WHITELISTED_ROLE) nonReentrant {
function setFeeAddress(address _feeAddress) public onlyRole(DEFAULT_ADMIN_ROLE)
function setVault(IERC20 _token, address _vault) external onlyRole(STRATEGIST_ROLE)
function approveStrategy(IERC20 _token, IStrategy _strategy) external onlyRole(DEFAULT_ADMIN_ROLE)