Prepared by:
HALBORN
Last Updated 04/26/2024
Date of Engagement by: August 5th, 2022 - September 6th, 2022
100% of all REPORTED Findings have been addressed
All findings
16
Critical
0
High
1
Medium
2
Low
7
Informational
6
DAMfinance engaged Halborn to conduct a security audit on their smart contracts beginning on 2022-08-05 and ending on 2022-09-06. The security assessment was scoped to the smart contracts provided to the Halborn team.
The team at Halborn was provided four weeks for the engagement and assigned a full-time security engineer to audit the security of the smart contract. The security engineer is a blockchain and smart-contract security expert with advanced penetration testing, smart-contract hacking, and deep knowledge of multiple blockchain protocols.
The purpose of this audit is to:
Ensure that smart contract functions operate as intended
Identify potential security issues with the smart contracts
In summary, Halborn identified some security risks that were mostly addressed by the DAMfinance team.
Halborn performed a combination of manual and automated security testing to balance efficiency, timeliness, practicality, and accuracy in regard to the scope of this audit. While manual testing is recommended to uncover flaws in logic, process, and implementation; automated testing techniques help enhance coverage of the bridge code and can quickly identify items that do not follow security best practices. The following phases and associated tools were used throughout the term of the audit:
Research into architecture and purpose
Smart contract manual code review and walk-through
Graphing out functionality and contract logic/connectivity/functions (solgraph
)
Manual assessment of use and safety for the critical Solidity variables and functions in scope to identify any arithmetic related vulnerability classes
Manual testing by custom scripts
Static Analysis of security for scoped contract, and imported functions. (Slither
)
Local deployment (Hardhat
, Remix IDE
, Brownie
)
IN-SCOPE: The security assessment was scoped to the following smart contracts:
CollateralJoinDecimals.sol
CollateralJoin.sol
dPrimeJoin.sol
dPrime.sol
LMCVProxy.sol
LMCV.sol
PSM.sol
WGLMR.sol
Commit ID: 3391f49ca23e67b2dbb39d35ff7d665dc5769661
Remediation plan:
Pull Request: 30 Branch: secondRoundAuditFixes Commit ID 9798fb6f03aab96d8702116e6bef394b2e501d59
OUT-OF-SCOPE: Other smart contracts in the repository, external libraries and economical attacks.
Critical
0
High
1
Medium
2
Low
7
Informational
6
Impact x Likelihood
HAL-02
HAL-01
HAL-05
HAL-06
HAL-07
HAL-03
HAL-11
HAL-16
HAL-04
HAL-09
HAL-08
HAL-10
HAL-12
HAL-13
HAL-14
HAL-15
Security analysis | Risk level | Remediation Date |
---|---|---|
USER FUNDS MAY GET LOCKED | High | Solved - 10/10/2022 |
ADMINS ARE ALLOWED TO BURN USERS DPRIME TOKENS WITHOUT AUTHORIZATION | Medium | Future Release |
MULTIPLE INTEGER UNDERFLOWS IN LMCV MODULE | Medium | Solved - 10/11/2022 |
INTEGER UNDERFLOW PSM MODULE | Low | Solved - 10/10/2022 |
CONTRACTS MIGHT LOSE ADMIN FUNCTIONALITY | Low | Solved - 10/10/2022 |
MISSING PAUSE/UNPAUSE FUNCTIONALITY | Low | Solved - 10/10/2022 |
IMPROPER ROLE-BASED ACCESS CONTROL | Low | Risk Accepted |
DIVISION BY ZERO IN ISWITHINCREDITLIMIT | Low | Solved - 10/10/2022 |
EDITCOLLATERALLIST BEHAVIOUR MAY BE MISLEADING | Low | Solved - 10/10/2022 |
MISSING ZERO ADDRESS CHECK | Low | Solved - 10/10/2022 |
MISSING EVENTS ON CHANGES | Informational | Solved - 10/10/2022 |
FUNCTIONS COULD BE DECLARED AS EXTERNAL | Informational | Solved - 10/10/2022 |
VARIABLES COULD BE DEFINED AS CONSTANT | Informational | Solved - 10/10/2022 |
COLLATERALLIST SEEMS TO BE UNUSED | Informational | Solved - 10/11/2022 |
MISSING NATSPEC DOCUMENTATION | Informational | Future Release |
CHANGE MEMORY TO CALLDATA | Informational | Solved - 10/10/2022 |
// High
When admin calls thecage
function of the CollateralJoin
contract, the live
flag is set to zero, which means the contract is stopped.
The user can repay the loan, but will not be able to exit (withdraw) assets from the CollateralJoin
contract.
cage
function:
function cage() external auth {
live = 0;
emit Cage();
}
The exit
function, requires contract to be live
:
function exit(address usr, uint256 wad) external {
require(live == 1, "CollateralJoin/not-live");
lmcv.pullCollateral(collateralName, msg.sender, wad);
require(collateralContract.transfer(usr, wad), "CollateralJoin/failed-transfer");
emit Exit(usr, wad);
}
Hardhat test case:
it("HAL-01 User collateral gets locked", async function () {
// initial collateralJoin balance: 2 users joined with 100 tokens each
expect(await mockToken.balanceOf(collateralJoin.address)).to.equal(fwad("200"));
//Total value of collateral: $6000
//Total loanable amount: $3000
await userLMCV.loan(collateralBytesList, [fwad("50"), fwad("100"), fwad("200")], fwad("2000"), addr1.address);
expect(await userLMCV.lockedCollateral(addr1.address, mockTokenBytes)).to.equal(fwad("50"));
expect(await userLMCV.lockedCollateral(addr1.address, mockToken2Bytes)).to.equal(fwad("100"));
expect(await userLMCV.lockedCollateral(addr1.address, mockToken3Bytes)).to.equal(fwad("200"));
expect(await userLMCV.normalizedDebt(addr1.address)).to.equal(fwad("2000"));
expect(await lmcv.totalDPrime()).to.equal(frad("2000"));
expect(await lmcv.totalNormalizedDebt()).to.equal(fwad("2000"));
// lock join contract
await collateralJoin.cage();
expect(await await collateralJoin.live()).to.equal(0);
// repay the loan
await userLMCV.repay(collateralBytesList, [fwad("50"), fwad("100"), fwad("200")], fwad("2000"), addr1.address);
// validate the loan was paid - there is no locked collateral, debt nor issued dPrime
expect(await userLMCV.lockedCollateral(addr1.address, mockTokenBytes)).to.equal(fwad("0"));
expect(await userLMCV.lockedCollateral(addr1.address, mockToken2Bytes)).to.equal(fwad("0"));
expect(await userLMCV.lockedCollateral(addr1.address, mockToken3Bytes)).to.equal(fwad("0"));
expect(await userLMCV.normalizedDebt(addr1.address)).to.equal(fwad("0"));
expect(await lmcv.totalDPrime()).to.equal(frad("0"));
expect(await lmcv.totalNormalizedDebt()).to.equal(fwad("0"));
// try to exit from join contract
let collatJoinConnect = collateralJoin.connect(addr1);
await expect(collatJoinConnect.exit(addr1.address, fwad("100")))
.to.be.revertedWith("CollateralJoin/not-live");
// balance does not change
expect(await mockToken.balanceOf(collateralJoin.address)).to.equal(fwad("200"));
});
SOLVED: Updated the cage
function to allow an admin to re-make the contract. When the administrator resumes the contract, users can withdraw their assets successfully.
Reference: CollateralJoin.sol
// Medium
An administrator of the dPrime
token can burn users' tokens. Admin burning dPrime
might break the LMCV
contract as there might be less supply than recorded in the LMCV
contract.
function burn(address from, uint256 value) external {
uint256 balance = balanceOf[from];
require(balance >= value, "dPrime/insufficient-balance");
if (from != msg.sender && admins[msg.sender] != 1) {
uint256 allowed = allowance[from][msg.sender];
[...]
Hardhat test scenario:
it("HAL-02 admin burns user dPrime", async function () {
await setupUser(addr1, ["1000", "1000", "1000"]);
await userLMCV.approveMultiple([lmcvProxy.address, dPrimeJoin.address]);
// lock some collataral of each token
await userLMCVProxy.createLoan(collateralBytesList, [fwad("100"), fwad("200"), fwad("300")], fwad("1000"));
expect(await lmcv.totalNormalizedDebt()).to.equal(fwad("1000"));
expect(await lmcv.normalizedDebt(addr1.address)).to.equal(fwad("1000"));
expect(await dPrime.balanceOf(addr1.address)).to.eq(fwad("990"));
expect(await dPrime.totalSupply()).to.eq(fwad("990"));
dPrime.burn(addr1.address, fwad("500"));
expect(await lmcv.totalNormalizedDebt()).to.equal(fwad("1000"));
expect(await lmcv.normalizedDebt(addr1.address)).to.equal(fwad("1000"));
expect(await dPrime.balanceOf(addr1.address)).to.be.eq(fwad("490"));
expect(await dPrime.totalSupply()).to.eq(fwad("490"));
});
PENDING: The DAMfinance team
stated that they are planning to implement the recommended fix with the governance module in the future.
// Medium
There are multiple cases in the LMCV
contract when subtracting balances without checks. Such behavior may cause the transaction to fail due to arithmetic errors (integer underflow).
The repay
function does not perform boundary checks; when the user tries to repay a loan after the interest rate has changed, the transaction may fail with an arithmetic error due to underflow:
Calculating the dPrime
amounts and debt:
dPrime[user] -= normalizedDebtChange * rateMult;
totalDPrime -= normalizedDebtChange * rateMult;
normalizedDebt[user] -= normalizedDebtChange;
totalNormalizedDebt -= normalizedDebtChange;
Calculating new collateral amount:
// Debit locked collateral amount and credit unlocked collateral amount.
uint256 newLockedCollateralAmount = lockedCollateral[user][collateralList[i]] -= collateralChange[i];
uint256 newUnlockedCollateralAmount = unlockedCollateral[user][collateralList[i]] += collateralChange[i];
deflate
function calculating protocol deficit:
function deflate(uint256 rad) external {
address u = msg.sender;
protocolDeficit[u] -= rad;
totalProtocoldeficit -= rad;
dPrime[u] -= rad;
totalDPrime -= rad;
emit Deflate(msg.sender, rad);
}
pullCollateral
function updating unlocked collateral:
function pullCollateral(bytes32 collat, address user, uint256 wad) external auth {
unlockedCollateral[user][collat] -= wad;
emit PullCollateral(collat, user, wad);
}
moveCollateral
function updating unlocked collateral:
function moveCollateral(bytes32 collat, address src, address dst, uint256 wad) external {
require(approval(src, msg.sender), "LMCV/collateral move not allowed");
unlockedCollateral[src][collat] -= wad;
unlockedCollateral[dst][collat] += wad;
emit MoveCollateral(collat, src, dst, wad);
}
moveDPrime
function:
function moveDPrime(address src, address dst, uint256 rad) external {
require(approval(src, msg.sender), "LMCV/dPrime move not allowed");
dPrime[src] -= rad;
dPrime[dst] += rad;
emit MoveDPrime(src, dst, rad);
}
Below are the Hardhat test cases for underflow issues:
Issues
✔ HAL-03 Integer underflow in LMCV pullCollateral function - exit twice (38ms)
✔ HAL-03 Integer underflow in LMCV pullCollateral function - exit with more than joined
✔ HAL-03 Integer underflow in LMCV moveCollateral - move more than deposited
✔ HAL-03 Integer underflow in LMCV moveDPrime (62ms)
✔ HAL-03 Integer underflow in LMCV repay - rate updated after loan (98ms)
Source code of test cases:
it("HAL-03 Integer underflow in LMCV pullCollateral function - exit twice", async function () {
expect(await userLMCV.unlockedCollateral(addr1.address, mockTokenBytes)).to.equal(fwad("100"));
// exit from CollateralJoin
let collatJoinConnect = collateralJoin.connect(addr1);
await collatJoinConnect.exit(addr1.address, fwad("100"));
expect(await userLMCV.unlockedCollateral(addr1.address, mockTokenBytes)).to.equal(fwad("0"));
// try to exit for the second time
await expect(collatJoinConnect.exit(addr1.address, fwad("100")))
.to.be.revertedWith("Arithmetic operation underflowed or overflowed outside of an unchecked block");
});
it("HAL-03 Integer underflow in LMCV pullCollateral function - exit with more than joined", async function () {
let collatJoinConnect = collateralJoin.connect(addr1);
expect(await userLMCV.unlockedCollateral(addr1.address, mockTokenBytes)).to.equal(fwad("100"));
// exit with more than joined
await expect(collatJoinConnect.exit(addr1.address, fwad("101")))
.to.be.revertedWith("Arithmetic operation underflowed or overflowed outside of an unchecked block");
expect(await userLMCV.unlockedCollateral(addr1.address, mockTokenBytes)).to.equal(fwad("100"));
});
it("HAL-03 Integer underflow in LMCV moveCollateral - move more than deposited", async function () {
expect(await userLMCV.unlockedCollateral(addr1.address, mockTokenBytes)).to.equal(fwad("100"));
expect(await userLMCV.unlockedCollateral(addr1.address, mockToken2Bytes)).to.equal(fwad("200"));
expect(await userLMCV.unlockedCollateral(addr1.address, mockToken3Bytes)).to.equal(fwad("300"));
await expect(userLMCV.moveCollateral(mockTokenBytes, addr1.address, addr1.address, fwad("200")))
.to.be.revertedWith("Arithmetic operation underflowed or overflowed outside of an unchecked block");
});
it("HAL-03 Integer underflow in LMCV moveDPrime", async function () {
await userLMCV.approve(dPrimeJoin.address);
await userLMCV.loan([mockTokenBytes], [fwad("50")], fwad("1000"), addr1.address);
let dPrimeJoinConnect = dPrimeJoin.connect(addr1);
await expect(dPrimeJoinConnect.exit(addr1.address, fwad("1001")))
.to.be.revertedWith("Arithmetic operation underflowed or overflowed outside of an unchecked block");
expect(await dPrime.balanceOf(addr1.address)).to.equal(fwad("0"));
expect(await lmcv.totalDPrime()).to.equal(frad("1000"));
});
it("HAL-03 Integer underflow in LMCV repay - rate updated after loan", async function () {
// take a loan
await userLMCV.loan(collateralBytesList, [fwad("50"), fwad("100"), fwad("200")], fwad("2000"), addr1.address);
expect(await lmcv.dPrime(addr1.address)).to.equal(frad("2000"));
expect(await userLMCV.normalizedDebt(addr1.address)).to.equal(fwad("2000"));
expect(await userLMCV.dPrime(addr1.address)).to.equal(frad("2000"));
// update rate
await lmcv.updateRate(fray(".1"));
// repay
await expect(userLMCV.repay(collateralBytesList, [fwad("50"), fwad("100"), fwad("200")], fwad("2000"), addr1.address))
.to.be.revertedWith("Arithmetic operation underflowed or overflowed outside of an unchecked block");
});
// Low
PSM contract may revert due to an arithmetic error caused by integer underflow when mintFee
is set to a large value.
function createDPrime(address usr, bytes32[] memory collateral, uint256[] memory collatAmount) external {
require(collateral.length == 1 && collatAmount.length == 1 && collateral[0] == collateralName, "PSM/Incorrect setup");
uint256 collatAmount18 = collatAmount[0] * to18ConversionFactor; // [wad]
uint256 fee = _rmul(collatAmount18, mintFee); // rmul(wad, ray) = wad
uint256 dPrimeAmt = collatAmount18 - fee;
collateralJoin.join(address(this), collatAmount[0], msg.sender);
[...]
SOLVED: The require
statement was added to ensure that mintFee
is less than 100%.
Reference: PSM.sol
// Low
The deny
function is not checking if there are any other active wards
before setting wards[usr] = 0
. If the user denies himself, when they are the only ward
, the contract will lose admin functionality.
LMCV
module:
function deny(address usr) external auth {
wards[usr] = 0;
emit Deny(usr);
}
PSM
module:
function deny(address usr) external auth {
wards[usr] = 0;
emit Deny(usr);
}
dPrime
module:
function deny(address usr) external auth {
admins[usr] = 0;
emit Deny(usr);
}
CollateralJoin
module:
function deny(address usr) external auth {
wards[usr] = 0;
emit Deny(usr);
}
CollateralJoinDecimals
module:
function deny(address usr) external auth {
wards[usr] = 0;
emit Deny(usr);
}
SOLVED: The ArchAdmin
variable was added to the contract. The address assigned to this field cannot be removed from wards
/admins
mapping via administrate
or deny
functions, ensuring there is at least one administrator on the contract. To update this address, a new ArchAdmin
must be set; then the address can be removed from admins mapping.
Reference:
// Low
In case a hack occurs, or an exploit is discovered, the team should be able to pause functionality until the necessary changes are made to the system. To use a THORchain example again, the team behind THOR chain noticed an attack was going to occur well before the system transferred funds to the hacker. However, they were unable to shut the system down fast enough (According to the incident report).
In case of the contracts in scope, only LMCV
and LMCVProxy
can be stopped/resumed. Other contracts can only be disabled by the cage
function (CollateralJoin
and CollateralJoinDecimals
) or do not have such possibility at all (PSM
, dPrimeJoin
).
SOLVED: The cage
function was modified, and the setLive
function was added to the contracts. Now all contracts except dPrime
and dPrimeJoin
can be stopped/resumed in case of an attack.
// Low
The smart contracts, in scope, do not implement granular access control. All the privileged functionality is assigned to one role. This could lead to serious consequences if, for example, a malicious admin decides to take over the platform.
RISK ACCEPTED: The DAMfinance team
accepted the risk of this finding and stated that in this role-based admin structure, only smart contracts would have access to specific roles, and a person-controlled owner address would be able to set all of these roles. Since a smart contract can only call functions it has interfaces for and admin access to in this setup, and an owner-level admin hack would have the ability to set itself as any other level admin, this does not seem like a useful check.
// Low
The isWithinCreditLimit
function does not handle leveraged-only collateral properly. When leveraged-only collateral is passed, and the credit value exceeds the collateral value, the transaction fails with a Division or modulo division by zero
error.
function isWithinCreditLimit(address user, uint256 rate) private view returns (bool) {
bytes32[] storage lockedList = lockedCollateralList[user];
uint256 creditLimit = 0; // [rad]
uint256 leverTokenCreditLimit = 0; // [rad]
uint256 noLeverageTotal = 0; // [wad]
uint256 leverageTotal = 0; // [rad]
for (uint256 i = 0; i < lockedList.length; i++) {
Collateral memory collateralData = CollateralData[lockedList[i]];
if(lockedCollateral[user][lockedList[i]] > collateralData.dustLevel){
uint256 collateralValue = lockedCollateral[user][lockedList[i]] * collateralData.spotPrice; // wad*ray -> rad
if(!collateralData.leveraged){
creditLimit += _rmul(collateralValue, collateralData.creditRatio);
noLeverageTotal += collateralValue / RAY;
} else {
leverageTotal += collateralValue;
leverTokenCreditLimit += _rmul(collateralValue, collateralData.creditRatio);
}
}
}
// If only leverage tokens exist, just return their credit limit
// Keep credit ratio low on levered tokens (60% or lower) to incentivize having non levered collateral in the vault
if(noLeverageTotal == 0 && leverageTotal > 0 && leverTokenCreditLimit >= normalizedDebt[user] * rate){
return true;
}
uint256 leverageMultiple = noLeverageTotal == 0 && leverageTotal == 0 ? RAY : RAY + leverageTotal / noLeverageTotal;
if (_rmul(creditLimit, leverageMultiple) >= (normalizedDebt[user] * rate)) {
return true;
}
return false;
}
SOLVED: The condition in the isWithinLimit
function of the LMCV.sol
contract was modified to handle correctly limits for leveraged tokens.
Reference: LMCV.sol
// Low
The editCollateralList
function takes three arguments: bytes32 collateralName, bool accepted, uint256 position
.
When adding collateral, only collateraName
is used, and position
is ignored.
When removing collateral, only position
is used; the function is not validated if a given collateralName
is located in a specified position.
Moreover, the function does not check if the given collateral is already added to the list.
function editCollateralList(bytes32 collateralName, bool accepted, uint256 position) external auth {
if(accepted){
CollateralList.push(collateralName);
}else{
deleteElement(CollateralList, position);
}
}
SOLVED: The editCollateralList
function was removed.
Reference: LMCV.sol
// Low
Following functions are not validating, that given address is different from zero:
approve
function of dPrime.solconstructor
of CollateralJoin.solconstructor
of CollateralJoinDecimals.solconstructor
of PSM.solSOLVED: Zero-address checks were added:
// Informational
Functions performing important changes on the contract: setLMCV
, setDPrimeJoin
, setDPrime
, and editCollateral
are not emitting events to facilitate monitoring of the protocol.
SOLVED: New events were added:
// Informational
The following functions could be declared as external
:
WGLMR.withdraw(uint256)
WGLMR.totalSupply()
WGLMR.approve(address,uint256)
WGLMR.transfer(address,uint256)
SOLVED: Function definitions were updated from public
to external
:
// Informational
The following variables could be defined as constant
:
WGLMR.decimals
WGLMR.name
WGLMR.symbol
SOLVED: Variable definitions were updated to constant
:
// Informational
The CollateralList
array seems to be used only in setup tests. There is no use of this array in any contract functions.
SOLVED: The editCollateralList
function and the CollateralList
array were removed.
// Informational
Solidity contracts can use a special form of comments to provide rich documentation for functions, return variables, and more. This special form is named the Ethereum Natural Language Specification Format (NatSpec).
LMCV.sol
, LMCVProxy.sol
, PSM.sol
, WGLMR.sol
, dPrimeJoin.sol
, dPrime.sol
, CollateralJoin.sol
, CollateralJoinDecimals.sol
PENDING: Natspec documentation is planned for future releases.
// Informational
It is often more optimal to define parameters as calldata instead of memory for external functions when the parameter is only read.
The following parameters of external functions are stored in memory
:
collateralList
, collateralChange
parameters of loan
, repay
and liquidate
functions in LMCV contractcollaterals
and amounts
in createLoan
and repayLoan
functions of LMCVProxy contractAfter changing those parameters from memory
to calldata
, gas usage in unit tests was reduced by around 1000.
SOLVED: Function definitions were updated with calldata
:
Halborn used automated testing techniques to enhance the coverage of certain areas of the smart contract in scope. Among the tools used was Slither, a Solidity static analysis framework. After Halborn verified the smart contract in the repository and was able to compile it correctly into its ABI and binary format, Slither was run against the contract. This tool can statically verify mathematical relationships between Solidity variables to detect invalid or inconsistent usage of the contracts' APIs across the entire code-base.
dPrimeLike is re-used:
- dPrimeLike (contracts/PSM.sol#31-36)
- dPrimeLike (contracts/dPrimeJoin.sol#7-10)
LMCVLike is re-used:
- LMCVLike (contracts/LMCVProxy.sol#24-38)
- LMCVLike (contracts/PSM.sol#12-29)
- LMCVLike (contracts/dPrimeJoin.sol#12-15)
CollateralJoinLike is re-used:
- CollateralJoinLike (contracts/LMCVProxy.sol#14-17)
- CollateralJoinLike (contracts/PSM.sol#38-44)
dPrimeJoinLike is re-used:
- dPrimeJoinLike (contracts/LMCVProxy.sol#19-22)
- dPrimeJoinLike (contracts/PSM.sol#6-10)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused
dPrimeJoinLike.dPrime().dPrime (contracts/PSM.sol#9) shadows:
- dPrimeJoinLike.dPrime() (contracts/PSM.sol#9) (function)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing
CollateralJoinDecimals.constructor(address,address,bytes32,address)._lmcvProxy (contracts/CollateralJoinDecimals.sol#81) lacks a zero-check on :
- lmcvProxy = _lmcvProxy (contracts/CollateralJoinDecimals.sol#89)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation
CollateralJoin.constructor(address,address,bytes32,address)._lmcvProxy (contracts/CollateralJoin.sol#104) lacks a zero-check on :
- lmcvProxy = _lmcvProxy (contracts/CollateralJoin.sol#108)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation
PSM.constructor(address,address,address).treasury_ (contracts/PSM.sol#111) lacks a zero-check on :
- treasury = treasury_ (contracts/PSM.sol#119)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation
LMCVProxy.createLoan(bytes32[],uint256[],uint256) (contracts/LMCVProxy.sol#112-121) has external calls inside a loop: require(bool,string)(ERC20Like(collateralContracts[collaterals[i]]).trans
ferFrom(msg.sender,address(this),amounts[i]),LMCVProxy/collateral transfer failed) (contracts/LMCVProxy.sol#116)
LMCVProxy.createLoan(bytes32[],uint256[],uint256) (contracts/LMCVProxy.sol#112-121) has external calls inside a loop: CollateralJoinLike(collateralJoins[collaterals[i]]).join(msg.sender,amoun
ts[i]) (contracts/LMCVProxy.sol#117)
LMCVProxy.repayLoan(bytes32[],uint256[],uint256) (contracts/LMCVProxy.sol#123-133) has external calls inside a loop: CollateralJoinLike(collateralJoins[collaterals[i]]).proxyExit(msg.sender,a
mounts[i]) (contracts/LMCVProxy.sol#131)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop
Reentrancy in CollateralJoin.exit(address,uint256) (contracts/CollateralJoin.sol#125-130):
External calls:
- lmcv.pullCollateral(collateralName,msg.sender,wad) (contracts/CollateralJoin.sol#127)
- require(bool,string)(collateralContract.transfer(usr,wad),CollateralJoin/failed-transfer) (contracts/CollateralJoin.sol#128)
Event emitted after the call(s):
- Exit(usr,wad) (contracts/CollateralJoin.sol#129)
Reentrancy in CollateralJoin.join(address,uint256) (contracts/CollateralJoin.sol#118-123):
External calls:
- require(bool,string)(collateralContract.transferFrom(msg.sender,address(this),wad),CollateralJoin/failed-transfer) (contracts/CollateralJoin.sol#120)
- lmcv.pushCollateral(collateralName,usr,wad) (contracts/CollateralJoin.sol#121)
Event emitted after the call(s):
- Join(usr,wad) (contracts/CollateralJoin.sol#122)
Reentrancy in CollateralJoin.proxyExit(address,uint256) (contracts/CollateralJoin.sol#132-137):
External calls:
- lmcv.pullCollateral(collateralName,usr,wad) (contracts/CollateralJoin.sol#134)
- require(bool,string)(collateralContract.transfer(usr,wad),CollateralJoin/failed-transfer) (contracts/CollateralJoin.sol#135)
Event emitted after the call(s):
- Exit(usr,wad) (contracts/CollateralJoin.sol#136)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3
Reentrancy in dPrimeJoin.exit(address,uint256) (contracts/dPrimeJoin.sol#69-73):
External calls:
- lmcv.moveDPrime(msg.sender,address(this),RAY * wad) (contracts/dPrimeJoin.sol#70)
- dPrime.mint(usr,wad) (contracts/dPrimeJoin.sol#71)
Event emitted after the call(s):
- Exit(usr,wad) (contracts/dPrimeJoin.sol#72)
Reentrancy in dPrimeJoin.join(address,uint256) (contracts/dPrimeJoin.sol#63-67):
External calls:
- lmcv.moveDPrime(address(this),usr,RAY * wad) (contracts/dPrimeJoin.sol#64)
- dPrime.burn(msg.sender,wad) (contracts/dPrimeJoin.sol#65)
Event emitted after the call(s):
- Join(usr,wad) (contracts/dPrimeJoin.sol#66)
Reentrancy in dPrimeJoin.proxyExit(address,uint256) (contracts/dPrimeJoin.sol#75-79):
External calls:
- lmcv.moveDPrime(usr,address(this),RAY * wad) (contracts/dPrimeJoin.sol#76)
- dPrime.mint(usr,wad) (contracts/dPrimeJoin.sol#77)
Event emitted after the call(s):
- Exit(usr,wad) (contracts/dPrimeJoin.sol#78)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3
dPrime.permit(address,address,uint256,uint256,uint8,bytes32,bytes32) (contracts/dPrime.sol#179-203) uses timestamp for comparisons
Dangerous comparisons:
- require(bool,string)(block.timestamp <= deadline,dPrime/permit-expired) (contracts/dPrime.sol#180)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp
LMCV.either(bool,bool) (contracts/LMCV.sol#615-617) uses assembly
- INLINE ASM (contracts/LMCV.sol#616)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage
console._sendLogPayload(bytes) (node_modules/hardhat/console.sol#7-14) uses assembly
- INLINE ASM (node_modules/hardhat/console.sol#10-13)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage
Different versions of Solidity are used:
- Version used: ['^0.8.0', '^0.8.7']
- ^0.8.0 (node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol#4)
- ^0.8.0 (node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol#4)
- ^0.8.0 (node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol#4)
- ^0.8.0 (node_modules/@openzeppelin/contracts/utils/Context.sol#4)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used
Different versions of Solidity are used:
- Version used: ['0.8.7', '>=0.4.22<0.9.0']
- 0.8.7 (contracts/LMCVProxy.sol#3)
- 0.8.7 (contracts/PSM.sol#2)
- 0.8.7 (contracts/dPrimeJoin.sol#3)
- >=0.4.22<0.9.0 (node_modules/hardhat/console.sol#2)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used
Pragma version^0.8.0 (node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol#4) allows old versions
Pragma version^0.8.0 (node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol#4) allows old versions
Pragma version^0.8.0 (node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol#4) allows old versions
Pragma version^0.8.0 (node_modules/@openzeppelin/contracts/utils/Context.sol#4) allows old versions
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity
Pragma version>=0.4.22<0.9.0 (node_modules/hardhat/console.sol#2) is too complex
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity
dPrimeJoin (contracts/dPrimeJoin.sol#17-80) should inherit from CollateralJoinLike (contracts/LMCVProxy.sol#14-17)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance
Parameter CollateralJoinDecimals.join(address,uint256,address)._msgSender (contracts/CollateralJoinDecimals.sol#96) is not in mixedCase
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions
Contract dPrime (contracts/dPrime.sol#7-204) is not in CapWords
Function dPrime.DOMAIN_SEPARATOR() (contracts/dPrime.sol#57-59) is not in mixedCase
Constant dPrime.version (contracts/dPrime.sol#13) is not in UPPER_CASE_WITH_UNDERSCORES
Variable dPrime._DOMAIN_SEPARATOR (contracts/dPrime.sol#29) is not in mixedCase
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions
Parameter LMCV.setTreasury(address)._treasury (contracts/LMCV.sol#195) is not in mixedCase
Parameter LMCV.editLeverageStatus(bytes32,bool)._leveraged (contracts/LMCV.sol#229) is not in mixedCase
Parameter LMCV.editAcceptedCollateralType(bytes32,uint256,uint256,uint256,uint256,bool)._lockedAmountLimit (contracts/LMCV.sol#248) is not in mixedCase
Parameter LMCV.editAcceptedCollateralType(bytes32,uint256,uint256,uint256,uint256,bool)._dustLevel (contracts/LMCV.sol#249) is not in mixedCase
Parameter LMCV.editAcceptedCollateralType(bytes32,uint256,uint256,uint256,uint256,bool)._creditRatio (contracts/LMCV.sol#250) is not in mixedCase
Parameter LMCV.editAcceptedCollateralType(bytes32,uint256,uint256,uint256,uint256,bool)._liqBonusMult (contracts/LMCV.sol#251) is not in mixedCase
Parameter LMCV.editAcceptedCollateralType(bytes32,uint256,uint256,uint256,uint256,bool)._leveraged (contracts/LMCV.sol#252) is not in mixedCase
Variable LMCV.PSMAddresses (contracts/LMCV.sol#24) is not in mixedCase
Variable LMCV.CollateralList (contracts/LMCV.sol#40) is not in mixedCase
Variable LMCV.CollateralData (contracts/LMCV.sol#41) is not in mixedCase
Variable LMCV.ProtocolDebtCeiling (contracts/LMCV.sol#60) is not in mixedCase
Variable LMCV.MintFee (contracts/LMCV.sol#61) is not in mixedCase
Variable LMCV.AccumulatedRate (contracts/LMCV.sol#62) is not in mixedCase
Variable LMCV.Treasury (contracts/LMCV.sol#69) is not in mixedCase
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions
Contract dPrimeJoinLike (contracts/LMCVProxy.sol#19-22) is not in CapWords
Parameter LMCVProxy.setLMCV(address)._lmcv (contracts/LMCVProxy.sol#75) is not in mixedCase
Parameter LMCVProxy.setDPrimeJoin(address)._dPrimeJoin (contracts/LMCVProxy.sol#80) is not in mixedCase
Parameter LMCVProxy.setDPrime(address)._dPrime (contracts/LMCVProxy.sol#85) is not in mixedCase
Contract dPrimeLike (contracts/PSM.sol#31-36) is not in CapWords
Contract dPrimeJoin (contracts/dPrimeJoin.sol#17-80) is not in CapWords
Contract console (node_modules/hardhat/console.sol#4-1532) is not in CapWords
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions
Reentrancy in WGLMR.withdraw(uint256) (contracts/WGLMR.sol#38-43):
External calls:
- address(msg.sender).transfer(wad) (contracts/WGLMR.sol#41)
Event emitted after the call(s):
- Withdrawal(msg.sender,wad) (contracts/WGLMR.sol#42)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4
console.slitherConstructorConstantVariables() (node_modules/hardhat/console.sol#4-1532) uses literals with too many digits:
- CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67) (node_modules/hardhat/console.sol#5)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits
WGLMR.decimals (contracts/WGLMR.sol#21) should be constant
WGLMR.name (contracts/WGLMR.sol#19) should be constant
WGLMR.symbol (contracts/WGLMR.sol#20) should be constant
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant
name() should be declared external:
- ERC20.name() (node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol#62-64)
symbol() should be declared external:
- ERC20.symbol() (node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol#70-72)
decimals() should be declared external:
- ERC20.decimals() (node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol#87-89)
totalSupply() should be declared external:
- ERC20.totalSupply() (node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol#94-96)
balanceOf(address) should be declared external:
- ERC20.balanceOf(address) (node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol#101-103)
transfer(address,uint256) should be declared external:
- ERC20.transfer(address,uint256) (node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol#113-117)
approve(address,uint256) should be declared external:
- ERC20.approve(address,uint256) (node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol#136-140)
transferFrom(address,address,uint256) should be declared external:
- ERC20.transferFrom(address,address,uint256) (node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol#158-167)
increaseAllowance(address,uint256) should be declared external:
- ERC20.increaseAllowance(address,uint256) (node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol#181-185)
decreaseAllowance(address,uint256) should be declared external:
- ERC20.decreaseAllowance(address,uint256) (node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol#201-210)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external
withdraw(uint256) should be declared external:
- WGLMR.withdraw(uint256) (contracts/WGLMR.sol#38-43)
totalSupply() should be declared external:
- WGLMR.totalSupply() (contracts/WGLMR.sol#45-47)
approve(address,uint256) should be declared external:
- WGLMR.approve(address,uint256) (contracts/WGLMR.sol#49-53)
transfer(address,uint256) should be declared external:
- WGLMR.transfer(address,uint256) (contracts/WGLMR.sol#55-57)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external
. analyzed (31 contracts with 78 detectors), 82 result(s) found
Slither correctly flagged that:
some state variables could be declared as constant
.
some functions can be defined as external
usage of external calls inside a loop
missing zero address checks
Those issues are included in the findings section of the report.
No major issues found by Slither.
Halborn strongly recommends conducting a follow-up assessment of the project either within six months or immediately following any material changes to the codebase, whichever comes first. This approach is crucial for maintaining the project’s integrity and addressing potential vulnerabilities introduced by code modifications.
// Download the full report
* Use Google Chrome for best results
** Check "Background Graphics" in the print settings if needed