User can double mint illuminate PTs through cross-function re-entrancy.
Summary
Due to user controlled input of Yield Space pool address, it is possible to control the flow such that a user can lend to the lender, then control the flow to call mint() and then finish the lend call to mint yet again.
Vulnerability Detail
Lender contract allows a user to lend underlying tokens to the Lender contract, and then receive illuminate PT tokens back in return. However, due to the user-input controlled y argument (Yield Space Pool for the principal token), it is possible for the user to control write their own contract to control the returned values when IYield(y) functions are called by lender contract, and then receive the transferred tokens back to the user controlled Yield Space pool contract, then control the flow to call mint() that will deposit princial token to lend contract and mint the difference in balance. Hence being able to mint twice within the same transaction and at same time receive underlying tokens from lender.
Impact
Loss of funds.
Code Snippet
contract A is IYield {
ILender Lender = lenderContractAddress;
address underlying = underlyingAddress;
uint256 amount = 1000e18;
address fyToken = principalToken;
uint8 p = principalValue;
uint256 m = maturity;
function startAttack() public {
address principal = IMarketPlace(marketPlace).token(u, m, p);
fyToken = principal;
IERC20(underlying).approve(address(Lender),
Lender.lend(p, underlying, maturity, amount, address(this), 0)
}
function sellBasePreview(uint128 _amount) external pure returns (uint128) {
return _amount;
}
function sellBase(address receiver, uint128 _amount) external {
IERC20(fyToken).approve(address(Lender), _amount);
Lender.mint(p, underlying, m, _amount);
}
}
The skeletal contract depicts how the double mint of illuminate PTs can happen while the attacker only have to pay once using the market's principal token and not the underlying token since the user will receive back the underlying token during the lending and minting.
Assume Alice creates contract A and contract A has 1000e18 of underlying token and principal token for a market.
Alice calls contractA.startAttack()
ContractA approves lender contract to spend underlying token, and then calls lend()
this would have underlying token transferred from contractA to lender contract, and pass the necessary checks.
The call to IYield(y).sellBase() would have the contractA approve lender contract to spend the principal token for the market, and then call lender contract's mint contract.
cryptphi
high
User can double mint illuminate PTs through cross-function re-entrancy.
Summary
Due to user controlled input of Yield Space pool address, it is possible to control the flow such that a user can lend to the lender, then control the flow to call mint() and then finish the lend call to mint yet again.
Vulnerability Detail
Lender contract allows a user to lend underlying tokens to the Lender contract, and then receive illuminate PT tokens back in return. However, due to the user-input controlled
y
argument (Yield Space Pool for the principal token), it is possible for the user to control write their own contract to control the returned values when IYield(y) functions are called by lender contract, and then receive the transferred tokens back to the user controlled Yield Space pool contract, then control the flow to call mint() that will deposit princial token to lend contract and mint the difference in balance. Hence being able to mint twice within the same transaction and at same time receive underlying tokens from lender.Impact
Loss of funds.
Code Snippet
The skeletal contract depicts how the double mint of illuminate PTs can happen while the attacker only have to pay once using the market's principal token and not the underlying token since the user will receive back the underlying token during the lending and minting.
Assume Alice creates contract A and contract A has 1000e18 of underlying token and principal token for a market.
Alice calls contractA.startAttack()
ContractA approves lender contract to spend underlying token, and then calls lend()
this would have underlying token transferred from contractA to lender contract, and pass the necessary checks.
In the call in Lender.yield(), the lender transfers the underlying token to the Yield Space Pool, since
y
input is user controlled and is contractA address, contract A will receive the underlying tokens back. https://github.com/sherlock-audit/2022-10-illuminate/blob/main/src/Lender.sol#L943The call to IYield(y).sellBase() would have the contractA approve lender contract to spend the principal token for the market, and then call lender contract's mint contract.
This will transfer principal tokens to lender, then mint Illuminate Principal tokens (1000e18 iL PTs) https://github.com/sherlock-audit/2022-10-illuminate/blob/main/src/Lender.sol#L270-L288
The difference in balance is checked and lend() would complete the call to mint the balance difference returned (1000e18) https://github.com/sherlock-audit/2022-10-illuminate/blob/main/src/Lender.sol#L332-L347
ContractA would receive 1000e18 illuiminate Principal Tokens again.
ContractA now has 2000e18 worth of illumainate Principal Tokens and still 1000e18 worth of underlying tokens.
Tool used
Manual Review
Recommendation
Duplicate of #179