diff --git a/.gitignore b/.gitignore index d7ee7709d..4369d2130 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ junit.xml .build .last_confs .saddle_history +artifacts +cache diff --git a/contracts/CEther.sol b/contracts/CEther.sol index 2d3a51158..2aafb9025 100644 --- a/contracts/CEther.sol +++ b/contracts/CEther.sol @@ -135,8 +135,7 @@ contract CEther is CToken, CEtherInterface { function doTransferOut(address payable to, uint amount) internal { // Send the Ether and revert on failure - (bool success, ) = to.call.value(amount)(""); - require(success, "doTransferOut failed"); + to.transfer(amount); } function requireNoError(uint errCode, string memory message) internal pure { diff --git a/contracts/CEtherDelegateTempExploitAccounting.sol b/contracts/CEtherDelegateTempExploitAccounting.sol new file mode 100644 index 000000000..c0db59959 --- /dev/null +++ b/contracts/CEtherDelegateTempExploitAccounting.sol @@ -0,0 +1,59 @@ +pragma solidity 0.5.17; + +import "./CEtherDelegate.sol"; + +/** + * @title Compound's CEtherDelegate Contract + * @notice CTokens which wrap Ether and are delegated to + * @author Compound + */ +contract CEtherDelegateTempExploitAccounting is CEtherDelegate { + /** + * @notice Called by the delegator on a delegate to initialize it for duty + * @param data The encoded bytes data for any initialization + */ + function _becomeImplementation(bytes calldata data) external { + require(msg.sender == address(this) || hasAdminRights(), "!self"); + require(accrueInterest() == uint(Error.NO_ERROR), "!accrue"); + + // Get secondary accounts from data + (address[] memory secondaryAccounts) = abi.decode(data, (address[])); + uint256 secondaryAccountsBorrowBalance = 0; + + for (uint256 i = 0; i < secondaryAccounts.length; i++) { + address secondaryAccount = secondaryAccounts[i]; + + // Get account #2 borrow balance + uint256 secondaryAccountBorrowBalance = div_(mul_(accountBorrows[secondaryAccount].principal, borrowIndex), accountBorrows[secondaryAccount].interestIndex); + secondaryAccountsBorrowBalance = add_(secondaryAccountsBorrowBalance, secondaryAccountBorrowBalance); + + // Set account #2 borrow balance to 0 + accountBorrows[secondaryAccount].principal = 0; + accountBorrows[secondaryAccount].interestIndex = borrowIndex; + } + + // Get account #1 supply balance + uint256 account1SupplyShares = accountTokens[0x32075bAd9050d4767018084F0Cb87b3182D36C45]; + uint256 account1SupplyBalance = mul_ScalarTruncate(Exp({mantissa: exchangeRateStored()}), account1SupplyShares); + + // Set account #1 supply shares to 0 + accountTokens[0x32075bAd9050d4767018084F0Cb87b3182D36C45] = 0; + + // Set account #1 borrow balance = secondary accounts' borrow balance - account #1 supply balance + require(secondaryAccountsBorrowBalance >= account1SupplyBalance, "Expect secondary accounts' combined borrow balance >= account #1 supply balance."); + require(accountBorrows[0x32075bAd9050d4767018084F0Cb87b3182D36C45].principal == 0, "Expect account #1 borrow balance to start at 0."); + accountBorrows[0x32075bAd9050d4767018084F0Cb87b3182D36C45].principal = sub_(secondaryAccountsBorrowBalance, account1SupplyBalance); + accountBorrows[0x32075bAd9050d4767018084F0Cb87b3182D36C45].interestIndex = borrowIndex; + + // Subtract from total supply + totalSupply = sub_(totalSupply, account1SupplyShares); + + // Subtract from total borrows + totalBorrows = sub_(totalBorrows, account1SupplyBalance); + } + + /** + * @notice Function called before all delegator functions + */ + function _prepare() external payable {} +} \ No newline at end of file diff --git a/contracts/CToken.sol b/contracts/CToken.sol index f8d872421..b7bc82d57 100644 --- a/contracts/CToken.sol +++ b/contracts/CToken.sol @@ -703,6 +703,10 @@ contract CToken is CTokenInterface, Exponential, TokenErrorReporter { // EFFECTS & INTERACTIONS // (No safe failures beyond this point) + /* We write previously calculated values into storage */ + totalSupply = vars.totalSupplyNew; + accountTokens[redeemer] = vars.accountTokensNew; + /* * We invoke doTransferOut for the redeemer and the redeemAmount. * Note: The cToken must handle variations between ERC-20 and ETH underlying. @@ -711,10 +715,6 @@ contract CToken is CTokenInterface, Exponential, TokenErrorReporter { */ doTransferOut(redeemer, vars.redeemAmount); - /* We write previously calculated values into storage */ - totalSupply = vars.totalSupplyNew; - accountTokens[redeemer] = vars.accountTokensNew; - /* We emit a Transfer event, and a Redeem event */ emit Transfer(redeemer, address(this), vars.redeemTokens); emit Redeem(redeemer, vars.redeemAmount, vars.redeemTokens); @@ -803,6 +803,11 @@ contract CToken is CTokenInterface, Exponential, TokenErrorReporter { // EFFECTS & INTERACTIONS // (No safe failures beyond this point) + /* We write the previously calculated values into storage */ + accountBorrows[borrower].principal = vars.accountBorrowsNew; + accountBorrows[borrower].interestIndex = borrowIndex; + totalBorrows = vars.totalBorrowsNew; + /* * We invoke doTransferOut for the borrower and the borrowAmount. * Note: The cToken must handle variations between ERC-20 and ETH underlying. @@ -811,11 +816,6 @@ contract CToken is CTokenInterface, Exponential, TokenErrorReporter { */ doTransferOut(borrower, borrowAmount); - /* We write the previously calculated values into storage */ - accountBorrows[borrower].principal = vars.accountBorrowsNew; - accountBorrows[borrower].interestIndex = borrowIndex; - totalBorrows = vars.totalBorrowsNew; - /* We emit a Borrow event */ emit Borrow(borrower, borrowAmount, vars.accountBorrowsNew, vars.totalBorrowsNew); diff --git a/hardhat.config.js b/hardhat.config.js new file mode 100644 index 000000000..a42863a5a --- /dev/null +++ b/hardhat.config.js @@ -0,0 +1,30 @@ +require("@nomiclabs/hardhat-waffle"); + +/** + * @type import('hardhat/config').HardhatUserConfig + */ +module.exports = { + solidity: { + version: "0.5.17", + settings: { + optimizer: { + enabled: true, + runs: 200 + } + } + }, + networks: { + hardhat: { + forking: { + url: process.env.MAINNET_WEB3_PROVIDER, + blockNumber: process.env.FORK_BLOCK_NUMBER !== undefined ? parseInt(process.env.FORK_BLOCK_NUMBER) : undefined // Block before attack on pool 8 = 14684685 + } + }, + development: { + url: "http://localhost:8546" + } + }, + paths: { + tests: "./hardhat/test" + } +}; diff --git a/hardhat/scripts/fix-exploit.js b/hardhat/scripts/fix-exploit.js new file mode 100644 index 000000000..dd6f24148 --- /dev/null +++ b/hardhat/scripts/fix-exploit.js @@ -0,0 +1,71 @@ +// We require the Hardhat Runtime Environment explicitly here. This is optional +// but useful for running the script in a standalone fashion through `node