Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
MishaShWoof committed Jun 6, 2024
1 parent b303912 commit 5db53a3
Show file tree
Hide file tree
Showing 12 changed files with 478 additions and 64 deletions.
96 changes: 78 additions & 18 deletions contracts/Comet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity 0.8.15;

import "./CometMainInterface.sol";
import "./ERC20.sol";
import "./IERC20NonStandard.sol";
import "./IPriceFeed.sol";

/**
Expand Down Expand Up @@ -139,7 +139,7 @@ contract Comet is CometMainInterface {
**/
constructor(Configuration memory config) {
// Sanity checks
uint8 decimals_ = ERC20(config.baseToken).decimals();
uint8 decimals_ = IERC20NonStandard(config.baseToken).decimals();
if (decimals_ > MAX_BASE_DECIMALS) revert BadDecimals();
if (config.storeFrontPriceFactor > FACTOR_SCALE) revert BadDiscount();
if (config.assetConfigs.length > MAX_ASSETS) revert TooManyAssets();
Expand Down Expand Up @@ -201,6 +201,33 @@ contract Comet is CometMainInterface {
(asset14_a, asset14_b) = getPackedAssetInternal(config.assetConfigs, 14);
}

modifier nonReentrant() {
nonReentrantBefore();
_;
nonReentrantAfter();
}

function nonReentrantBefore() internal {
bytes32 slot = REENTRANCY_GUARD_FLAG_SLOT;
uint256 status;
assembly ("memory-safe") {
status := sload(slot)
}

if (status == REENTRANCY_GUARD_ENTERED) revert ReentrantCallBlocked();
assembly ("memory-safe") {
sstore(slot, REENTRANCY_GUARD_ENTERED)
}
}

function nonReentrantAfter() internal {
bytes32 slot = REENTRANCY_GUARD_FLAG_SLOT;
uint256 status;
assembly ("memory-safe") {
sstore(slot, REENTRANCY_GUARD_NOT_ENTERED)
}
}

/**
* @notice Initialize storage for the contract
* @dev Can be used from constructor or proxy
Expand Down Expand Up @@ -241,7 +268,7 @@ contract Comet is CometMainInterface {

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

// Ensure collateral factors are within range
if (assetConfig.borrowCollateralFactor >= assetConfig.liquidateCollateralFactor) revert BorrowCFTooLarge();
Expand Down Expand Up @@ -482,15 +509,15 @@ contract Comet is CometMainInterface {
* @param asset The collateral asset
*/
function getCollateralReserves(address asset) override public view returns (uint) {
return ERC20(asset).balanceOf(address(this)) - totalsCollateral[asset].totalSupplyAsset;
return IERC20NonStandard(asset).balanceOf(address(this)) - totalsCollateral[asset].totalSupplyAsset;
}

/**
* @notice Gets the total amount of protocol reserves of the base asset
*/
function getReserves() override public view returns (int) {
(uint64 baseSupplyIndex_, uint64 baseBorrowIndex_) = accruedInterestIndices(getNowInternal() - lastAccrualTime);
uint balance = ERC20(baseToken).balanceOf(address(this));
uint balance = IERC20NonStandard(baseToken).balanceOf(address(this));
uint totalSupply_ = presentValueSupply(baseSupplyIndex_, totalSupplyBase);
uint totalBorrow_ = presentValueBorrow(baseBorrowIndex_, totalBorrowBase);
return signed256(balance) - signed256(totalSupply_) + signed256(totalBorrow_);
Expand Down Expand Up @@ -760,18 +787,50 @@ contract Comet is CometMainInterface {
}

/**
* @dev Safe ERC20 transfer in, assumes no fee is charged and amount is transferred
* @dev Safe ERC20 transfer in and returns the final amount transferred (taking into account any fees)
* @dev Note: Safely handles non-standard ERC-20 tokens that do not return a value. See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
*/
function doTransferIn(address asset, address from, uint amount) internal {
bool success = ERC20(asset).transferFrom(from, address(this), amount);
function doTransferIn(address asset, address from, uint amount) internal returns (uint) {
uint256 preTransferBalance = IERC20NonStandard(asset).balanceOf(address(this));
IERC20NonStandard(asset).transferFrom(from, address(this), amount);
bool success;
assembly ("memory-safe") {
switch returndatasize()
case 0 { // This is a non-standard ERC-20
success := not(0) // set success to true
}
case 32 { // This is a compliant ERC-20
returndatacopy(0, 0, 32)
success := mload(0) // Set `success = returndata` of override external call
}
default { // This is an excessively non-compliant ERC-20, revert.
revert(0, 0)
}
}
if (!success) revert TransferInFailed();
return IERC20NonStandard(asset).balanceOf(address(this)) - preTransferBalance;
}

/**
* @dev Safe ERC20 transfer out
* @dev Note: Safely handles non-standard ERC-20 tokens that do not return a value. See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
*/
function doTransferOut(address asset, address to, uint amount) internal {
bool success = ERC20(asset).transfer(to, amount);
IERC20NonStandard(asset).transfer(to, amount);
bool success;
assembly ("memory-safe") {
switch returndatasize()
case 0 { // This is a non-standard ERC-20
success := not(0) // set success to true
}
case 32 { // This is a compliant ERC-20
returndatacopy(0, 0, 32)
success := mload(0) // Set `success = returndata` of override external call
}
default { // This is an excessively non-compliant ERC-20, revert.
revert(0, 0)
}
}
if (!success) revert TransferOutFailed();
}

Expand Down Expand Up @@ -809,7 +868,7 @@ contract Comet is CometMainInterface {
* @dev Supply either collateral or base asset, depending on the asset, if operator is allowed
* @dev Note: Specifying an `amount` of uint256.max will repay all of `dst`'s accrued base borrow balance
*/
function supplyInternal(address operator, address from, address dst, address asset, uint amount) internal {
function supplyInternal(address operator, address from, address dst, address asset, uint amount) internal nonReentrant {
if (isSupplyPaused()) revert Paused();
if (!hasPermission(from, operator)) revert Unauthorized();

Expand All @@ -827,7 +886,7 @@ contract Comet is CometMainInterface {
* @dev Supply an amount of base asset from `from` to dst
*/
function supplyBase(address from, address dst, uint256 amount) internal {
doTransferIn(baseToken, from, amount);
amount = doTransferIn(baseToken, from, amount);

accrueInternal();

Expand All @@ -854,7 +913,7 @@ contract Comet is CometMainInterface {
* @dev Supply an amount of collateral asset from `from` to dst
*/
function supplyCollateral(address from, address dst, address asset, uint128 amount) internal {
doTransferIn(asset, from, amount);
amount = safe128(doTransferIn(asset, from, amount));

AssetInfo memory assetInfo = getAssetInfoByAddress(asset);
TotalsCollateral memory totals = totalsCollateral[asset];
Expand Down Expand Up @@ -920,7 +979,7 @@ contract Comet is CometMainInterface {
* @dev Transfer either collateral or base asset, depending on the asset, if operator is allowed
* @dev Note: Specifying an `amount` of uint256.max will transfer all of `src`'s accrued base balance
*/
function transferInternal(address operator, address src, address dst, address asset, uint amount) internal {
function transferInternal(address operator, address src, address dst, address asset, uint amount) internal nonReentrant {
if (isTransferPaused()) revert Paused();
if (!hasPermission(src, operator)) revert Unauthorized();
if (src == dst) revert NoSelfTransfer();
Expand Down Expand Up @@ -1031,7 +1090,7 @@ contract Comet is CometMainInterface {
* @dev Withdraw either collateral or base asset, depending on the asset, if operator is allowed
* @dev Note: Specifying an `amount` of uint256.max will withdraw all of `src`'s accrued base balance
*/
function withdrawInternal(address operator, address src, address to, address asset, uint amount) internal {
function withdrawInternal(address operator, address src, address to, address asset, uint amount) internal nonReentrant {
if (isWithdrawPaused()) revert Paused();
if (!hasPermission(src, operator)) revert Unauthorized();

Expand Down Expand Up @@ -1192,14 +1251,14 @@ contract Comet is CometMainInterface {
* @param baseAmount The amount of base tokens used to buy the collateral
* @param recipient The recipient address
*/
function buyCollateral(address asset, uint minAmount, uint baseAmount, address recipient) override external {
function buyCollateral(address asset, uint minAmount, uint baseAmount, address recipient) override external nonReentrant {
if (isBuyPaused()) revert Paused();

int reserves = getReserves();
if (reserves >= 0 && uint(reserves) >= targetReserves) revert NotForSale();

// Note: Re-entrancy can skip the reserves check above on a second buyCollateral call.
doTransferIn(baseToken, msg.sender, baseAmount);
baseAmount = doTransferIn(baseToken, msg.sender, baseAmount);

uint collateralAmount = quoteCollateral(asset, baseAmount);
if (collateralAmount < minAmount) revert TooMuchSlippage();
Expand Down Expand Up @@ -1254,14 +1313,15 @@ contract Comet is CometMainInterface {
* @dev Only callable by governor
* @dev Note: Setting the `asset` as Comet's address will allow the manager
* to withdraw from Comet's Comet balance
* @dev Note: For USDT, if there is non-zero prior allowance, it must be reset to 0 first before setting a new value in proposal
* @param asset The asset that the manager will gain approval of
* @param manager The account which will be allowed or disallowed
* @param amount The amount of an asset to approve
*/
function approveThis(address manager, address asset, uint amount) override external {
if (msg.sender != governor) revert Unauthorized();

ERC20(asset).approve(manager, amount);
IERC20NonStandard(asset).approve(manager, amount);
}

/**
Expand Down Expand Up @@ -1322,4 +1382,4 @@ contract Comet is CometMainInterface {
default { return(0, returndatasize()) }
}
}
}
}
7 changes: 7 additions & 0 deletions contracts/CometCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ abstract contract CometCore is CometConfiguration, CometStorage, CometMath {
/// @dev The scale for factors
uint64 internal constant FACTOR_SCALE = 1e18;

/// @dev The storage slot for reentrancy guard flags
bytes32 internal constant REENTRANCY_GUARD_FLAG_SLOT = bytes32(keccak256("comet.reentrancy.guard"));

/// @dev The reentrancy guard statuses
uint256 internal constant REENTRANCY_GUARD_NOT_ENTERED = 0;
uint256 internal constant REENTRANCY_GUARD_ENTERED = 1;

/**
* @notice Determine if the manager has permission to act on behalf of the owner
* @param owner The owner account
Expand Down
1 change: 1 addition & 0 deletions contracts/CometMainInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ abstract contract CometMainInterface is CometCore {
error NotForSale();
error NotLiquidatable();
error Paused();
error ReentrantCallBlocked();
error SupplyCapExceeded();
error TimestampTooLarge();
error TooManyAssets();
Expand Down
31 changes: 30 additions & 1 deletion contracts/IERC20NonStandard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,37 @@ pragma solidity 0.8.15;
* See https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
*/
interface IERC20NonStandard {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);

/**
* @notice Approve `spender` to transfer up to `amount` from `src`
* @dev This will overwrite the approval amount for `spender`
* and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)
* @param spender The address of the account which may transfer tokens
* @param amount The number of tokens that are approved (-1 means infinite)
*/
function approve(address spender, uint256 amount) external;

/**
* @notice Transfer `value` tokens from `msg.sender` to `to`
* @param to The address of the destination account
* @param value The number of tokens to transfer
*/
function transfer(address to, uint256 value) external;

/**
* @notice Transfer `value` tokens from `from` to `to`
* @param from The address of the source account
* @param to The address of the destination account
* @param value The number of tokens to transfer
*/
function transferFrom(address from, address to, uint256 value) external;

/**
* @notice Gets the balance of the specified address
* @param account The address from which the balance will be retrieved
*/
function balanceOf(address account) external view returns (uint256);
}
}
25 changes: 16 additions & 9 deletions contracts/test/EvilToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,27 @@ contract EvilToken is FaucetToken {
attack = attack_;
}

function transfer(address, uint256) external override returns (bool) {
return performAttack();
function transfer(address dst, uint256 amount) public override returns (bool) {
numberOfCalls++;
if (numberOfCalls > attack.maxCalls){
return super.transfer(dst, amount);
} else {
return performAttack(address(this), dst, amount);
}
}

function transferFrom(address, address, uint256) external override returns (bool) {
return performAttack();
function transferFrom(address src, address dst, uint256 amount) public override returns (bool) {
numberOfCalls++;
if (numberOfCalls > attack.maxCalls) {
return super.transferFrom(src, dst, amount);
} else {
return performAttack(src, dst, amount);
}
}

function performAttack() internal returns (bool) {
function performAttack(address src, address dst, uint256 amount) internal returns (bool) {
ReentryAttack memory reentryAttack = attack;
numberOfCalls++;
if (numberOfCalls > reentryAttack.maxCalls) {
// do nothing
} else if (reentryAttack.attackType == AttackType.TRANSFER_FROM) {
if (reentryAttack.attackType == AttackType.TRANSFER_FROM) {
Comet(payable(msg.sender)).transferFrom(
reentryAttack.source,
reentryAttack.destination,
Expand Down
4 changes: 2 additions & 2 deletions contracts/test/FaucetToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ contract StandardToken {
decimals = _decimalUnits;
}

function transfer(address dst, uint256 amount) external virtual returns (bool) {
function transfer(address dst, uint256 amount) public virtual returns (bool) {
require(amount <= balanceOf[msg.sender], "ERC20: transfer amount exceeds balance");
balanceOf[msg.sender] = balanceOf[msg.sender] - amount;
balanceOf[dst] = balanceOf[dst] + amount;
emit Transfer(msg.sender, dst, amount);
return true;
}

function transferFrom(address src, address dst, uint256 amount) external virtual returns (bool) {
function transferFrom(address src, address dst, uint256 amount) public virtual returns (bool) {
require(amount <= allowance[src][msg.sender], "ERC20: transfer amount exceeds allowance");
require(amount <= balanceOf[src], "ERC20: transfer amount exceeds balance");
allowance[src][msg.sender] = allowance[src][msg.sender] - amount;
Expand Down
Loading

0 comments on commit 5db53a3

Please sign in to comment.