The main cause of the accident was that Alchemix added 3 vaults through the transmuter, which ultimately led to the wrong revenue through the wrong elements.
Original title: “The longest road I have traveled is my own routine-analysis of the Alchemix incident”
Written by: yudan, working for the SlowMist security team
According to news from the SlowMist Zone, on June 16 , 2021, the alETH contract of the Ethereum DeFi project Alchemix is ​​suspected to have a security problem. On the 17th, Alchemix released an accident analysis report. The SlowMist safety team quickly intervened in the analysis and sorted out the entire context and core key points of the incident based on the official analysis report for your reference.
Too long to watch the series
This analysis article is very long. Let me talk about the conclusion first, so that everyone can have a general understanding. The main reason for this accident is that Alchemix added 3 vaults through transmuter, which resulted in the revenue information being recorded on a wrong element, and the correct index value was not passed in when the harvest function of transmuter was called, resulting in passing the wrong element. The wrong income was obtained, and the wrong income of 4300 ETH was sent to the adapter contract to help the user repay the loan of alETH, which caused the problem of increased income and led to tragedy.
Core analysis-Round 1
According to the official accident analysis report, the cause of the accident was that the official alETH deployment script accidentally created additional vaults, which caused Alchemix to use the wrong index in the vaults array and calculate the wrong reward, which caused the transmuter to take all The reward is used to repay all the user’s liabilities. I know that this brief analysis alone is a bit confusing and confusing, so we can only focus on the official transaction and see if we can find the truth.
According to the official transaction, analyzed by the ethtx.info analysis tool, we can easily find that this transaction called the harvest function of the AlchemistEth contract, and passed the parameter _vaultId=0, and finally returned “4308144937764982868765” and These two values ​​are “4308144937764982866415”.
In order to better understand the role of the harvest function, we need to analyze the entire function:
It is not difficult to find that the harvest function actually contains two important operations, namely harvesting rewards and distributing the rewards to the transmuter contract. Where vault is a library contract, and the harvest logic is implemented as follows:
Through code analysis, it is not difficult to find that the harvest function of the vault library contract actually checks the total amount of funds of the external adapter, and then calculates the revenue part based on the amount of funds in the adapter minus the user’s recharge amount.
Here we can understand this adapter as a strategy pool for managing users’ funds and income. Then we return to the harvest function in the AlchemistEth contract at the beginning of the user, and find that the returned values ​​of “4308144937764982868765” and “4308144937764982866415” actually correspond to the number of tokens that need to be withdrawn calculated by the harvest function of the vault library contract and from The number of tokens retrieved in the adapter (strategy pool). Since the income token corresponding to this adapter is WETH, with an accuracy of 18 digits, the value of “4308144937764982866415” is converted to “4308.144937764982866415” WETH.
That is to say, this harvest operation has benefited more than 4300 ETH, and then this benefit will be distributed to the transmuter contract through the _distributeToTransmuter function in the next step. Let’s look at the logic of the distribution process:
The logic of the _distributeToTransmuter function is only three simple lines. Our main concern is the last external call-the lowerHashMinted function. The xtoken corresponding to this function here refers to alETH itself. Because alETH itself is borrowed by users through loans, the operation of lowerHashMinted here is actually to use the income of harvest to reduce the total amount of alETH loaned out, thereby reducing the loan for each user. In summary, the proceeds of harvest 4300 ETH will be used to repay the user’s alETH loan.
Make a small summary
Here is a summary of the process, that is, the AlchemistEth contract obtains 4300 ETH through the harvest function, and distributes this income to repay the user’s alETH loan, which led to the situation we saw-alETH has been loaned Of users can get back their pledged ETH without repayment. So why is there a profit of 4300 ETH? How did the extra 4300 ETH come from? In response to this problem, we start the next round of analysis.
Core analysis-Round 2
To understand why the extra 4300 ETH was added, it is necessary to understand AlchemistEth’s fund storage process. In the AlchemistEth contract, the total recharge status of the contract is recorded using the Data structure of the Vault library, and then the corresponding recharge amount (totalDeposit) is updated through the flushActiveVault function.
Then the depositAll function will transfer the amount of the recharged tokens to the corresponding adapter (strategy pool), then in the next harvest, the totalValue obtained through the adapter (strategy pool) will be the user’s principal plus the strategy pool income. In order to calculate the principal part of the income process, we debugged the official transaction and found that the principal was only 9000 ETH, and the income obtained from the adapter plus the principal amounted to 13000 ETH, which means that the principal of 9000 ETH was generated With a gain of 4300 ETH.
However, according to the logic analyzed above, the user’s principal will not generate such a large income. The problem must be the totalValue obtained by the adapter. In other words, the adapter not only has AlchemistEth recharge tokens, but also has other income channels. In order to verify our ideas, the SlowMist security team analyzed all the token income of the adapter and found an abnormal transfer behavior , and the amount was just right for the extra 4300 ETH income. In other words, the problem lies here.
By looking at the transaction data, it is found that this is a transaction that calls the harvest operation, and the contract called is the transmuter contract:
In other words, the harvest function has a problem. The logic of the harvest function is as follows:
It is also called the harvest function of vault, the familiar formula, the familiar taste. We debugged again and found an amazing fact- when the revenue was performed, the totalDeposit of the vault turned out to be 0 , resulting in the 4300 ETH revenue being directly distributed to the adapter, which caused the totalValue obtained by the adapter to be wrong, and 4300 ETH more. , The reason is here.
At this point, we are very close to the truth. What remains to be solved is why totalDeposit is 0? We inquired where totalDeposit can be changed in the transmuter contract, and found that only the _plantOrRecallExcessFunds function can change this value, and the upper layer of this function calls the distribute function. The distribute function of the transmuter contract is called by the AlchemistEth contract when it earns. In other words , the process itself should be:
- AlchemistEth contract calls harvest for revenue
- The AlchemistEth contract calls the distribute function of the transmuter contract to record the income and gives the income part to the adapter
- The adapter receives the proceeds from the transmuter and repays the user’s alETH loan based on the proceeds
But the problem lies in the _plantOrRecallExcessFunds function. Since _vaults.last() is used to obtain the latest vault when recording the recharge information, the recharge information is actually superimposed on the last element. But the project party called the setActiveVault function three times, so the recharge information is actually superimposed on the 3rd element of the _vaults array, that is, the vault element with index 2. But the _vaultId passed in when the transmuter contract was harvested was 0, and element 0 has no recharge record, so the transmuter contract mistakenly gave all the proceeds to the adapter. Caused the tragedy to happen.
to sum up
At this point, the whole thing has become very clear. For some reason, the Alchemix project team added 3 vaults through transmuter, resulting in the revenue information being recorded on a wrong element, and there was no when calling the harvest function of transmuter. Passing in the correct index value results in the wrong income being obtained through the wrong element, and the wrong income is sent to the adapter contract, which increases the income and leads to tragedy.
The SlowMist security team hereby reminds that DeFi is a complex system. When performing DeFi operations, remember to check every process in the business logic to prevent accidents. If necessary, you can contact a professional security team to proceed. Professional safety audit to prevent accidents.
Reference link
Official accident analysis report:
https://forum.alchemix.fi/public/d/137-incident-report-06162021Earnings calculation error trading
https://etherscan.io/tx/0x3cc071f9f40294bb250fc7b9aa6b2d7e6ca5707ce4d6d222157d7a0feef618b3
Source link:mp.weixin.qq.com
Disclaimer: As a blockchain information platform, the articles published on this site only represent the author’s personal views, and have nothing to do with the position of ChainNews. The information, opinions, etc. in the article are for reference only, and are not intended as or regarded as actual investment advice.