RWA Security Review: CFG Token Audit

Cantina Security Report

Organization

@centrifuge

Engagement Type

Cantina Reviews

Period

-

Repositories

N/A


Findings

Medium Risk

1 findings

1 fixed

0 acknowledged

Low Risk

2 findings

2 fixed

0 acknowledged

Informational

5 findings

3 fixed

2 acknowledged

Gas Optimizations

2 findings

2 fixed

0 acknowledged


Medium Risk1 finding

  1. Function CFG::burn() doesn't call _moveDelegateVotes()

    Severity

    Severity: Medium

    Likelihood: High

    ×

    Impact: Low

    Submitted by

    undefined avatar image

    Gerard Persoon


    Description

    Function CFG::burn() doesn't call _moveDelegateVotes() unlike the similar functions like DelegationToken::burn().

    This could leave token delegations, while the underlying tokens are burnt and could lead to incorrect results in voting.

    This function will mostly be used by tokens bridges to the impact seems low.

    Recommendation

    Consider adding a call to _moveDelegateVotes().

Low Risk2 findings

  1. Invalid signatures not detected in delegateWithSig()

    Severity

    Severity: Low

    Submitted by

    undefined avatar image

    Gerard Persoon


    Description

    Invalid signatures not detected in delegateWithSig(). In that situation delegator will be address(0).

    The risk is limited because address(0) won't contain tokens.

    However, the following unwanted effects are present:

    • setting a delegate for address(0), that normally shouldn't have a delegate
    • emitting DelegateeChanged for address(0)

    Also future changes/bugs could potentially allow tokens to exist on address(0).

    Recommendation

    Consider reverting when delegator == address(0).

    Alternatively use a variation of isValidSignature(), which is also used used in ERC20:Permit() and already contains this check.

  2. msg.sender of CFG.sol might hold unwanted authorizations

    Severity

    Severity: Low

    Submitted by

    undefined avatar image

    Gerard Persoon


    Description

    The msg.sender of CFG.sol is authorized via auth to be able to do file(). Then ward is also authorized.

    Assuming ward is not equal to msg.sender, msg.sender stays authorized and can still to mint().

    Recommendation

    Make sure only one address is authorized after the contract is deployed. This can be done in several ways:

    • add something like: if (ward != msg.sender) deny(msg.sender); in the constructor of CFG.sol;
    • change the code so only ward is authorized;
    • uncomment the deny statement in the deployment script.

Informational5 findings

  1. transfer() and transferFrom() don't check result of their super functions

    State

    Acknowledged

    Severity

    Severity: Informational

    Submitted by

    undefined avatar image

    Gerard Persoon


    Description

    The functions transfer() and transferFrom() don't explicitly check the result of their super functions.

    The risk is limited because the underlying implementation is known and allways returns true. However it is safer not to rely on this implicit information.

    Recommendation

    Consider check the return values of the super functions, or at least add a comment.

  2. URL in comment has a newer version

    Severity

    Severity: Informational

    Submitted by

    undefined avatar image

    Gerard Persoon


    Description

    The URL https://github.com/morpho-org/morpho-token-upgradeable resolves to https://github.com/morpho-org/morpho-token. So the resulting url could also be references in DelegationToken.sol.

    Recommendation

    Consider changing the comment in the following way:

    -/// @author Modified from https://github.com/morpho-org/morpho-token-upgradeable
    +/// @author Modified from https://github.com/morpho-org/morpho-token
  3. The repository protocol-v3 might not be accessible

    State

    Fixed

    PR #1

    Severity

    Severity: Informational

    Submitted by

    undefined avatar image

    Gerard Persoon


    Description

    The repository protocol-v3 might not be accessible by everyone, which makes reviewing the code more difficult.

    Recommendation

    Consider adding the underlying code to the cfg-token repository.

  4. delegateWithSig() doesn't support signatures from smart contract accounts

    State

    Acknowledged

    Severity

    Severity: Informational

    Submitted by

    undefined avatar image

    Gerard Persoon


    Description

    ERC20::permit() uses SignatureLib.isValidSignature(), which also allow smart contract accounts to sign.

    However delegateWithSig() doesn't support this. The result is that smart contract accounts can't used. This is increasingly relevant with ERC 4337/EIP 7702.

    Recommendation

    Consider using a variation of SignatureLib.isValidSignature() to also allow smart contract accounts.

    Note: SignatureLib.isValidSignature() currently requires signer as an input parameter, which isn't passed to delegateWithSig().

  5. no interface file for CFG.sol exists

    Severity

    Severity: Informational

    Submitted by

    undefined avatar image

    Gerard Persoon


    Description

    There is no interface file for CFG.sol. This makes using the token more complicated.

    Recommendation

    Consider creating an interface file ICFG.sol. Also inherit from it to make sure the interface is correct.

Gas Optimizations2 findings

  1. Use of balanceOf() in burn() not optimal

    Severity

    Severity: Gas optimization

    Submitted by

    undefined avatar image

    Gerard Persoon


    Description

    Function burn() uses both balanceOf() and _balanceOf(). The second call could be replaced with balance, which cantains the value of the first version.

    Recommendation

    Consider changing the code to:

    function burn(uint256 value) external {
    uint256 balance = balanceOf(msg.sender);
    require(balance >= value, InsufficientBalance());
    ...
    - _setBalance(msg.sender, _balanceOf(msg.sender) - value);
    + _setBalance(msg.sender, balance - value);
    ...
    }

    Note: these changes can also be done in ERC20.sol for functions transfer(), _transferFrom() and burn().

  2. Optimization possible for calculation of totalSupply

    Severity

    Severity: Gas optimization

    Submitted by

    undefined avatar image

    Gerard Persoon


    Description

    A small optimization can be done in the calculation of totalSupply.

    Note: similare optimizations can also be done in ERC20.sol for mint() and burn().

    Recommendation

    -totalSupply = totalSupply - value;
    +totalSupply -= value;