[GUSD] ERC20 Token Smart Contract Technical Assessment

[GUSD] ERC20 Token Smart Contract Technical Assessment

General Information

Risk Summary

  • Does the contract implement the ERC20 token standards? Yes.
  • Risk analysis : MEDIUM.

Technical Information

  • Compiler version : v0.4.21+commit.dfe3193c
  • Decimals : 2
  • Overflow checks : Yes, where there are practical implications of overflow and underflow. There are a few edge cases noted below.
  • Mitigation against allowance race-condition : Yes, the contract implements increaseApproval and decreaseApproval to get around this issue.
  • Upgradeable contract patterns : Yes, the contract is upgradeable. It is still on the first implementation: ERC20Impl.sol
  • Access control or restriction lists : Yes.
    • There is access control on minting, but no restriction list. An online wallet is used for limited minting (currently set to 100M). An offline 2-of-N multisig is used for any larger changes.
  • Non-standard features or behaviors : Yes.
    • Ability to control the token supply.
    • Ability to upgrade the contract.
    • Ability to batch transfer.
    • Ability to sweep balances.

Formal Verification Considerations:

  • Does transfer have simple semantics? No. Calls other contracts.
  • Does transferFrom have simple semantics? No. Calls other contracts.
  • Can balances be arbitrarily modified by some actor? Yes, the contract can mint to any on-chain address.
  • Are there any external calls? Yes, ERC20Impl calls ERC20Store and ERC20Proxy.

Testnet Information

  • N/A or an inactive testnet.

Contract Logic Summary

Upgradeability

GUSD uses a custom CustodianUpgradeable contract which adds logic for the inheriting contract to set an initial custodian and switch that custodian when approved by the custodian. This functionality is further extended by ERC20ImplUpgradeable which allows the custodian to update erc20Impl which points to the current implementation.

Gemini explains this in their whitepaper here.

Administrative Addresses

Difference between limitedPrinter, sweeper and custodian :

  • custodian can mint any amount of GUSD to any address. The custodian can also update the GUSD implementation.
  • limitedPrinter can mint tokens up to a limit set by the custodian.
  • sweeper can transfer tokens from any number of wallets which have previously signed a message to allow sweeping.

Note: limitedPrinter and sweeper could be two different addresses, but are currently set to the same externally owned account 0xd24400ae8bfebb18ca49be86258a3c749cf46853.

Time Lock

All custodian-level changes are behind a time-lock. The duration of the time-lock depends on who initiates the request. Changes initiated by the primary account (0xd24400ae8bfebb18ca49be86258a3c749cf46853) are subject to the defaultTimeLock. Any other addresses are subject to the extendedTimeLock and must additionally put up 1 ETH as collateral to prevent spamming. Confirming requests always requires 2 signatures from offline accounts, and the primary account is not one of them.

Changes to erc20Impl are locked behind a 2 day / 7 day window for default and extended respectively.

Changes to the amount of supply the limited printer can mint are locked behind a 1 hour / 1 day window respectively.

Inheritance Structure

GUSD’s contract logic, excluding the upgradeability patterns, is all encompassed within the ERC20Impl.sol contract. The contract inherits no other contracts.

Contract Risk Summary

This is a medium risk contract. The ERC20 function are implemented to industry standard, there are checks to prevent over/underflows, and there is no inheritance beyond the upgradeability patterns. However, the non-standard features outlined in the technical information section of this assessment introduce additional risk.

LockRequestable

There is no overflow check on lockRequestCount, a uint256 incrementer. The risk that this overflows extremely low.

ERC20Impl

In batchTransfer() there is a potential overflow, however impractical, in erc20Store.addBalance(to, v) if (to + v) were to overflow. The risk here is low, as the real balance of to or the balance transfered in v would need to be close to overflow.

In transferFromWithSender() there is a potential overflow, however impractical, in erc20Store.addBalance(_to, _value) if (to + _value) were to overflow. The risk here is low, as the real balance of to or the balance transfered in _value would need to be close to overflow.

In transferWithSender() there is a potential overflow, however impractical, in erc20Store.addBalance(_to, _value) if (to + _value) were to overflow. The risk here is low, as the real balance of to or the balance transfered in _value would need to be close to overflow.

In enableSweep() there is a potential overflow, however impractical, in sweptBalance += fromBalance if (sweptBalance + fromBalance) were to overflow. The risk here is low, as the real balance of sweptBalance or the balance transfered in fromBalance would need to be close to overflow. All of this carries into erc20Store.addBalance(_to, sweptBalance), which is another potential location for overflow.

In replaySweep() there is a potential overflow, however impractical, in sweptBalance += fromBalance if (sweptBalance + fromBalance) were to overflow. The risk here is low, as the real balance of sweptBalance or the balance transfered in fromBalance would need to be close to overflow. All of this carries into erc20Store.addBalance(_to, sweptBalance), which is another potential location for overflow.

Supporting Materials

Architecture Diagram

Inheritance Diagram

ABI Description

 +  LockRequestable 
    - [Pub] <Constructor> #
    - [Int] generateLockId #

 +  CustodianUpgradeable (LockRequestable)
    - [Pub] <Constructor> #
       - modifiers: LockRequestable
    - [Pub] requestCustodianChange #
    - [Pub] confirmCustodianChange #
       - modifiers: onlyCustodian
    - [Prv] getCustodianChangeReq

 +  ERC20ImplUpgradeable (CustodianUpgradeable)
    - [Pub] <Constructor> #
       - modifiers: CustodianUpgradeable
    - [Pub] requestImplChange #
    - [Pub] confirmImplChange #
       - modifiers: onlyCustodian
    - [Prv] getImplChangeReq

 +  ERC20Interface 
    - [Pub] totalSupply
    - [Pub] balanceOf
    - [Pub] transfer #
    - [Pub] transferFrom #
    - [Pub] approve #
    - [Pub] allowance

 +  ERC20Proxy (ERC20Interface, ERC20ImplUpgradeable)
    - [Pub] <Constructor> #
       - modifiers: ERC20ImplUpgradeable
    - [Pub] totalSupply
    - [Pub] balanceOf
    - [Pub] emitTransfer #
       - modifiers: onlyImpl
    - [Pub] transfer #
    - [Pub] transferFrom #
    - [Pub] emitApproval #
       - modifiers: onlyImpl
    - [Pub] approve #
    - [Pub] increaseApproval #
    - [Pub] decreaseApproval #
    - [Pub] allowance

 +  ERC20Impl (CustodianUpgradeable)
    - [Pub] <Constructor> #
       - modifiers: CustodianUpgradeable
    - [Pub] approveWithSender #
       - modifiers: onlyProxy
    - [Pub] increaseApprovalWithSender #
       - modifiers: onlyProxy
    - [Pub] decreaseApprovalWithSender #
       - modifiers: onlyProxy
    - [Pub] requestPrint #
    - [Pub] confirmPrint #
       - modifiers: onlyCustodian
    - [Pub] burn #
    - [Pub] batchTransfer #
    - [Pub] enableSweep #
       - modifiers: onlySweeper
    - [Pub] replaySweep #
       - modifiers: onlySweeper
    - [Pub] transferFromWithSender #
       - modifiers: onlyProxy
    - [Pub] transferWithSender #
       - modifiers: onlyProxy
    - [Pub] totalSupply
    - [Pub] balanceOf
    - [Pub] allowance

 +  ERC20Store (ERC20ImplUpgradeable)
    - [Pub] <Constructor> #
       - modifiers: ERC20ImplUpgradeable
    - [Pub] setTotalSupply #
       - modifiers: onlyImpl
    - [Pub] setAllowance #
       - modifiers: onlyImpl
    - [Pub] setBalance #
       - modifiers: onlyImpl
    - [Pub] addBalance #
       - modifiers: onlyImpl


 ($) = payable function
 # = non-constant function

Sūrya’s Description Report (tool: Surya )

Files Description Table

File Name SHA-1 Hash
gusd.sol 6ef30aaedd883064e469ef5f410054b0141b89b6

Contracts Description Table

Contract Type Bases
└ Function Name Visibility Mutability Modifiers
LockRequestable Implementation
└ Public :exclamation: :stop_sign: NO❗️
└ generateLockId Internal :lock: :stop_sign:
CustodianUpgradeable Implementation LockRequestable
└ Public :exclamation: :stop_sign: LockRequestable
└ requestCustodianChange Public :exclamation: :stop_sign: NO❗️
└ confirmCustodianChange Public :exclamation: :stop_sign: onlyCustodian
└ getCustodianChangeReq Private :closed_lock_with_key:
ERC20ImplUpgradeable Implementation CustodianUpgradeable
└ Public :exclamation: :stop_sign: CustodianUpgradeable
└ requestImplChange Public :exclamation: :stop_sign: NO❗️
└ confirmImplChange Public :exclamation: :stop_sign: onlyCustodian
└ getImplChangeReq Private :closed_lock_with_key:
ERC20Interface Implementation
└ totalSupply Public :exclamation: NO❗️
└ balanceOf Public :exclamation: NO❗️
└ transfer Public :exclamation: :stop_sign: NO❗️
└ transferFrom Public :exclamation: :stop_sign: NO❗️
└ approve Public :exclamation: :stop_sign: NO❗️
└ allowance Public :exclamation: NO❗️
ERC20Proxy Implementation ERC20Interface, ERC20ImplUpgradeable
└ Public :exclamation: :stop_sign: ERC20ImplUpgradeable
└ totalSupply Public :exclamation: NO❗️
└ balanceOf Public :exclamation: NO❗️
└ emitTransfer Public :exclamation: :stop_sign: onlyImpl
└ transfer Public :exclamation: :stop_sign: NO❗️
└ transferFrom Public :exclamation: :stop_sign: NO❗️
└ emitApproval Public :exclamation: :stop_sign: onlyImpl
└ approve Public :exclamation: :stop_sign: NO❗️
└ increaseApproval Public :exclamation: :stop_sign: NO❗️
└ decreaseApproval Public :exclamation: :stop_sign: NO❗️
└ allowance Public :exclamation: NO❗️
ERC20Impl Implementation CustodianUpgradeable
└ Public :exclamation: :stop_sign: CustodianUpgradeable
└ approveWithSender Public :exclamation: :stop_sign: onlyProxy
└ increaseApprovalWithSender Public :exclamation: :stop_sign: onlyProxy
└ decreaseApprovalWithSender Public :exclamation: :stop_sign: onlyProxy
└ requestPrint Public :exclamation: :stop_sign: NO❗️
└ confirmPrint Public :exclamation: :stop_sign: onlyCustodian
└ burn Public :exclamation: :stop_sign: NO❗️
└ batchTransfer Public :exclamation: :stop_sign: NO❗️
└ enableSweep Public :exclamation: :stop_sign: onlySweeper
└ replaySweep Public :exclamation: :stop_sign: onlySweeper
└ transferFromWithSender Public :exclamation: :stop_sign: onlyProxy
└ transferWithSender Public :exclamation: :stop_sign: onlyProxy
└ totalSupply Public :exclamation: NO❗️
└ balanceOf Public :exclamation: NO❗️
└ allowance Public :exclamation: NO❗️
ERC20Store Implementation ERC20ImplUpgradeable
└ Public :exclamation: :stop_sign: ERC20ImplUpgradeable
└ setTotalSupply Public :exclamation: :stop_sign: onlyImpl
└ setAllowance Public :exclamation: :stop_sign: onlyImpl
└ setBalance Public :exclamation: :stop_sign: onlyImpl
└ addBalance Public :exclamation: :stop_sign: onlyImpl

Legend

Symbol Meaning
:stop_sign: Function can modify state
:dollar: Function is payable

Slither Report (tool: Slither)

== ERC20 functions definition ==
[✓] transfer (address, uint256) -> (bool)
[✓] approve (address, uint256) -> (bool)
[✓] transferFrom (address, address, uint256) -> (bool)
[✓] allowance (address, address) -> (uint256)
[✓] balanceOf (address) -> (uint256)

== Custom modifiers ==
[✓] No custom modifiers in ERC20 functions

== ERC20 events ==
[✓] Transfer (address, address, uint256)
[✓] Approval (address, address, uint256)
[x] transfer must emit Transfer (address, address, uint256)
[x] approve must emit Approval (address, address, uint256)
[x] transferFrom must emit Transfer (address, address, uint256)

== ERC20 getters ==
[✓] totalSupply () -> (uint256)
[✓] decimals () -> (uint8)
[✓] symbol () -> (string)
[✓] name () -> (string)

== Allowance frontrunning mitigation ==
[x] increaseAllowance (address, uint256) -> (bool)
[x] decreaseAllowance (address, uint256) -> (bool)

== Balance check in approve function ==
[✓] approve function should not check for sender's balance
6 Likes