We regularly publish short recaps on a decentralized audit in which we participated. This time, we cover the audit of Taurus Protocol.
brainbot is a web3 service provider, offering consulting and development services as well as smart contract audits. To gain more experience in auditing, our security researchers regularly participate in decentralized audits. In this series, we will publish recaps of audits in which we participated in order to provide some insight into the functioning, the smart contract architecture and our findings for the respective protocols.
The Taurus protocol offers liquidity on interest-bearing tokens. Depositors can mint Taurus' stablecoin ($TAU) by collateralizing their tokens. Taurus is an over-collateralized stablecoin protocol that allows users to borrow $TAU against the value of the collateral deposited. The collateral consists of interest-bearing tokens from perpetual swap platforms built on Arbitrum.
Unknown at this stage – Taurus Protocol is currently working in "stealth mode".
Similar to MakerDAO's DAI and Liquity's LUSD, Taurus' smart contracts allow depositors to collateralize and mint the stablecoin $TAU. However, Taurus requires depositors to use yield-bearing collateral tokens like GLP, which earns rewards and fees. Taurus collects the yield from deposited collateral and uses it to repay users' debts, continuously reducing their debt. Unlike MakerDAO and Liquity, Taurus does not charge direct stability fees on loans made through its vaults.
We believe that there are specific aspects of the Taurus protocol that require further investigation to determine if they are implemented correctly or if there are areas for improvement. Some of the items we want to examine include:
1. How does Taurus distribute repaid debt among its users?
If rewards are sold and immediately written on-chain, is it possible for an attacker to sandwich the yield by entering the contract and then immediately exiting after the sold rewards have been used to repay users' debts?
Within the modifier, the _disburseTau()
function is called to distribute available yield from the drip feed. The tauWithheld
variable holds the cumulative TAU for repaying the debt of users. In order to prevent sandwich attacks, Taurus spreads the tauWithheld
over a time interval. TAU is distributed according to the formula tokensToDisburse = (_timeElapsed * tauWithheld) / DRIP_DURATION
; where timeElapsed
is the time passed since the last time disburseTau
was called and DRIP_DURATION
is the maximum amount of spread, which is 1 day.
For example, if there are 100 TAU to repay the debt of users, tauWithheld
will be set to 100, and this TAU will be repaying the debt of the users for the entire day, increasing linearly up to reach 100 after 1 day. So after 12 hours, 50 TAU will be repaid, and after another 12 hours, the remaining 50 TAU will be repaid to users. Since TAU is repaid every second for a day, there are no sandwich attack vulnerabilities.
Overall, the Taurus protocol's updateReward
modifier and disburseTau function provide a secure and effective way to distribute rewards and repay debts.
2. Is there a risk of keepers acting maliciously and not selling the rewards properly, given that the rewards will be collected from the vault contract and sold for TAU in the Taurus protocol?
Unfortunately, there is a risk. During our audit process, we found a critical vulnerability in the keeper functions, as well as some logical errors. The code snippet below shows the keeper's swap function:
As can be seen, the inputs to the keepers are not thoroughly checked. Here are some of the critical issues we found:
Before we delve into these issues, let's first understand what the rewardProportion
variable that the keeper is passing to the function means. According to the comment in the Taurus original code:
As we can see from the example provided in the comment, rewardProportion
should be an integer 0 < rewardProportion
< 1e18. However, there are no checks inside the contract verifying that rewardProportion
is within these limits. If a keeper were to give a very large integer value for this variable, then basically all of the debt would be repaid from the users. So the keeper be clearing the debt of the users. Let's dig even deeper!
The rewardProportion
variable is used as an input of the_withholdTau()
function, which is responsible for incrementing the tauWithheld
variable, which is the TAU used to repay the users' debt.
As you may recall from the previous finding, tauWithheld is used in the _disburseTau()
function. The following lines in the _disburseTau()
function are important to check:
It has been discovered that the rewardProportion
input by the keeper can be manipulated to cause an issue in the updateReward
modifier. Specifically, if a very large value is inputted, the resulting _tokensToDisburse
will also be very high, leading to an excessively high cumulativeTauRewardPerCollateral
.
If a user then calls the updateReward
modifier, the following lines of code become problematic:
Since the cumulativeTauRewardPerCollateral
has been artificially increased, the _rewardDiff
and resulting _tauEarned
values will be excessively high. In this case, if the tau earned is greater than the user's debt, the debt will be completely erased. This is clearly an unintended behavior and needs to be addressed.
Taurus protocol enables depositors to use yield-bearing tokens as collateral to generate the stablecoin $TAU. Unlike traditional systems, Taurus collects all reward tokens from the deposited collateral and sells them for $TAU to repay users' debts, rather than allowing the collateral position to increase. However, we believe this system may face challenges for several reasons.
Firstly, Taurus requires external liquidity to enable keepers to sell reward tokens for $TAU to repay debts. If there is insufficient liquidity to cover the constant sell pressure, trades may be inefficient, resulting in a return of $TAU that does not fully compensate the sold reward token amount. Even with sufficient external liquidity, swap fees will impact the yield's power, leading to a loss when converting reward tokens to $TAU. In addition to these, Taurus also takes some of the reward tokens as fee to their treasury.
Additionally, users must generate some $TAU and create liquidity pools elsewhere for the keepers to obtain the necessary liquidity. While there is currently no information on how to incentivize these liquidity providers, we speculate that Taurus governance token TGT will be awarded to attract more liquidity to these pools.
Lastly, depositors must generate some $TAU on their collateral position; otherwise, the yield of the collateral will be utilized to cover the debts of other $TAU generators, leading to potential complications.
If a user is not generating TAU over their yield-bearing collateral, the collateral will be used to repay the debt of other TAU generators. To optimize the use of their collateral, users must generate some TAU. This ensures that they can fully benefit from their collateral while avoiding the risk of inadvertently helping to repay the debt of other users. By doing so, they can maximize the returns on their investments and maintain a fair and balanced ecosystem within the Taurus protocol.
We'll continue to regularly publish audit recaps for different protocols. Meanwhile, you can also have a look at our other publications on Medium.
You can also find our previous audit recaps on our overview page.
Decentralized Audit Platform: Sherlock
Audited Protocol: Taurus Protocol
Security Researchers:
Côme du Crest (ranked 2/159 in this contest),
Murathan Selimoğlu (ranked 10/159 in this contest)