Lendroid Protocol version 1.0

QuantstampFiles named in the report

Quantstamp reviewed Lendroid Whalestreet (Lendroid Protocol version 1.0) on 18 Feb 2019: 15 findings, 0 critical; 11 marked fixed, 4 open.

No open critical findings
Open the reportOpen Lendroid Whalestreet’s security record

Report hosted on certificate.quantstamp.com.

Trustblock does not review code.

Findings15

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High000
Medium006
Low405
MediumClashing Position HashFixed

Added as part of the second report. position_hash() can be called for two or more separate positions with the same arguments. This method mitigates this possibility by adding a _nonce value but the validation is insufficient. Exploit Scenario: 1. User calls fill_kernel() once with _is_creator_lender set as true 2. open_position() is called with _kernel_creator = lender 3. L280 _nonce is validated for the wrangler and the creator (lender) 4. User calls fill_kernel() again with the same arguments but with _is_creator_lender set as false 5. open_position() is called with the same arguments except for _kernel_creator = borrower 6. L280 _nonce is validated for the wrangler and the creator (borrower), allowing for the same _nonce value to pass 7. This results in the position_hash being the same for both positions 8. Anything that relies on position_hash will be unable to distinguish between multiple positions

MediumDebugging Code Is Present While Real Logic Is NotFixed

L452, 453 In cancel_kernel, debugging code is active, rendering the function useless if deployed as it is.

MediumIncorrect Logic When Calculating Filled or Cancelled Loan AmountFixed

The same value self.kernels_filled[_kernel_hash] is being added twice, whereas the function should be taking the sum of the filled and cancelled loan amounts.

MediumIncorrect Logic When Calculating Total InterestFixed

L247 _position_duration_in_days is not taken into account when _total_interest is calculated. This doesn't match up with how loanAmountOwed is calculated in SimpleLoan.sol, the Solidity implementation that we used as a reference.

MediumIncorrect Logic When Cancelling KernelFixed

L459 checks if collateralAmount - filledOrCancelledLoanAmount >= cancelledLoanTokenAmount. This should instead be loanAmountOffered - filledOrCancelledLoanAmount >= cancelledLoanTokenAmount according to the Solidity implementation found in Kernel.sol

MediumLending and Borrowing Positions Can Be OverwrittenFixed

borrow_positions (L209) and lend_positions (L211) can be unexpectedly overwritten because the indices (last_borrow_position_index and last_lend_position_index) are not incremented/decremented correctly. Exploit Scenario: 1. User borrows twice by calling open_position(). Assume the resulting _position_hashs are 0x123 and 0x456 2. Both positions get recorded in self.borrow_positions[_borrower]. eg ({1 : 0x123, 2: 0x456}) 3. At this point, self.last_borrow_position_index[_borrower] == 2 4. User closes position 0x123 by calling close_position() 5. remove_position() L217 gets called, decrementing self.last_borrow_position_index[_borrower] 6. At this point, self.last_borrow_position_index[_borrower] == 1 7. User borrows again, self.borrow_positions[user_address][2] is overwritten with the new borrow position. The borrowing data for 0x456 is lost

LowCreator Signature Is Not VerifiedFixed

L429 assert _kernel_creator == ecrecover(_k_hash,...) is commented out. Exploit Scenario: Malicious actors may pose as a creator

LowOther Code Defects/SuggestionsFixed

The following is a list of code defects that are bugs, do not adhere to best practices, affect gas use, or are deprecated/obsolete.

LowReentrancyFixed

A reentrancy vulnerability is a scenario where an attacker can repeatedly call a function from itself, unexpectedly leading to potentially disastrous results. Here's a basic example representing the very attack which impacted The DAO in 2016: function withdraw_with_reentrancy(uint256 _amount) public { msg.sender.call.value(_amount)(); // ATTACKER CAN CALL AGAIN BEFORE // FUNCTION TERMINATES because `call` // allows msg.sender to execute any code using fallback function balances[msg.sender] -= _amount; // Balance only updated AFTER funds withdrawn } Exploit Scenario: 1. L331 When calling liquidate_position() the function checks that the position status is POSITION_STATUS_OPEN 2. L333 The transfer() method on an external ERC20 contract is called 3. There could be a re-entrancy exploit that calls liquidate_position() again and transfer tokens 4. L337 Position is finally updated to POSITION_STATUS_LIQUIDATED

LowRemoved AssertionsFixed

Added as part of the second report Assertions have been removed with an added comment that refers to the error message "May not call non-constant function function_name within an assertion". The assertions should however be kept. In order to resolve the error message, return values should be saved in a temporary variable and used for the assertion afterwards.

Show 1 more
LowWorking With Inconsistent ERC20 ImplementationsFixed

The ERC20 standard stipulates that the transfer() function returns true on success. The Lendroid protocol makes external calls to transfer() in functions open_position() topup_position() liquidate_position() and close_position() asserting that the result is true (the transactions will fail otherwise). An adversarial implementation of the ERC20 token can consistently or occasionally return false on transfer() calls, which will make these methods fails. The Ethereum network does not provide the Lendroid protocol with any means of checking that an external token implements the ERC20 standard correctly. It is up to the users to verify the source codes of ERC20 tokens that they intend to use the Lendroid protocol with, however, the Lendroid team needs to inform the users about these limitations. Exploit Scenario: 1. User puts up token A as collateral and borrows a malicious Token B. 2. User tries to retrieve his deposit by calling close_position() and returning token B 3. Token B is programmed so that it fails for all transfers coming from user B 4. The lender waits until the position expires and liquidates the position, successfully collecting user B's deposit

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (2)

  • contracts/ERC20.v.py
  • contracts/protocol.v.py

Part of Lendroid Whalestreet’s security record

Lendroid Whalestreet
Audits2 audits by 1 firmLast audit Nov 2020, by Quantstamp.
Critical findingsNo open critical findingsAcross 2 reports.
Open Lendroid Whalestreet’s security record

Security firm

Quantstamp

236 projects reviewed · 332 reports · latest Oct 2026

See Quantstamp’s audits

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us