Commit hash: 4d2599cfe1c7d2647c26750cf547631a896dd518 Audited by: Parth Patel (Parth#7949) # Issues ## FooToken **[H-1]** `delegateWithSignature` doesn't check if `signatory` is blacklisted. On line [206-211], FooToken.sol has the following code: bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, newDelegate, nonce, expiry)); bytes32 digest = keccak256(abi.encodePacked('\x19\x01', domainSeparator, structHash)); address signatory = ecrecover(digest, v, r, s); require(signatory != address(0), 'IT_INVALID_SIGNATURE'); require(nonce == nonces[signatory]++, 'IT_INVALID_NONCE'); _delegateFrom(signatory, newDelegate); `delegateWithSignature` function generates `signatory` from the signature params `v`, `r` and `s` and try to delegate the voting power to `newDelegate` which is one of the function argument. However, this doesn't check if the `signatory` is blacklisted. Due to this, some blacklisted account can delegate their voting power to alternate addresses and vote. Consider: Consider adding a check for the signatory to be blacklisted before delegating votes. **[H-2]** `_mint` updates the voting balance of the address the tokens are minted to and not the delegate of that address. On line [73-80], FooToken.sol has the following code: function _mint(address to, uint256 _amount) internal { uint96 amount = safe96(_amount); totalSupply = totalSupply.add(_amount); balances[to] = balances[to].add96(amount); emit Transfer(address(0), to, _amount); _updateVotes(address(0), to, amount); } `mint` function mints the tokens to address `to`. But it updates the vote of address `to` which is not desired. If the address `to` has delegated its voting power to some other account, then that account should get votes. Consider: using `getDelegate(to)` to be argument of `_updateVotes` instead of `to`. Fixed line: ``` _updateVotes(address(0), getDelegate(to), amount); ``` **[H-3]** `mint` function not being override. On line [67-71], FooToken.sol has the following code: function mint(address to, uint256 _amount) external override { require(isMinter[msg.sender], 'IT_ONLY_WHITELISTED'); require(!isBlacklisted[msg.sender] && !isBlacklisted[to], 'IT_BLACKLISTED'); _mint(to, _amount); } `mint` function using `override` keyword means that it expects that function to be implemented in the parent contract(i.e. `Votes`). But `Votes` contract does not implement `mint` function. Due to this syntax error, the code is resulting in a syntax error. Consider: Remove `override` keyword from `mint` function. **[M-4]** Use two-step ownership change for changing the owner of the contract. On line [55-59], FooToken.sol has the following code: function setOwner(address _owner) external { require(msg.sender == owner, 'IT_FORBIDDEN'); owner = _owner; emit OwnerSet(owner); } If `owner` by mistake grants the ownership to some unknown/undesired address, then the contract is prone to become uncontrolled. Instead use two-step ownership change where a new owner can claim the ownership and after that only, ownership changes. This is useful for this contract where a lot of things are dependent on the owner. Consider: Consider using two-step change for changing the owner. **[L-5]** `delegateWithSignature` checks if `msg.sender` is blacklisted. On line [200], FooToken.sol has the following code: require(!isBlacklisted[msg.sender] && !isBlacklisted[newDelegate], 'IT_BLACKLISTED'); `delegateWithSignature` function checks if `msg.sender` is blacklisted or not. However, this function will be called by `relayer` which should not be stopped to delegate the signature of some other account. The goal of `relayer` is just to relay some other account's signature by paying gas. This should not be stopped even if `relayer` is blacklisted. Consider: Consider removing this check for `relayer`. **[L-6]** The contract is designed in such a way that all tokens are assumed to fit in `uint96`, but uses `uint256` in all calculations. Consider the following lines of FooToken.sol: uint256 public totalSupply; //line 22 mapping(address => uint96) internal balances; //line 27 constructor(address account, uint256 _initialAmount) //line 40 function _mint(address to, uint256 _amount) internal { //line 73 function burn(uint256 _amount) external { //line 88 function approve(address spender, uint256 _amount) external returns (bool) { //line 99 function increaseAllowance(address spender, uint256 _extraAmount) external returns (bool) { //line 116 function decreaseAllowance(address spender, uint256 _subtractedAmount) external returns (bool) { //line 123 function transfer(address to, uint256 _amount) external returns (bool) { //line 132 function transferFrom( address from, address to, uint256 _amount ) { //line 139-143 `line 27` assumes that `balances` of any account can be fit in `uint96` and doesn't exceed that. However, `totalSupply` is declared as `uint256`. Also, all the above lines shown in the snippet take arguments of `amount` or `votes` as `uint256` and in the functions, the function argument is cast to `uint96`. So, it's recommended to follow the standard type for all related variables. Consider: Consider making the function arguments as `uint96` in the above snippet instead of typecasting it later. ## SafeMath **[H-1]** `ceil_div` doesn't perform correct calculation. On line [33-40], SafeMath.sol has the following code: function ceil_div(uint256 a, uint256 b) internal pure returns (uint256 c) { c = div(a, b); if (c == mul(a, b)) { return c; } else { return add(c, 1); } } `ceil_div(6,3)` returns `3` but it should return `2`. That is because it should check whether `a == mul(b,c)` instead of checking `c == mul(a,b)`. Consider: Using the above check in the function.