Skip to content

Instantly share code, notes, and snippets.

@danbogd
Last active December 10, 2019 13:33
Show Gist options
  • Save danbogd/9b801bffbfc94ae7ef83369dda7feb7a to your computer and use it in GitHub Desktop.
Save danbogd/9b801bffbfc94ae7ef83369dda7feb7a to your computer and use it in GitHub Desktop.

Audit report of 2key network Smart Contracts.

1. Summary

This document is a security audit report performed by danbogd, where 2key network has been reviewed.

2. In scope

Сommit hash c85f3e1f3a04c56afd28bf673758367cc3df6609.

2.1 Classification of detected issues

HIGH - vulnerability can be exploited at any time and cause a loss of customers funds or a complete breach of contract operability. (Example: Parity Multisig hack, a user has exploited a vulnerability and violated the operability of the whole system of smart-contracts (Parity Multisigs). This could performed regardless of external conditions at any time.)
MEDIUM - vulnerability can be exploited in some specific circumstances and cause a loss of customers funds or a breach of operability of smart-contract (or smart-contract system). (Example: ERC20 bug, a user can exploit a bug (or "undocumented opportunity") of transfer function and occasionally burn his tokens. A user can not violate someone else's funds or cause a complete breach of the whole contract operability. However, this leads to millions of dollars losses for Ethereum ecosystem and token developers.)
LOW - vulnerability can not cause a loss of customers funds or a breach of contracts operability. However it can cause any kind of problems or inconveniences. (Example: Permanent owners of multisig contracts, owners are permanent, thus if it will be necessary to remove a misbehaving "owner" from the owners list then it will require to redeploy the whole contract and transfer funds to a new one.)
OWNER PRIVILEGES - the ability of an owner to manipulate contract, may be risky for investors.
NOTE - other code flaws, not security-related issues.

3. Findings

In total, 30 issues were reported including:

  • 5 high severity issues.

  • 9 medium severity issues.

  • 6 low severity issues.

  • 6 notes.

  • 4 owner privileges (the ability of an owner to manipulate contract, may be risky for investors).

3.1. No checking for zero address

Severity: low

Description

Incoming addresses should be checked for an empty value(0x0 address) to avoid loss of funds or blocking some functionality. Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/TwoKeyAirdropCampaign.sol#L88

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/token-pools/TokenPool.sol#L24-L25

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/token-pools/TwoKeyCommunityTokenPool.sol#L21-L23

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/token-pools/TwoKeyCommunityTokenPool.sol#L52

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/donation-campaign-contracts/TwoKeyDonationCampaign.sol#L38

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/donation-campaign-contracts/TwoKeyDonationConversionHandler.sol#L82

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/acquisition-campaign-contracts/TwoKeyAcquisitionCampaignERC20.sol#L45

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyEventSource.sol#L134

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeySingletonesRegistry.sol#L55

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/upgradability/StructuredStorage.sol#L39

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/upgradability/UpgradabilityProxy.sol#L26

3.2. ERC20 Compliance.

Severity: low

Description

According to ERC20 standard, when initializing a token contract if any token value is set to any given address a transfer event should be emitted.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/ERC20CustomToken.sol#L12

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/ERC20TokenMock.sol#L12

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/acquisition-campaign-contracts/TwoKeyAcquisitionCampaignERC20.sol#L78

3.3. Owner Privileges

Severity: owner privileges

Description

Contract owner allow himself to:

[upgrade contract](https://github.com/2key/contracts/blob/295b13424a21ae9f52844a3a706af02aa4472fd4/contracts/2key/upgradability/UpgradeabilityProxy.sol#L26) and implement any logic in the new contract. And even if the new contract will be audited, at any time possible to change the address of the new contract again to not audited and insecure.

to [freeze](https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyEconomy.sol#L58) all transfers on ERC for some period of time

Owner can [change](https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyUpgradableExchange.sol#L400) any uint value in [TwoKeyUpgradableExchange](https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyUpgradableExchange.sol) contract as buy and sell rates, weiRaised values, etc.

Owner can [change](https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/upgradability/StructuredStorage.sol#L48) proxy logic contract any times and chan change it to not audited contract.

3.4. Wrong value passing

Severity: medium

Description

In function setInitialParams there is wrong value passing to function setInitialParameters(_erc20Address, TWO_KEY_SINGLETON_REGISTRY);, because in all other similar functions there is passing value from arguments of functions and also TWO_KEY_SINGLETON_REGISTRY variable not initialized in this contract.

Recommendation

There should be passed twoKeySingletonesRegistry instead of TWO_KEY_SINGLETON_REGISTRY.

   setInitialParameters(_erc20Address, twoKeySingletonesRegistry);

3.5. Multi Transfer arrays length check.

Severity: low

Description

There are several input arrays, but no check for the same length. In this case it is possible to skip some parameters.

Code snippet

<<<<<<< HEAD

b78ee16...

3.6. Wrong membership address replacing

Severity: medium

Description

In function replaceMemberAddress there is setting old member info to new address in line 199, but memberAddress will still be old address, which should be replaced.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyCongress.sol#L199

Recommendation

Create new Member structure for replaced address with copying memberSince, votingPower and name.

3.7. Wrong transferFrom function

Severity: note

Description

In function transferFrom there is adding conversionQuota to _to address balance, where conversionQuota is maximal ARC tokens that can be passed in transferFrom, but value will always be equal to 1. So in this function there will not be transferring tokens from one address to another, there will be burning from one and minting to another.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/campaign-mutual-contracts/TwoKeyCampaign.sol#L59

3.8. No checking for enough tokens

Severity: note

Description

There is no checking that there will be enough tokens in line 344 in function buyTokensAndDistributeReferrerRewards as wrote in TODO comment.

   //TODO: add require that there's enough tokens at this moment

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/acquisition-campaign-contracts/TwoKeyAcquisitionCampaignERC20.sol#L344

Recommendation

Add checking that there is enough tokens.

3.9. Wrong condition checking

Severity: medium

Description

In the function isCampaignEnded there is possibility that campaignRaisedAlready will be more than campaignHardCapWei so in this case this function will return false instead of true.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/acquisition-campaign-contracts/TwoKeyAcquisitionLogicHandler.sol#L280

Recommendation

Check that campaignRaisedAlready is at least equal to campaignHardCapWei.

   if(endCampaignWhenHardCapReached == true && campaignRaisedAlready >= campaignHardCapWei) {
    return true;
   }

3.10. Empty if statement

Severity: note

Description

If statement in constructor has no code inside of it and could be deleted.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/TwoKeyAirdropCampaign.sol#L94

3.11. Anyone can transfer others tokens

Severity: high

Description

Using function transferFrom anyone can transfer other addresses voting points to himself. There is no checking for person who call this function, so he can pass his address as to address and transfer himself anyone's voting points.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/TwoKeyVoteToken.sol#L109

Recommendation

Better to use transfer function so only voting points' owners will be able to transfer their voting points.

3.12. Allowance function show wrong value

Severity: medium

Description

Function allowance show balanceOf functions result and doesn't show the amount of tokens that an owner allowed to a spender as wrote in comment above function.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/TwoKeyVoteToken.sol#L135

3.13. Distribution date changing

Severity: medium

Description

In function changeTokenDistributionDate the shift value will be positive if _newDate less then tokenDistributionDate and negative otherwise. Therefore this value should be subtracted from tokenUnlockingDate instead of added.

Same issue occur in function changeDistributionDate in TwoKeyPurchasesHandler.sol contract

Code snippet

        uint shift = tokenDistributionDate - _newDate;
        // If the date is changed shifting all tokens unlocking dates for the difference
        for(uint i=0; i<numberOfVestingPortions+1;i++) {
            tokenUnlockingDate[i] = tokenUnlockingDate[i] + shift;
        }

Recommendation

Use subtraction shift instead of addition:

        uint shift = tokenDistributionDate - _newDate;
        // If the date is changed shifting all tokens unlocking dates for the difference
        for(uint i=0; i<numberOfVestingPortions+1;i++) {
            tokenUnlockingDate[i] = tokenUnlockingDate[i] - shift;
        }

3.14. Not complete removal of a user from Congress

Severity: low

Description

There are no restrictions for adding same user several times. In this case during the user removing only first record will be removed from allMembers array. The rest of the user records will be saved.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyCongress.sol#L239

3.15. Failure to delete a user is possible

Severity: high

Description

It may happen that the user cannot be removed in case if block gas limit is reached. When removed, two cycles are used and when a large number of users will not have enough gas to perform the function. And considering vulnerability 3.14. an attacker can intentionally spam an array of users.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyCongress.sol#L265-L274

3.16. There is no way to remove user

Severity: high

Description

Only the contract itself can call the `removeMember` function. But the contract does not have this call, respectively, there is no way to remove the user.
The `removeMember` function contains this code:
    for (uint j = i; j< allMembers.length; j++){
        allMembers[j] = allMembers[j+1];
    }

But the last assignment will not be performed. Because if the array consists of 5 elements then allMembers[4] = allMembers[5] expression attempts to access a nonexistent element(last index is 4). And transaction will be reverted.

Note: Also access to nonexistent element is performed here.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyCongress.sol#L256

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyCongress.sol#L273

3.17. Anyone can get access to onlyMember functions

Severity: high

Description

Anyone can call replaceMemberAddress function and there is no check of membership. Therefore, an attacker can add any address to isMemberInCongress array and get access to all functions with onlyMember modifier.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyCongress.sol#L187

3.18. Underflow is possible

Severity: medium

Description

When removing a member, there is no safe mathematical operation maxVotingPower-= votingPower. The following actions may cause an underflow:

addMember 0x1, maxVotingPower = 10
removeMember 0x1, maxVotingPower = 10
replaceMemberAddress 0x1 to 0x2, maxVotingPower = 10 (look at issue 3.17 where the removed user can restore his membership with his votingPower)
removeMember 0x2, maxVotingPower = 0-10 (underflow)

And the same scheme underflow with minimumQuorum variable.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyCongress.sol#L261

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyCongress.sol#L292

3.19. Multiplication after division

Severity: note

Description

Solidity operates only with integers. Thus, if the division is done before the multiplication, the rounding errors can increase dramatically.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/acquisition-campaign-contracts/TwoKeyAcquisitionCampaignERC20.sol#L342

3.20. Adding a member is possible only from constructor

Severity: note

Description

The addMember function is used condition:

  if(initialized == true) {
      require(msg.sender == address(this));
  }

But after the initialization there is no way to use addMember because it is not being called from anywhere else in the contract.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyCongress.sol#L226-L228

3.21. Unused contract

Severity: note

Description

TwoKeyVoteToken contract is created but never used:

  votingToken = new TwoKeyVoteToken(address(this));

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/DecentralizedNation.sol#L115

3.22. Known vulnerabilities of ERC-20 token

Severity: low

Description

  1. It is possible to double withdrawal attack. More details here.

  2. Lack of transaction handling mechanism issue. WARNING! This is a very common issue and it already caused millions of dollars losses for lots of token users! More details here.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/donation-campaign-contracts/InvoiceTokenERC20.sol

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyEconomy.sol

Recommendation

Add into a function transfer(address _to, ... ) following code:

   require( _to != address(this) );

3.23. Out of Gas

Severity: medium

Description

The contracts have loops the number of iterations of which depends on the variables in the storage and can increase over time. This can lead to "Out of Gas" error or to "Block Gas Limit". Need to create a mechanism for breaking cycles into parts.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/donation-campaign-contracts/TwoKeyDonationConversionHandler.sol#L261-L270

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyPlasmaEvents.sol#L395-L397

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/acquisition-campaign-contracts/TwoKeyConversionHandler.sol#L368-L377

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyPlasmaEvents.sol#L251-L286

https://github.com/RideSolo/contracts-4/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyCongress.sol#L76

3.24. Power Law (Rewards Distribution)

Severity: medium

Description

If a value of factor is higher than 2 in powerLawRewards function the cumulative reward for every address (numberOfInfluencers) will be higher than the total allocated reward totalRewardEthWEI.

Code snippet

https://github.com/2key/contracts/blob/f5e7322ed781bbd0a026bb4183ac5d2ebf8efc77/contracts/2key/libraries/IncentiveModels.sol#L50

3.25. Contract Access Following Code Hash

Severity: high

Description

GetCode library function at access the code of another contract and load it into a bytes variable, this function is used in TwoKeyVoteToken to load contract code and calculate a hash that will be used to allow the contract or not to transfer tokens following the developers comments (check the code below).

function addContract(address _contractAddress) public onlyOwner {
        require(_contractAddress != address(0), 'addContract zero');
        bytes memory _contractCode = GetCode.at(_contractAddress);
        bytes32 cc = keccak256(abi.encodePacked(_contractCode));
        canEmit[cc] = true;
    }


    ///@notice Modifier which will only allow allowed contracts to transfer tokens
    function allowedContract() private view returns (bool) {
        //just to use contract code instead of msg.sender address
        bytes memory code = GetCode.at(msg.sender);
        bytes32 cc = keccak256(abi.encodePacked(code));
        return canEmit[cc];
        return true;
    }

    modifier onlyAllowedContracts {
        require(allowedContract(), 'onlyAllowedContracts');
        _;
    }

However when the code is loaded no state variable that has changed after the deployment is taken into account meaning that an attacker can load the code of a pre approved contract, deploy a new contract with the admin address changed (an example can be if the contract set the admin address inside the constructor), this will allow the attacker to access the same privileges as the original allowed contract on TwoKeyVoteToken or any inherited contract.

Pease note that no function is implemented to remove the privileges for a contract

Code snippet

https://github.com/2key/contracts/blob/440b7016157e0f115294a38566d4f3d42a79dfed/contracts/2key/libraries/GetCode.sol#L10

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/TwoKeyVoteToken.sol#L37

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/TwoKeyVoteToken.sol#L46

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/TwoKeyVoteToken.sol#L54

3.26. Compare Function

Severity: low

Description

compare function member of TwoKeyCongress compare only the first 3 bytes of the allowed function signature, if a not allowed function contain the same 3 first bytes the proposal can pass the validation phase.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyCongress.sol#L143

3.27. Maintainers Management

Severity: medium

Description

getAllMaintainers function implemented in of TwoKeyMaintainersRegistry and TwoKeyPlasmaMaintainersRegistry return an incomplete or a wrong array of maintainers addresses since it is only updated once when initialized but not updated when calling addMaintainer or removeMaintainer later on after initialization.

Code snippet

https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyMaintainersRegistry.sol#L104 https://github.com/2key/contracts/blob/c85f3e1f3a04c56afd28bf673758367cc3df6609/contracts/2key/singleton-contracts/TwoKeyPlasmaMaintainersRegistry.sol#L79

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment