tags: Final Report

YuzuSwap Audit

Copyright © 2022 by Verilog Solutions. All rights reserved.
January 4, 2022
by Verilog Solutions

Yuzu-SWAP-COVER

This report presents our engineering engagement with YuzuSwap, one of the first DEX projects for the Emerald paratime on the Oasis Network. YuzuSwap is an AMM DEX with innovative trading incentive designs, such as the trading pool share token (TPST). YuzuSwap’s TPST design keeps track of DEX users’ swap operations and provides rewards for these users.


Table of Content


Project Summary

YuzuSwap is a decentralized exchange on the Oasis Emerald paratime that includes incentives such as liquidity mining and trade mining. YuzuSwap follows a non-custodial, peer-to-peer, automated-market-maker model and aims to provide a safe, swift, and low-cost tool to discover and swap tokens within the Oasis ecosystem. The YuzuSwap platform itself is fully open to developers and members of the Yuzu DAO.


Service Scope

Our review focused on the main branch, specifically, commit hash 12b06a57c53ec9eed3eadaded2afd93362d44d8b.

Our auditing service for YuzuSwap includes the following two stages:

  1. Pre-Audit Consulting Service

    As a part of the pre-audit service, the Verilog team worked closely with the YuzuSwap development to discuss potential vulnerability and smart contract development best practices in a timely fashion. Verilog team is very appreciative of establishing an efficient and effective communication channel with the YuzuSwap team, as new findings are often exchanged promptly and fixes were deployed quickly, during the preliminary report stage.

  2. Audit Service

    The Verilog team conducted a thorough study of the YuzuSwap code, with the YuzuSwap architecture graph and UML graph presented below in the YuzuSwap Architecture section. The list of findings, along with the severity and solution, is available under section Findings & Improvement Suggestions.


YuzuSwap Architecture

WOOF Swap Architecture
Yuzu Swap Architecture

These are the major smart contracts in YuzuSwap:

  1. YuzuKeeper.sol
    YuzuKeeper has the privilege to mint yuzu tokens to the registered applications and dev address.
  2. YuzuToken.sol
    YuzuToken is a standard ERC20 token contract, that can be minted by owner.
  3. YuzuPark.sol
    YuzuPark is the contract where it distributes yuzu tokens half-attenuationally as rewards for staking eligible tokens.
  4. YuzuParkExt.sol
    YuzuParkExt is a extenstion for YuzuPark. Multiple tokens can be set as reward tokens besides the yuzu token.
  5. YuzuRouter.sol
    It’s where users can swap tokens and accumulate yuzu rewards for eligible pairs.
  6. YuzuSwapMining.sol
    It’s where users can withdraw their accumulated yuzu rewards when user use YuzuRouter to swap tokens of eligible pair.
  7. YuzuZap.sol
    In YuzuZap, users can get lp tokens by supplying a single token. Users can also swap back to a single token with the lp token.
  8. YUZUTokenBlackHole.sol @ address 0x00b9dCA177aa3DB6F344A455d9E0511a6Aa7ad8D
    Its a blank contract deployed by YuzuSwap team acts like a blackhole( address(0) ) to burn all buy backed $YUZU token.

Privileged Roles

There is one main privileged role owner.
It can:

  1. Mint yuzu tokens in YuzuToken.sol
  2. Add and publish applications in YuzuKeeper.sol
  3. Add, set pools, set reward rate and set swap mining at YuzuPark.sol, YuzuParkExt.sol, YuzuSwapMining.sol, YuzuRouter.sol, StandardReward.sol.

Findings & Improvement Suggestions

InformationalMinorMediumMajorCritical

Total Acknowledged Resolved
Critical 3 3 3
Major 0 0 0
Medium 2 1 1
Minor 9 7 5
Informational 7 3 3

Critical

  1. Router address should be a variable set by owner (YuzuZap.sol: L48, L93, L98, L112, L144, 165, L171).Critical
    Description: Router address is being passed in as argument and token allowance for router is set to maximum. Attackers can deploy a set of swap and router contracts with the same interface to drain contracts’ funds.
    Recommendation: Router address should be a variable set by owner to prevent exploits.
    Result: Resolved in commit 7333cda08cf6afabeb633e03cbc07143e8fb6923.

  2. <address>.call() should be used instead of <address>.transfer() to transfer native tokens (YuzuZap.sol:L448). Critical
    Description: <address>.call() should be used instead of <address>.transfer to transfer native tokens.
    Recommendation: Use uniswap’s TransferHelper.safeTransferETH() to transfer native token.
    Result: Resolved in commit 7333cda08cf6afabeb633e03cbc07143e8fb6923.

  3. Reset amount of rewarder contract at emergencyWithdraw(YuzuParkExt.sol: L313). Critical
    Description: User amounts of rewarder contracts are not reset to zero.
    Recommendation: Reset User amounts of rewarder contracts to zero.

    for (uint256 i = 0;i < _rewarders.length ; i ++ ) {
        IRewarder _rewarder = _rewarders[i];
        if(address(_rewarder) != address(0)) {
            _rewarder.onYUZUReward(_pid,msg.sender, 0,0);
        }
    }
    

    Result: Resolved in commit 6867b4b00980862cefc5feb8487e9bb98ae4e31c.

Major

No major issue was found.

Medium

  1. Add nonRentrant modifier for external functions:zapInToken(), zapIn(), zapAcross(), zapOut(), zapOutToken(), swapToken(), swapToNative() (YuzuZap.sol:L48, L93, L98, L112, L144, 165, L171). Medium
    Description: No nonReentrant modifier for those functions.
    Recommendation: Add nonReentrant modifier.
    Result: Resolved in commit 7333cda08cf6afabeb633e03cbc07143e8fb6923.

  2. Approve actual amount instead of maximum amount (YuzuZap.sol:L180). Medium
    Description: Maximum amount is used for approval here.
    Recommendation: Modify the functions to approve actual amount instead of maximum amount.
    Result: This suggestion is not adopted.

Minor

  1. Require only receive native tokens from router address (YuzuZap.sol:L46). Minor
    Description: Anyone can send native tokens to the contract
    Recommendation: Require only router can send native tokens to current contract.
    Result: This suggestion is not adopted.

  2. Change public to external for external call only functions(YuzuParkExt.sol:L123, L149, L242, L280, L313, YuzuPark.sol:L96, L120, L190, L217, L234). Minor
    Description: public and external differs in gas usage. Also public allows calls made inside the contract.
    Recommendation: Change public to external for external call only functions.
    Result: Resolved in commit 7333cda08cf6afabeb633e03cbc07143e8fb6923 and in commit 0d0caf24d4898dcd13ef803d8f29b1e6f2022eaf.

  3. Add check for pool Id to make sure the pool does not exist at addPool() and add() (StandardReward.sol:L70, YuzuPark.sol:L96). Minor
    Description: Pool might already exist.
    Recommendation: For example, add the following requirement to check if pool id is valid.

    require(poolInfo[_pid].lastRewardBlock == 0, "Pool already exists");
    
    // or
    
    require(_pid >= poolInfo.length, "Pool already exists");
    

    Result: Not Resolved.

  4. Add check for pool Id to make sure pool exist at updatePool() and setPool() (YuzuParkExt.sol:L219, YuzuPark.sol:L167, StandardReward.sol:L89, L99). Minor
    Description: Pool might not exist.
    Recommendation: For example, add the following requirement to check if pool already added.

    require(poolInfo[_pid].lastRewardBlock != 0, "Pool not exists");
    
    // or
    
    require(_pid < poolInfo.length, "Pool not exists");
    

    Result: Not Resolved.

  5. Some varibales can be immutable. (YuzuZap.sol:L35, HalfAttenuationYuzuReward.sol:L13-L17). Minor
    Description: WNATIVE at YuzuZap and startBlock, blockNumberOfHalfAttenuationCycle and yuzuPerBlock at HalfAttenuationYuzuReward can be immutable.
    Recommendation: add immutable keyword to WNATIVE.
    Result: Resolved in commit 0d0caf24d4898dcd13ef803d8f29b1e6f2022eaf.

  6. The visibility of functiongetYuzuFromStartblock() can be pure (L41). Minor
    Description: Function visibility can be restricted to pure.
    Recommendation: Change function visibility to pure.
    Result: Resolved in commit 0d0caf24d4898dcd13ef803d8f29b1e6f2022eaf.

  7. Use div from SafeMath (StandardReward.sol:L107, L109, L152, L177, L178, L182). Minor
    Description: The / is used for division instead of div.
    Recommendation: Use div from SafeMath.
    Result: Resolved in commit 7333cda08cf6afabeb633e03cbc07143e8fb6923.

  8. Add Solidity pragma version. Minor
    Description: There is no soldity version been added to the YuzuSwapMining.sol.
    Recommendation: Add pragma solidity 0.6.12; as other smart contracts.
    Result: Resolved in commit 7333cda08cf6afabeb633e03cbc07143e8fb6923.

  9. Multiply before dividing (YuzuKeeper: L117). Minor

    function _queryActualYUZUReward(uint256 _amount) internal view returns (uint256) {
        uint256 actualAmount =  _amount.div(10).mul(7);
        return actualAmount;
    }
    

    Description: Use div before mul.
    Recommendation: Use mul before div to prevent precision lost.

    // suggested changes
    function _queryActualYUZUReward(uint256 _amount) internal view returns (uint256) {
        uint256 actualAmount =  _amount.mul(7).div(10);
        return actualAmount;
    }
    

    Result: This suggestion is not adopted.

Informational

  1. Seperate interface. Informational
    Description: Currently, interfaces is inside the same file with contract.
    Recommendation: Seperate interface.
    Result: This suggestion is not adopted.

  2. Call massUpdatePools() directly at functions add and set (YuzuParkExt.sol:L130, L157, YuzuPark.sol:L102, L126). Informational
    Description: The _withUpdate is always true. massUpdatePools() can be called directly.
    Recommendation: Remove if else check for _withUpdate. massUpdatePools() can be called directly.
    Result: Resolved in commit 7333cda08cf6afabeb633e03cbc07143e8fb6923.

  3. Modifies the logic of function duplicatedTokenDetect(). (YuzuPark.sol:L254, YuzuParkExt.sol:L345). Informational
    Description: the check for dulipcate tokens detection can be optimized.
    Recommendation: For example, use a mapping to track added tokens.
    Result: Resolved in commit 0d0caf24d4898dcd13ef803d8f29b1e6f2022eaf.

  4. Input Multicheck and Limitation (YuzuKeeper: L62). Informational

    function addApplication(address _yuzuMember , uint256 _totalValue, uint256 _perBlockLimit,uint256 _startBlock ) public onlyOwner appNotPublished {
        YuzuApplicatioin storage app = applications[_yuzuMember];
        app.yuzuMember = _yuzuMember;
        app.totalValue = _totalValue;
        app.transferedValue = 0;
        app.perBlockLimit = _perBlockLimit;
        app.startBlock = _startBlock;
        emit ApplicationAdded(_yuzuMember,_totalValue,_perBlockLimit,_startBlock);
    }
    

    Description: As one of the most important functions, input _totalValue should be limited into a certain range (less than 10% of the total token supply), input _perBlockLimit should be limited into a certain range, input _startBlock should be limited after certain date.
    Recommendation: Add requirement for each of the input.

    // suggested changes, limitation should be determined by the project team
    function addApplication(address _yuzuMember , uint256 _totalValue, uint256 _perBlockLimit,uint256 _startBlock ) public onlyOwner appNotPublished {
        require(_yuzuMember != address(0), "YUZUKEEPER: ZERO ADDRESS");
        require(_totalValue <= yuzu.totalSupply().div(10), "YUZUKEEPER: NO MORE THAN 10% OF TOTAL SUPPLY");
        require(_perBlockLimit <= 1e18, "YUZUKEEPER: PER BLOCK LIMITATION");
        require(_startBlock >= block.timestamp, "YUZUKEEPER: START AFTER CERTAIN TIME");
        ......
    }
    

    Result: This suggestion is not adopted. We suggest that the owner of the smart contract must be very careful when calling this function.

  5. Logic Improvement for Function RequestForYUZU() (YuzuKeeper: L79). Informational

    Description: When 1 $YUZU is mint, 0.1 for investor, 0.1 for foundation, 0.1 for dev. The current logic is to send the above 0.3 $YUZU first before sending the leftAmount to the user. This design is a bit unfair for the unlucky users who mint the last bit of Yuzu.
    Recommendation:
    step 1: check how many $YUZU is left.
    step 2: adjust the mint amount to the leftover amount of $YUZU if the mint amount exceeds the leftover amount.
    step 3: mint based on the distribution.
    *external changes required: YuzuToken.sol needs to add a circulatingSupply() external view function to feed YuzuKeeper how many tokens have been mint out.
    Result: This suggestion is not adopted.

  6. Add public view functions for poolInfo.length at StandardReward.sol.Informational
    Description: A view function can be added to check the poolInfo.length for the convenience of adding pools in the future.
    Recommendation: Add a public view function for poolInfo.length
    Result: Resolved in commit 7333cda08cf6afabeb633e03cbc07143e8fb6923.

  7. Unused variable _pendingYUZU at onYUZUReward() and pendingToken() (StandardReward.sol:L145, L168). Informational
    Description: Unused variable _pendingYUZU.
    Recommendation: Change the code to followings to remove compiler warnings for unused variables.

    // onYUZUReward
    function onYUZUReward(
    	uint256 pid,
    	address _user,
    	uint256 lpToken,
    	uint256 // using just uint256 without variable name will remove the warning
    ) { ... }
    
    // similarly for pendingToken
    function pendingToken(uint256 _pid, address _user, uint256) 
    { ... }
    

    Result: This suggestion is not adopted.


Disclaimer

Verilog receives compensation from one or more clients for performing the smart contract and auditing analysis contained in these reports. The report created is solely for Clients and published with their consent. As such, the scope of our audit is limited to a review of code, and only the code we note as being within the scope of our audit detailed in this report. It is important to note that the Solidity code itself presents unique and unquantifiable risks since the Solidity language itself remains under current development and is subject to unknown risks and flaws. Our sole goal is to help reduce the attack vectors and the high level of variance associated with utilizing new and consistently changing technologies. Thus, Verilog in no way claims any guarantee of security or functionality of the technology we agree to analyze.

In addition, Verilog reports do not provide any indication of the technologies proprietors, business, business model, or legal compliance. As such, reports do not provide investment advice and should not be used to make decisions about investment or involvement with any particular project. Verilog has the right to distribute the Report through other means, including via Verilog publications and other distributions. Verilog makes the reports available to parties other than the Clients (i.e., “third parties”) – on its website in hopes that it can help the blockchain ecosystem develop technical best practices in this rapidly evolving area of innovation.