1. Use Two-Step Transfer Pattern for Access Controls
Impact
Contracts implementing access control's, e.g. owner, should consider implementing a Two-Step Transfer pattern. Otherwise it's possible that the role mistakenly transfers ownership to the wrong address, resulting in a loss of the role
/**
* @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.
* @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.
* @param newPendingAdmin New pending admin.
* @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function _setPendingAdmin(address payable newPendingAdmin) external returns (uint) {
// Check caller = admin
if (msg.sender != admin) {
return fail(Error.UNAUTHORIZED, FailureInfo.SET_PENDING_ADMIN_OWNER_CHECK);
}
// Save current value, if any, for inclusion in log
address oldPendingAdmin = pendingAdmin;
// Store pendingAdmin with value newPendingAdmin
pendingAdmin = newPendingAdmin;
// Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin)
emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin);
return uint(Error.NO_ERROR);
}
/**
* @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin
* @dev Admin function for pending admin to accept role and update admin
* @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
*/
function _acceptAdmin() external returns (uint) {
// Check caller is pendingAdmin and pendingAdmin ≠ address(0)
if (msg.sender != pendingAdmin || msg.sender == address(0)) {
return fail(Error.UNAUTHORIZED, FailureInfo.ACCEPT_ADMIN_PENDING_ADMIN_CHECK);
}
// Save current values for inclusion in log
address oldAdmin = admin;
address oldPendingAdmin = pendingAdmin;
// Store admin with value pendingAdmin
admin = pendingAdmin;
// Clear the pending value
pendingAdmin = address(0);
emit NewAdmin(oldAdmin, admin);
emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);
return uint(Error.NO_ERROR);
}
2. Unspecific Compiler Version Pragma
Impact
A known vulnerable compiler version may accidentally be selected or security tools might fall-back to an older compiler version ending up checking a different EVM compilation that is ultimately deployed on the blockchain.
Use SafeERC20, or ensure that the transfer/transferFrom return value is checked.
4. Missing checks for address(0) when assigning values to address state variables.
Impact
Setters of address type parameters should include a zero-address check otherwise contract functionality may become inaccessible or tokens burnt forever.
Across the contracts, there are several areas which refer to the constant value -1 in documentation, but, in fact, refers to uint(-1), which is just the maximum value of a uint (2^256 - 1). This could result in improper assumptions about functionality and lead to misunderstanding
developer intent.
Proof of Concept
/* Fail if repayAmount = -1 */
if (repayAmount == uint(-1)) {
return (fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX), 0);
}
Recommendation
Ensure documentation is correct to avoid potential confusion.
6. Incorrect use of "infinite" in documentation.
Incorrect usage of the word "infinite". uint(-1) does not mean infinite, like it says in the comments, but it just means 2^256 - 1.
Low
1. Use Two-Step Transfer Pattern for Access Controls
Impact
Contracts implementing access control's, e.g.
owner
, should consider implementing a Two-Step Transfer pattern. Otherwise it's possible that the role mistakenly transfers ownership to the wrong address, resulting in a loss of the roleProof of Concept
Comptroller.sol::_changeAdmin()
Recommendation
Consider adding a
pendingOwner
where the new owner will have to accept the ownership. This two-step process is already implemented inCToken.sol
.Example from
CToken.sol
2. Unspecific Compiler Version Pragma
Impact
A known vulnerable compiler version may accidentally be selected or security tools might fall-back to an older compiler version ending up checking a different EVM compilation that is ultimately deployed on the blockchain.
Proof of Concept
Recommendation
Avoid floating pragmas for non-library contracts. It is recommended to pin to a concrete compiler version.
3. Unsafe ERC20 Operations
Impact
The return value of an external transfer/transferFrom call is not checked
Proof of Concept
Recommendation
Use
SafeERC20
, or ensure that thetransfer
/transferFrom
return value is checked.4. Missing checks for
address(0)
when assigning values to address state variables.Impact
Setters of address type parameters should include a zero-address check otherwise contract functionality may become inaccessible or tokens burnt forever.
Proof of Concept
Comptroller.sol::_changeAdmin()
,Comptroller.sol::_setBorrowCapGuardian()
,Comptroller.sol::_setPauseGuardian()
.Recommendation
Add
address(0)
checks.5. Invalid documentation regardin upper-bounds error checks.
Impact
Across the contracts, there are several areas which refer to the constant value -1 in documentation, but, in fact, refers to
uint(-1)
, which is just the maximum value of auint
(2^256 - 1). This could result in improper assumptions about functionality and lead to misunderstanding developer intent.Proof of Concept
Recommendation
Ensure documentation is correct to avoid potential confusion.
6. Incorrect use of "infinite" in documentation.
Incorrect usage of the word "infinite".
uint(-1)
does not mean infinite, like it says in the comments, but it just means2^256 - 1
.Proof of Concept
CToken.sol#L80
,CToken.sol#L153
,CToken.sol#L167
.Check
@audit
tags.Recommendation
Fix comments.
Non-Critical
1. Natspec is incomplete
Missing
@return
:Comptroller.sol#L395-L402
,Comptroller.sol#L442-L448
,CToken.sol#L221-L224
,CToken.sol#L377-L381
.Recommendation
Add
@return
's.2. Declare
uint
asuint256
.Recommendation
To favor explicitness, all instances of
uint
should be declared asuint256
.Tools used
c4udit, manual, slither.