Submitted on Fri Jul 26 2024 12:33:21 GMT-0400 (Atlantic Standard Time) by @Kalogerone for Boost | Folks Finance
Report ID: #33687
Report type: Smart Contract
Report severity: Medium
Target: https://testnet.snowtrace.io/address/0x2cAa1315bd676FbecABFC3195000c642f503f1C9
Impacts:
Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)
Description
Brief/Intro
A user who tries to create a loan has to choose the loanId
. Any user can frontrun this transaction with the same loanId
, making the initial user's transaction to revert because his selected loanId
is taken.
Vulnerability Details
Each loan has a unique bytes32
identifier named loanId
. During the loan creation, each user is asked to provide the loanId
that his loan will have.
Copy SpokeCommon .sol
function createLoan (
Messages.MessageParams memory params ,
bytes32 accountId ,
@ > bytes32 loanId ,
uint16 loanTypeId ,
bytes32 loanName
) external payable nonReentrant {
_doOperation (params , Messages . Action .CreateLoan , accountId , abi .encodePacked (loanId , loanTypeId , loanName));
}
Copy SpokeToken .sol
function createLoanAndDeposit (
Messages.MessageParams memory params ,
bytes32 accountId ,
@ > bytes32 loanId ,
uint256 amount ,
uint16 loanTypeId ,
bytes32 loanName
) external payable nonReentrant {
_doOperation (
params ,
Messages . Action .CreateLoanAndDeposit ,
accountId ,
amount ,
abi .encodePacked (loanId , poolId , amount , loanTypeId , loanName)
);
}
This arbitrary loanId
value is sent through a bridge to the Hub.sol
contract which in turn calls the createUserLoan
function is LoanManager.sol
.
Copy Hub .sol
function _receiveMessage (Messages.MessageReceived memory message) internal override {
Messages .MessagePayload memory payload = Messages .decodeActionPayload ( message .payload);
.
.
.
} else if ( payload .action == Messages . Action .CreateLoan) {
bytes32 loanId = payload . data .toBytes32 (index);
index += 32 ;
uint16 loanTypeId = payload . data .toUint16 (index);
index += 2 ;
bytes32 loanName = payload . data .toBytes32 (index);
@ > loanManager .createUserLoan (loanId , payload .accountId , loanTypeId , loanName);
} else if ( payload .action == Messages . Action .DeleteLoan) {
bytes32 loanId = payload . data .toBytes32 (index);
loanManager .deleteUserLoan (loanId , payload .accountId);
} else if ( payload .action == Messages . Action .CreateLoanAndDeposit) {
bytes32 loanId = payload . data .toBytes32 (index);
index += 32 ;
uint8 poolId = payload . data .toUint8 (index);
index += 1 ;
uint256 amount = payload . data .toUint256 (index);
index += 32 ;
uint16 loanTypeId = payload . data .toUint16 (index);
index += 2 ;
bytes32 loanName = payload . data .toBytes32 (index);
@ > loanManager .createUserLoan (loanId , payload .accountId , loanTypeId , loanName);
loanManager .deposit (loanId , payload .accountId , poolId , amount);
// save token received
receiveToken = ReceiveToken ({poolId : poolId , amount : amount});
} else if ( payload .action == Messages . Action .Deposit) {
.
.
.
Copy LoanManager .sol
function createUserLoan (
bytes32 loanId ,
bytes32 accountId ,
uint16 loanTypeId ,
bytes32 loanName
) external override onlyRole (HUB_ROLE) nonReentrant {
// check loan types exists, is not deprecated and no existing user loan for same loan id
if ( ! isLoanTypeCreated (loanTypeId)) revert LoanTypeUnknown (loanTypeId);
if ( isLoanTypeDeprecated (loanTypeId)) revert LoanTypeDeprecated (loanTypeId);
@ > if ( isUserLoanActive (loanId)) revert UserLoanAlreadyCreated (loanId);
// create loan
UserLoan storage userLoan = _userLoans[loanId];
userLoan .isActive = true ;
userLoan .accountId = accountId;