# [takadao-2023/04/30] Takadao Security Review ###### tags: `private`, `takaDao` # Introduction A security review of the Goat Trading protocol was done by Trumpero team. This audit report includes all the vulnerabilities, issues and code improvements found during the security review. # Disclaimer A smart contract security review cannot assure the absolute absence of vulnerabilities. It involves a constrained allocation of time, resources, and expertise to identify as many vulnerabilities as possible. I cannot provide a guarantee of 100% security following the review, nor can I guarantee that any issues will be discovered during the review of your smart contracts. # About Trumpero Team The Trumpero team was established by two independent smart contract researchers, Trungore and duc, who share a profound interest in Web3 security. Demonstrating their capabilities through numerous audits, contests, and bug bounties, the team is dedicated to contributing to the blockchain ecosystem and its protocols by investing significant time and effort into security research and reviews. Twitter - [Trungore](https://twitter.com/Trungore), [duc](https://twitter.com/duc_hph) \ Sherlock - [Trumpero](https://audits.sherlock.xyz/watson/Trumpero) \ Code4rena - [KIntern_NA](https://code4rena.com/@KIntern_NA) # Severity Classification **High** - leads to a significant material loss of assets in the protocol or significantly harms a group of users. **Medium** - only a small amount of funds can be lost (such as leakage of value) or a core functionality of the protocol is affected. **Low** - can lead to any kind of unexpected behaviour with some of the protocol's functionalities that's not so critical. # Protocol Overview Takaturn are mini DAOs that are created for a defined period of time to fund a cooperative loan. Participants of the DAO pay a predetermined amount into the DAO contract on a regular basis (weekly or monthly) and take turns to receive the entirety of the DAO fund (the “money pot”). Each participant puts up collateral to create a trustless system. At the end of the DAO term, everyone will have been the beneficiary once and all collateral will be returned. ![](https://hackmd.io/_uploads/rJTaj9iS3.png) For further background of Takadao protocol, refer to: * whitepaper: https://takadao.gitbook.io/takaturn-whitepaper/ * docs: https://app.gitbook.com/o/ot8mO8k6Wyg8WohrniYU/home # Security Assessment Summary **_review commit hash_ - [6bb7a13ecb08aed08811239b963fac90b44e6ada](https://github.com/TakafulDAO/takaturn/commit/6bb7a13ecb08aed08811239b963fac90b44e6ada)** ## Scope The following smart contracts were in scope of the audit: - `Collateral.sol` - `Fund.sol` - `TakaturnFactory.sol` The following number of issues were found, categorized by their severity: - High: 2 issues - Medium: 4 issues - Low: 5 issues # Findings Summary | ID | Title | Severity | Status | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- | | [H-01](#H01) | Incorrect calculation of remainingCycles in the _isUnderCollaterized function | HIGH | Pending | | [H-02](#H02) | USDC blacklist is vulnerable for the process of `requestContribution()` | HIGH | Pending | | [M-01](#M01) | Oracle data can be outdated | MEDIUM | Pending | | [M-02](#M02) | Missing check for the status of Arbitrum sequencer | MEDIUM | Pending | | [M-03](#M03) | The collateral will be distributed unfairly if all participants are undercollateralized | MEDIUM | Pending | | [M-04](#M04) | Only the first participant is checked in the `initiateFundContract` function | MEDIUM | Pending | | [L-01](#L01) | Redundant conversion `address(msg.sender)` | LOW | Pending | | [L-02](#L02) | There is no need to initialize the value to 0 | LOW | Pending | | [L-03](#L03) | Inconsistent use of `require` and `revert` | LOW | Pending | | [L-04](#L04) | Should check if `contributionPeriod` is less than `cycleTime` in constructor of Fund contract | LOW | Pending | | [L-05](#L05) | The `closeFundingPeriod` and `startNewCycle` functions should not restrict the caller to only the owner | LOW | Pending | # Detailed Findings ## <a id="High"></a>High ### <a id="H01"></a> [H-01] Incorrect calculation of remainingCycles in the _isUnderCollaterized function #### Description The `_isUnderCollateralized` function verifies whether a user's collateral is below 1.0x of their total contribution amount. This function calculates the `collateralLimit` and checks if the user's collateral in USD is below that limit, returning a boolean value indicating the result. ```solidity= if (fundContract == address(0)) { collateralLimit = totalParticipants * contributionAmount * 10 ** 18; } else { uint remainingCycles = 1 + counterMembers - _fundInstance.currentCycle(); collateralLimit = remainingCycles * contributionAmount * 10 ** 18; // Convert to Wei } ``` When a user participates in a funding cycle (`fundContract` != `address(0)`), the calculation of the `collateralLimit` involves the usage of the `remainingCycles`. However, there is an issue with the calculation of `remainingCycles`. The problem arises from the fact that the `counterMembers` value never changes during the funding cycle, even though the actual `remainingCycles` should decrease if `Fund.totalAmountOfCycles` decreases (due to someone being expelled). This leads to an incorrect assessment of collateralization for the user. As a result, users may be considered undercollateralized and expelled, even when their collateral is sufficient to contribute to the remaining cycles. #### Impact User might be undercollateralized and expelled even when their collateral are enough to contribute for the remaining cycles. #### Code Snippet https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Collateral.sol#L365 #### Tool used Manual Review #### Recommendation The calculation of remainingCycles should be based on the Fund.totalAmountOfCycles variable instead of counterMembers --- ### <a id="H02"></a> [H-02] USDC blacklist is vulnerable for the process of `requestContribution()` #### Description USDC has a special mechanism that reverts any transfer transaction containing a blacklisted address. You can refer to [this link](https://twitter.com/usdcblacklist?lang=en) to stay updated on the blacklist. Let's consider the scenario where a user who has joined the fund becomes blacklisted in a later cycle. Assume that Bob has joined the fund as a beneficiary and has enabled autoPay, which automatically pays the USDC in each cycle. In cycle `x`, Bob gets blacklisted, causing the `Fund.closeFundingPeriod()` function to revert. The reason for this scenario is that the `Fund.closeFundingPeriod()` function attempts to call `_autoPay()` during each call. This internal function, in turn, calls `_payContribution()` to transfer USDC from users who have sufficient balance and allowance to the Fund contract. ```solidity= /// link: https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L340-L351 function _payContribution(address payer, address participant) internal { // Get the amount and do the actual transfer // This will only succeed if the sender approved this contract address beforehand uint amount = contributionAmount; bool success = stableToken.transferFrom(payer, address(this), amount); require(success, "Contribution failed, did you approve stable token?"); // Finish up, set that the participant paid for this cycle and emit an event that it's been done paidThisCycle[participant] = true; emit OnPaidContribution(participant, currentCycle); } ``` If the function is applied to Bob, it will revert due to the presence of a blacklisted address. Consequently, the entire process of the `Fund.closeFundingPeriod()` function will also revert. #### Impact Users are unable to take out the loan due to the transaction being reverted. #### Code Snippet https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L324-L351 #### Tool used Manual Review #### Recommendation Using try/catch when executing transfer the USDC tokens. --- ## <a id="Medium"></a>Medium ### <a id="M01"></a> [M-01] Oracle data can be outdated #### Description The `lastRoundData()`'s parameters according to [Chainlink](https://docs.chain.link/docs/data-feeds/price-feeds/api-reference/) are the following: ```solidity= function latestRoundData() external view returns ( uint80 roundId, // The round ID. int256 answer, // The price. uint256 startedAt, // Timestamp of when the round started. uint256 updatedAt, // Timestamp of when the round was updated. uint80 answeredInRound // The round ID of the round in which the answer was computed. ) ``` These return value are meant to be used to do some [checks](https://docs.chain.link/data-feeds/historical-data#getrounddata-return-values) before updating the price. By just getting the price, user will be affected by the stale price and incomplete rounds. #### Impact Function `Collateral.getLatestPrice()` calls out to a Chainlink oracle receiving the `lastestRoundData()`. If there is a problem with Chainlink starting a new round or finding consensus on the new value for the oracle (e.g. chainlink nodes abandon the oracle, chain congestion, vulnerability/attacks on the chainlink system), the contract will continue using outdated / incorrect data. This can lead to some serious screnarios when calculating the collateral value of a user and make it expelled by mistake. #### Code Snippet https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Collateral.sol#L320-L328 #### Tool used Manual Review #### Recommendation Add the recommended checks: ```solidity= ( uint80 roundID, int256 price, , uint256 timeStamp, uint80 answeredInRound ) = priceFeed.latestRoundData(); require( timeStamp != 0, “ChainlinkOracle::getLatestAnswer: round is not complete” ); require( answeredInRound >= roundID, “ChainlinkOracle::getLatestAnswer: stale data” ); require(price > 0, "Chainlink Malfunction”); ``` --- ### <a id="M02"></a> [M-02] Missing check for the status of Arbitrum sequencer #### Description The protocol wants to deploy for Arbitrum. Chainlink recommends that all Arbitrum oracles consult the Sequencer Uptime Feed to ensure that the sequencer is live before trusting the data returned by the oracle. There is no such check implemented in the `Collateral.sol` #### Impact If the Arbitrum Sequencer goes down, oracle data will not be kept up to date, and thus could become stale. However, users are able to continue to interact with the protocol directly through the L1 contract. You can review [Chainlink docs](https://docs.chain.link/data-feeds/l2-sequencer-feeds) on L2 Sequencer Uptime Feeds for more details on this. As a result, owner may be able to use the protocol while oracle feeds are stale. Then outdated price can be used to calculate the collateral value. #### Code Snippet https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Collateral.sol#L320-L328 #### Tool used Manual Review #### Recommendation Add a check for sequencer status similar to example of chainlink recommend: https://docs.chain.link/data-feeds/l2-sequencer-feeds#example-code --- ### <a id="M03"></a> [M-03] The collateral will be distributed unfairly if all participants are undercollateralized #### Description We will consider one edge-case in which all the participants are undercollateralized / don't have enough collateral to pay for the current cycle and should be expelled (It also means that nobody has paid for this cycle). When this scenario happens, if we call `Fund.closeFundingPeriod()` the first participant will be chosen as the beneficiary for the cycle since the `beneficiaryIndex` is [initialized with value 0](https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#LL376C14-L376C30). The function will then [execute the external call](https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L409-L410) to `Collateral.requestContribution()`, where the param `beneficiary` is `Fund.beneficiariesOrder[0]` and `defaulters` is the entire array `Fund.beneficiariesOrder[]`, to request contribution from collateral from defaulters. During the first for-loop of the function `Collateral.requestContribution()`, the memory variable `share` is calculated as the sum of all participants that are expelled in the current cycle. Combine this with our assumption of the edge-case, the `share` will be the sum of all collateral value of all participants except the first participant since the beneficiary is excluded in this [line](https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Collateral.sol#L180). ```solidity= /// link: https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Collateral.sol#L178-L190 /// auditor comment: beneficiary is excluded in this for-loop if (currentDefaulter == ben) continue; // Avoid expelling graced defaulter if ( (wasBeneficiary && _isUnderCollaterized(currentDefaulter)) || (currentDefaulterBank < contributionAmountWei) ) { isCollateralMember[currentDefaulter] = false; // Expelled! expellants[i] = currentDefaulter; share += currentDefaulterBank; collateralMembersBank[currentDefaulter] = 0; totalExpellants++; ``` The sum collateral `share` will be then distributed among all the participants that aren't expelled and haven't been beneficiary yet. In this case only the first participant is satisified the conditions since the remaining are expelled. ```solidity= /// link: https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Collateral.sol#L218-L224 if (nonBeneficiaryCounter > 0) { // This case can only happen when what? share = share / nonBeneficiaryCounter; for (uint i = 0; i < nonBeneficiaryCounter; i++) { collateralPaymentBank[nonBeneficiaries[i]] += share; } } ``` So the first participant will claim all collaterals of all other participants. #### Impact Unfair distribution among the participants. All of the participants are in the same condition (undercollateralized and should be expelled) but only the first participants have the privilege to claim all the collaterals. #### Code Snippet https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Collateral.sol#L180 https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Collateral.sol#L219-L223 #### Tool used Manual Review #### Recommendation Consider to close fund immediately when collateral of participants aren't enough to pay for the cycle. It will let all participants claim their remaining collateral when the case happens. --- ### <a id="M04"></a> [M-04] Only the first participant is checked in the initiateFundContract function #### Description The `initiateFundContract` function in the Collateral contract only checks the collateralization status of the first participant in the participants list. ```solidity= function initiateFundContract() external onlyOwner atState(States.AcceptingCollateral) { ... require( !_isUnderCollaterized(participants[0]), "Eth prices dropped"); ... ``` If the first participant (participants[0]) is undercollateralized, the function will revert. However, if participants[0] is not undercollateralized and other participants become undercollateralized, the fund contract will be initiated normally, putting these participants at risk of losing their collaterals due to expulsion. This situation is unfair to the participants. For example, let's say the `fixedCollateralEth` is set to 1 ETH. All participants, except for participants[0], have deposited only 1 ETH to the Collateral contract, while participants[0] has deposited 2 ETH. When the `initiateFundContract` function is called and the price of ETH decreases, the 1 ETH collateral will no longer be sufficient. Despite this, the function can still be executed normally, resulting in a fund cycle where all participants, except for participants[0], are at risk of being expelled. #### Impact This situation leads to unfair treatment of the participants based on their position in the list. #### Code Snippet https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Collateral.sol#L107-L108 #### Tool used Manual Review #### Recommendation Should check the collateralization status of all participants in the `initiateFundContract` function. --- ## <a id="Low"></a>Low ### <a id="L01"></a> [L-01] Redundant conversion `address(msg.sender)` #### Description `msg.sender` is already a `address` type, so we don't need to covert it to address. #### Code Snippet https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Collateral.sol#L251 https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Collateral.sol#L263 #### Recommendation Modify `address(msg.sender)` to `msg.sender` --- ### <a id="L02"></a> [L-02] There is no need to initialize the value to 0 #### Description The default value of new stack `uint` variable is 0, so we don't need to initialize it to 0 #### Code Snippet https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L376 #### Recommendation Remove initialization to value 0 --- ### <a id="L03"></a> [L-03] Inconsistent use of `require` and `revert` #### Description Across the smart contracts, some places use `request` and some places `revert`. #### Code Snippet Require: https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L121-L123 https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L167 https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L200-L202 https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L191 https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L209-L211 https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L218-L219 https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L224 https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L307-L308C16 Revert: https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L231-L234 #### Recommendation I do recommend using `revert` with custom error to save gas --- ### <a id="L04"></a> [L-04] Should check if `contributionPeriod` is less than `cycleTime` in constructor of Fund contract #### Description The `contributionPeriod` must be less than `cycleTime` (refer to [the docs](https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L73)) to ensure the smooth flow of the funding cycle. Therefore, the constructor should verify this requirement. #### Code Snippet https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L100 #### Recommendation Should add the requirement to constructor of Fund contract to esure `contributionPeriod` is less than `cycleTime`. --- ### <a id="L05"></a> [L-05] The `closeFundingPeriod` and `startNewCycle` functions should not restrict the caller to only the owner #### Description The `closeFundingPeriod` and `startNewCycle` functions has a time-based condition that can impact the entire funding cycle if executed too late. To ensure the process and prevent mistakes, they should be executable by anyone instead of only the owner. #### Code Snippet https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L114 https://github.com/TakafulDAO/takaturn/blob/6bb7a13ecb08aed08811239b963fac90b44e6ada/ethereum/contracts/Fund.sol#L119 #### Recommendation Should not utilize the `onlyOwner` modifier for `closeFundingPeriod` and `startNewCycle` functions ---