News & Updates

ZeroShift DAO Presale Contract Review

Code Review for the presale of ZeroShift DAO, an Olympus fork on the Avalanche blockchain.
ZeroShift DAO Presale Contract Review

Review Date: 11 Jan 2021 20:12 UTC

Special Thanks
Proofreading: Checkmate

Disclaimer: This is not an audit of any kind. Please do not call my code review an audit. These reviews are for entertainment & education purpose only and are not financial advice.

Basic Information

Contract Address

Owner/DAO Address (Deprecate Gnosis Multi-Sig Contract)

dZRST Token Address

ZRST Token Address

Note: The presale is not started yet. The contract was provided by ZeroShift DAO from the project’s discord announcement

ZeroShift Announcement in Discord

Interesting Points

*Owner = DAO Address and is multi-sig

1-. The presale contract ownership is not renounced.

2. There are constant values that can not be changed:
- dZSRT token(dZeroShift) = 0xbfd44c6feb91dbf3c84228be65e844968567fcc2
- ZRST token (zeroShift) = 0x7b2d03e8e569042b91750ab32c544894c726a712
- USDC.e token (USD) = 0xa7d7079b0fead91f3e65f86e8915cb59c1a4c664
- DAI token (DAI) = 0xd586e7f844cea2f87f50152665bcbc2c279d8d70
- DAO Address (DAO) = 0x197123d62a2252c0ac668a72baae39af333843e0
- Price for Whitelist User (price) = 5x10⁶ or 5 USDC.e per dZSRT
- Price for Whitelist Team Member (priceDai) = 5x10¹⁸ or 5 DAI per dZSRT
- Max Cap for Whitelist User (cap) = 500x10⁶ or 500 USDC.e
- Max Cap for Whitelist Team Member (capDai) = 500x10¹⁸ or 500 DAI

Fixed value in Presale Contract

Contract Creation Parameter (Constructor) in Presale Contract

3. There are 2 types of whitelist users in this presale throughout the contract: NORMAL and TEAM users.

4. addWhitelistaddMultipleWhitelist and removeWhitelist function can be used by the owner before the presale starts. This allow the owner to add(single/batch) and remove(single) normal whitelist user.

Note that only addWhitelist and addMultipleWhitelist function can be used if the owner paused the contract with togglePause function.

addWhitelist, addMultipleWhitelist, removeWhitelist function in Presale Contract

5. addTeam and removeTeam function can be used by the owner before the presale starts. This allow the owner to add(single/batch) and remove(single) team whitelist user along with how many whitelist number they get.

Note: addTeam function can also be used if the owner paused the contract with togglePause function.

Team whitelist user can get multiple number of whitelist, which make the max purchase cap for team user larger than normal user.

addTeam, removeTeam function in Presale Contract

6. deposit function can be use by the normal user to exchange USDC.e for dZRST.

The user can only buy if
- Presale has started by the owner
- Presale has not ended by the owner
- user is a normal whitelist
- the amount bought doesn’t exceed the max cap

When the user deposit, the set amount of USDC.e is sent from the user to the owner(DAO). The contract then mints dZRST to the user at the rate of 5 USDC.e per dZRST. The contract then records the amount of USDC.e that the user purchased for and the amount of dZRST received.

deposit function in Presale Contract

7. depositTeam function can be use by the team user to exchange DAI for dZRST. The rate is 5 DAI per dZRST.

All process is the same as normal user except
- team user use DAI instead of USDC.e
- team user can have multiple whitelist spot, meaning they can buy more than the normal user depending on the multiplier

For example, the team with 2 whitelist spot can buy 1000 DAI as a max cap (500x2).

depositTeam function in Presale Contract

8. start and end function can only be used by the owner to start the presale at anytime and end the presale at anytime after it started.

Once started, no whitelist user of any type can be added, deposit will be open and owner can initiate end presale at anytime manually.

After ending is triggered by the owner, whitelist user can no longer deposit and owner can then trigger claiming using claimUnlock later.

All process from starting, ending and open claiming is manual without indicated timeframe. Each process can not be reverted and must go in succession.

start, end function in Presale Contract

9. claimUnlock function can only be called by the owner anytime but only once the end presale has been called.

This function allows the owner to open claim for the whitelist user to swap dZRST to ZRST using withdraw function.

When the user use withdraw function, the dZRST is burn from the user’s wallet and the contract sends ZRST to the user. The exchange rate for dZRST to ZRST is at a 1:1 rate.

Note that if there is no ZRST exist in the presale contract, withdraw will not work. This means that the owner of the ZRST token contract will need to mint to the presale contract at the same dZRST amount before opening claim (claimUnlock function).

claimUnlock function in Presale Contract

withdraw function in Presale Contract

10. adminWithdraw function can only be use by the owner at anytime. This allows the owner to withdraw any token at full amount at anytime with no constraint.

adminWithdraw function in Presale Contract

11. togglePause function can only be use by the owner at anytime. This allows the owner to pause the contract which disables the following ability
- deposit for both normal and team whitelist
- withdraw (swap) of ZRST using dZRST

This means that the owner can pause withdrawal(swapping for ZRST) at anytime while the owner can withdraw any token without this restriction.

togglePause function in Presale Contract

12. Owner and DAO Address is the same. The address is a deprecated Gnosis Multi-Sig Contract, deprecated version means that the contract is no longer officially support with the introduction of Safe Multi-sig since 2019.

The multi-sig policy is 4/6 meaning that meaning 3 signer out of 5 signer address need to confirm the transaction.

6 Signer Address consist of
Wallet #1 :–
0x6635515d11ab778bfb781e749a581a12270dd4cb (~17 days old, 13 txn)
Funded by Tornado Cash Protocol

Wallet #2 :–
0x941df11c97182263a2103cec49e383ad3967fbb8 (~30 days old, 7 txn)
Funded by 0x3a26c271cd9be1bc96a1b636f124007af9b91069 which funded by Binance Hot Wallet

Wallet #3 :–
0x722970e5c9099e3b93cb1e09ccb62b56ebfa344b (~27 days old, 6 txn)
Funded by 0x7ed53f6e3de6b2b4156fa8e618506e60d8e65843

Wallet #4 :–
0x95290be82fcd524eb759c1b0b1ee2e369bbc1460 (~32 days old, 1 txn)
Funded by 0x32855ec5680956f3b6db54d24babf29bb6b360fb which somehow doesn’t have AVAX funds origin in snowtrace

Wallet #5 :–
0x201f6bd429a7b97fe27c3221d3a1b336eb363568 (~17 days old, 1 txn) Funded by Tornado Cash Protocol

Wallet #6 :–
0xead16e57a35a16f3b4ed3a34033ccff1f8c39a65 (~17 days old, 1 txn)
Funded by Tornado Cash Protocol

In short, total of 3 wallets: Wallet #1, Wallet #5, and Wallet #6 is transactionally anonymous and funded by Tornado Cash. I would highly suggest user to always confirm if the wallet is the designated party, to avoid 4 wallet control by 1 person scenario which is just enough for this 4/6 multi-sig policy.

User should always check, confirm and identify with the project on who is holding the multi-sig to avoid ruggable scenario.

13. dZRST is a customized token contract but is implemented similarly to vault-based minting ERC20 token. This means the the owner can assign who mints the coin which can means 2 things:
1) Allow the presale to mint and work properly in the presale contract when user deposit
2) Allow the owner to assign anyone to mint the token using setPresale which include allowing the owner themselves to unlimited minting.

mint function in dZRST token Contract

setPresale function in dZRST token Contract

14. ZRST is a vault minting token contract where the owner can unlimited minting ZRST token. No treasury is set at the time of writing this, this suggests that the complete dApp is not setup for public or it doesn’t exist yet.

mint function in ZRST token Contract

Closing Thought

  1. This contract allows the owner address to withdraw all deposit USDC.e and DAI from the contract using adminWithdraw function. The wallet address is a 4/6 multi signature wallet contract.
    The multi-sig can risk in multiple ways:
    - risk in stale situation where the funds stuck because 3 address does not approve for the transaction
    (If implemented correctly, the risk substantially is lower)
    - risk of 4 address belong to 1 person which make multi-sig a normal wallet (3 address are funded by Tornado Cash which may belong to one of the 3 other address)
    - risk in of 4 address conspiring for illicit/bad decision such as rug-pulling (If implemented correctly, the risk substantially is lower)


  2. There is a risk of unlimited minting of ZSRT and dZSRT token by the token owner which is a multi-sig, this may cause soft-rug in the future.
    [Suggest user to check who the multi-sig signer is]
  3. There is a risk of owner never opening claim or pauses the contract, making it impossible for the user to retrieve ZRST since all the process need to be done manually.
    [Suggest user to check the process and who the signer is]
  4. There is a risk of over allocation of whitelist since team have the ability to get larger allocation with whitelist multiplier.
    [Suggest user to check the allocation amount executed in the contract or multi-sig.]
  5. There is a risk of fund stuck due to multi-sig inactivity since there is no cancel or refund functionality in the contract.
  6. Overall this is a manual contract which depends highly on the multi-sig decision with the risk mention above.

Possible Rug Route

Here we demonstrate a possible way to hard-rug with this contract

  1. Let people buy dZRST in exchange of USDC.e and DAI with deposit function because all funds goes to contract.
  2. If 4 wallet belong to the owner, the wallet is treated as owner normal wallet. Owner then use adminWithdrawal to get all USDC.e and DAI (or transfer ownership to normal wallet then withdraw)
    [Note: Need consent from 4/6 Multi-sig]
  3. Owner don’t deploy ZRST liquidity or OHM fork contract.

Here we demonstrate a possible way to soft-rug with this contract

  1. Let people buy dZRST in exchange of USDC.e and DAI with deposit function because all funds goes to contract.
  2. Deploy OHM fork normally as well as ZRST-DAI LP or ZRST-USDC.e LP
  3. Owner use unlimited minting via ZRST token minting and gradually dump minted ZRST to the market.
    [Note: Need consent from 4/6 Multi-sig]

Here we demonstrate a possible way to stuck-rug with this contract

  1. Let people buy dZRST in exchange of USDC with deposit function because all funds goes to contract.
  2. Something happen and 3 of the signer address in multi-sig refuse to approve for transaction.
    [Hard to happen for 4/6 multi-sig when set up properly]
  3. Fund stuck forever in the multi-sig wallet or the presale contract.

If this helpful to you, you can tip me here:

Tip Address


Article written by Indyza.

Follow me in Twitter

Join Olympians Discord for discussion and code review request in (#🛠│code-talk):