25
Audit report for Clever Token 23/12/2020

Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

  • Upload
    others

  • View
    1

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

Audit report for Clever Token

23/12/2020

21/12/2020

Page 2: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

2

Clever Token audit report 1 Executive summary

The following audit report presents the effect of the research that Blockhunters team

conducted on the part of the Clever Token code.

Our audit focused on two parts of the project – ERC20 token mechanisms and distribution

processes, responsible for the business logic of the project.

Blockhunters team has checked the possibility of known Ethereum attacks to be exploited

in the network. Fortunately, the main token contract contains basic functionalities that are not

vulnerable to discovered Ethereum attacks. Clever Token developer has been using SafeMath

libraries that significantly lower the risk of possible miscalculations and errors, although the

implementation of this library was faulty and repaired afterwards. All of the contracts, methods

and state variables were tested and dangerous globalBurn() function was deleted after

exposing it as a potential vulnerability.

Distribution mechanisms were tested and besides their huge complexity, the process

itself can be described as safe. A public primitiveDistribution() function was found, declared as

a vulnerability and then deleted.

1.1 Liability clause

Please note that Blockhunters Company doesn’t verify the economic foundation of the

project but only its code correctness and security issues. We do not take any responsibility for

any misuse or misunderstanding of the information provided and potential economic losses

due to faulty investment decisions. This document doesn’t ensure that the code itself is free

from potential vulnerabilities that were not found. If any questions arise please contact us by

www.blockhunters.io.

1.2 Source code checksums

Before using the smart contracts, please verify the MD5 checksums with the following

ones, which describe the files that were audited.

e8a4df5d1b2da7e4eb4313e734d19e8c ./contracts/CleverProtocol.sol

d86e95e4f61952f4e4c19bacb9cedfa7 ./contracts/CleverToken.sol

354d0a40514c389f0f11f98b092e3b0a ./contracts/TimedSwap.sol

15b15c59f0470c4b214276f4127af763 ./contracts/crowdsale/Crowdsale.sol

8d0efc6e445a760ddd6790c86c187e8f ./contracts/crowdsale/MintedCrowdsale.sol

Page 3: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

3

4e99b75d8d54be37c872f137e3251c37 ./contracts/crowdsale/TimedCrowdsale.sol

082a82ed1ab0f52098ef76f9c216994f ./contracts/token/.DS_Store

e249054930e8d626c36e822a7ae74cd3 ./contracts/token/ERC20/.DS_Store

93c501784ad395fb60b279a992d4aba6 ./contracts/token/ERC20/ERC20.sol

d4b36c388a1aeaf8343c0e5545e697ed ./contracts/token/ERC20/ERC20Capped.sol

9842b94e5146a999c4b89c3277d4657a ./contracts/token/ERC20/ERC20Detailed.sol

1b1e299c39f74d8e3f2d38901592eb32 ./contracts/token/ERC20/ERC20Mintable.sol

183ff1f5e5f04bcfa85cee597bcb0236 ./contracts/token/ERC20/IERC20.sol

1f3061e7a8a74527fe22e5ecbbec2cb8 ./contracts/token/ERC20/SafeERC20.sol

a41701ff3cdbb98920cd49750549d127 ./contracts/utils/Address.sol

32a66f6f4d0584b403f26f8e5e4d17b5 ./contracts/utils/Context.sol

2d9ec4de161520693c92bd68ebadf363 ./contracts/utils/MinterRole.sol

036cb43163c154f43db99590e741e801 ./contracts/utils/Ownable.sol

aefce07cec09f52f7bab7bd48b85de1c ./contracts/utils/ReentrancyGuard.sol

118d44aa29245b854a8785a01af0c4e9 ./contracts/utils/Roles.sol

d208da96b6f6c1d204ee8248087cf2d7 ./contracts/utils/SafeMath.sol

2159dd45f55a91233c87e65092c26845 ./contracts/utils/Secondary.sol

f077df9a0e4486e069b7412d46a681e7 ./readme.txt

56f4050df5d7a420b564df7921bd88a6 ./script/app.py

c6d43f119c240e865196b7bcabfe8c93 ./script/wallet.py

1.3 Table of contents

1 Executive summary ............................................................................... 2

1.1 Liability clause ............................................................................. 2

1.2 Source code checksums .............................................................. 2

1.3 Table of contents ........................................................................ 3

2 Main contract audit .............................................................................. 4

2.1 Errors known from Ethereum ..................................................... 4

3 Clever Token overview .......................................................................... 5

3.1 Externally accessible methods .................................................... 5

3.2 State variables ............................................................................ 6

4 Clever Protocol ..................................................................................... 7

4.1 Externally accessible methods .................................................... 7

4.2 State variables ............................................................................ 8

5 TimedSwap mechanism ........................................................................ 9

5.1 Externally accessible methods .................................................... 9

5.2 State variables .......................................................................... 10

6 Cycle distribution tests ....................................................................... 11

7 Token mechanisms tests ..................................................................... 13

8 Comments and suggestions ................................................................ 24

Page 4: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

4

2 Main contract audit

2.1 Errors known from Ethereum

✓ Reentrancy attack

The contracts adhere to ERC20 protocol and use OpenZeppelin standards where possible.

Critical methods that manipulate funds are protected with nonReentrant modifier and are

therefore safe against these types of attacks.

✓ Race conditions

Flow of the system is linear and straightforward. Nothing time-sensitive and requiring

synchronicity is performed.

✓ Integer over / underflow

Contracts use the SafeMath library, which prevents this class of errors.

✓ Timestamps

Custom logic dependent on block.timestamp is a source of many leaks as it can be

influenced by the miners. The contract is safe from any such attacks. Time is used to calculate

distribution awards upon activation and if the activation has to be performed in the first place.

Activation of each distribution cycle is protected with a time-based lock and can be performed

only once per cycle.

✓ Library dependencies

All used dependencies are in the source files. Some issues regarding following common

standards have been found, but are not critical. Rather they emerged as a quick-and-dirty

solution and could be fixed in the future version of the project.

✓ Front-running

Front running doesn’t occur here because foreseeing transactions before visible in the

blocks won’t have any bad results for the contract. This kind of attack is dangerous when a

contract acts like a market, because it if one can analyse the buy/sell transactions before they

appear in the block, he could influence the price of assets being managed.

✓ DoS

Neither of the contracts can be rendered inoperable by the users

Page 5: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

5

✓ Insufficient gas griefing

The contracts don’t use any low level contract calls, thus this error won’t occur.

This attack may be possible on a contract which accepts generic data and uses it to make a call another contract (a 'sub-call') via the low level address.call() function, as is often the case with multisignature and transaction relayer contracts

3 Clever Token overview

3.1 Externally accessible methods

addMinter(address account) OK Standard

12approve(address spender, uint256 amount)bool OK Standard

decreaseAllowance(address spender, uint256 subtractedValue)bool

OK Standard

distribute(uint256 _cycle, uint256 _percentageFactor) OK Standard

increaseAllowance(address spender, uint256 addedValue)bool

OK Standard

mint(address account, uint256 amount)bool OK Safe; only minters like TimedSwap can mint

primitiveDistribution(uint16 _cycle, uint256 _percentage)

Vulnerability -> OK

Public function that allows altering the amount of tokens; Deleted

renounceMinter() OK Standard

renounceOwnership() OK Standard

setProtocol(address _protocol) OK Safe; only owner can access

transfer(address to, uint256 value)bool OK Standard

transferFrom(address from, address to, uint256 value)bool

OK Standard

transferOwnership(address newOwner) OK Standard

Protocol()address OK Read-only

Page 6: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

6

allowance(address owner, address spender)uint256 OK Standard

balanceOf(address account)uint256 OK Safe

cap()uint256 OK Read-only

decimals()uint8 OK Read-only

isMinter(address account)bool OK Standard

isOwner()bool OK Standard

name()string OK Read-only

owner()address OK Read-only

symbol()string OK Read-only

totalSupply()uint256 OK Read-only

3.2 State variables

mapping (address => uint256) internal _balances OK

mapping (address => mapping (address => uint256)) internal _allowances

OK

uint256 internal _totalSupply OK

address public Protocol OK Safe; set by external method, only owner has access

uint256 private fragsPerToken OK

uint256 private lastCyclePaid OK

uint256 private DECIMALS OK

uint256 private MAX_UINT256 OK

Page 7: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

7

uint256 private TOTAL_FRAGS OK

bool private lockedSwap OK

uint256 internal _cap OK

4 Clever Protocol

4.1 Externally accessible methods

checkIsLive()bool OK

distributeCycleAward()uint256 OK

renounceOwnership() OK Standard

setTimedSwap(address _TimedSwap) OK

timeRestrictedWithdraw() OK Works after 8th cycle

transferOwnership(address newOwner) OK Standard

cyclesCompleted()uint256 OK Read-only

fortnight()uint256 Suggestion

It's an auto-generated getter for something that should be internal to the contract

getCycle()uint256 OK Read-only

getDay()uint256 OK Read-only

Page 8: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

8

getElapsedTime()uint256 OK Read-only

isLive()bool OK Read-only

isOwner()bool OK Standard

owner()address OK Read-only

4.2 State variables

CleverToken private token OK Safe; set only in constructor

TimedSwap private ico OK Safe; set only with setTimedSwap

uint256 private openingTime OK Safe; set only when setTimedSwap is called

uint256 private closingTime OK Safe; set only when setTimedSwap is called

uint256 public fortnight Suggestion

Should be a private constant

bool public isLive OK

uint256 default MAX_SUPPLY OK

uint default DECIMALS OK

address default admin OK

uint256 private cycles OK

Page 9: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

9

mapping (uint256 => uint256) private cycleAwards OK

mapping (uint256 => uint256) private cycleBonus OK

mapping (uint256 => uint256) private payoutPercent OK

mapping (uint256 => bool) private cyclePaid OK

mapping (uint256 => bool) private ETHflushed OK

5 TimedSwap mechanism

5.1 Externally accessible methods

buyTokens(address beneficiary) OK Standard

closingTime()uint256 OK Read-only

getCurrentRate()uint256 OK Safe

getElapsedTime()uint256 OK Read-only

initialRate()uint256 OK Read-only

isOpen()bool OK Standard

openingTime()uint256 OK Read-only

rate()uint256 OK Read-only

token()address OK Read-only

wallet()address OK Read-only

Page 10: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

10

weiRaised()uint256 OK Read-only

5.2 State variables

CleverToken private _token OK

address internal _protocol OK

uint256 private _rate OK

uint256 private _weiRaised OK

uint256 private _openingTime Suggestion Shouldn't be a hardcoded value

uint256 private _closingTime Suggestion Shouldn't be a hardcoded value

uint256 private _firstRate OK

uint256 private _secondRate OK

uint256 private _thirdRate OK

uint256 private _fourthRate OK

uint256 private _finalRate OK

uint256 private _firstWindow OK

uint256 private _secondWindow OK

Page 11: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

11

uint256 private _thirdWindow OK

uint256 private _fourthWindow OK

uint256 private _finalWindow OK

6 Cycle distribution tests

The following tests were run on the project with successful execution, proving that the

distribution mechanism of the token is correct.

const { assertReverts, assertEqualBN, getTokenState, getBalances, maybeBNToString, assertEqualBNArrays, asyncForEach, range, } = require("./utils.js");

const TimedSwap = artifacts.require("TimedSwap"); const CleverTokenMock = artifacts.require("CleverTokenMock"); const CleverProtocol = artifacts.require("CleverProtocol"); const web3 = global.web3; const BN = web3.utils.BN;

const oneHour = 60 * 60; const oneDay = 24 * oneHour; const oneMonth = 30 * oneDay;

contract('CleverProtocol', async (accounts) => { const admin = accounts[0]; const anyone = accounts[7];

const tokenAddr = CleverTokenMock.address; const protocolAddr = CleverProtocol.address; var timedSwapAddr;

var token, protocol, timedSwap;

it('everything is deployed; with TimedSwap in the past', async () => { token = await CleverTokenMock.deployed(); protocol = await CleverProtocol.deployed();

const blockNumber = await web3.eth.getBlockNumber(); const timestamp = (await web3.eth.getBlock(blockNumber)).timestamp; timedSwap = await TimedSwap.new(protocolAddr, tokenAddr, timestamp - (45 *

oneDay), oneMonth, {from: admin});

Page 12: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

12

timedSwapAddr = timedSwap.address;

await token.setProtocol.sendTransaction(protocolAddr, {from: admin}); await token.addMinter.sendTransaction(timedSwapAddr, {from: admin}); await protocol.setTimedSwap.sendTransaction(timedSwapAddr, {from: admin}); await protocol.setMockToken.sendTransaction(tokenAddr, {from: admin}); });

it('getCycle returns proper values', async () => { const fewBelow51 = [...Array(17).keys()]; const fewPotentiallyErrorProne = fewBelow51. concat(range(47,53)). concat(range(97,103)). concat(range(197, 203));

await asyncForEach(fewPotentiallyErrorProne, async cycleToGenerate => { let blockNumber = await web3.eth.getBlockNumber(); let timestamp = (await web3.eth.getBlock(blockNumber)).timestamp; let tempTimedSwap = await TimedSwap.new(protocolAddr, tokenAddr, timestamp -

(oneMonth + oneDay + (cycleToGenerate * 14 * oneDay)), oneMonth, {from: admin}); let tempTimedSwapAddr = tempTimedSwap.address;

await protocol.setTimedSwap.sendTransaction(tempTimedSwapAddr, {from: admin}); await protocol.checkIsLive({from: anyone});

let currentCycle = await protocol.getCycle.call(); assert.equal(currentCycle, cycleToGenerate);

})

// clean-up await protocol.setTimedSwap.sendTransaction(timedSwapAddr, {from: admin}); });

it('ico has closed', async () => { const openingTime = await timedSwap.openingTime.call(); const closingTime = await timedSwap.closingTime.call(); assert.ok(closingTime > openingTime);

// WARNING TimedSwap has to be added as a minter to the Token for that to work!! assert.equal(await timedSwap.hasClosed.call(), true); });

it('protocol is live; anyone can trigger', async () => { await protocol.checkIsLive({from: anyone}); assert.equal(await protocol.checkIsLive.call(), true, "Protocol is live");

const currentCycle = await protocol.getCycle.call(); assert.equal(currentCycle, 1); });

it('checking distribution', async () => { let balance = await web3.eth.getBalance(protocol.address); assert.equal(balance, 0, "Balance prior should be 0"); protocol.distributeCycleAward.sendTransaction({from: anyone});

let lastCycle = await token.lastCycleSet(); let totalPercentage = await token.totalPercentageSet(); assertEqualBNArrays([lastCycle, totalPercentage], [1, 11000], "") });

});

Page 13: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

13

The output:

7 Token mechanisms tests

The following tests:

const { assertReverts, assertEqualBN, getTokenState, getBalances, maybeBNToString, assertEqualBNArrays, asyncForEach, } = require("./utils.js");

const CleverToken = artifacts.require("CleverToken"); const CleverProtocol = artifacts.require("CleverProtocol"); const web3 = global.web3; const BN = web3.utils.BN;

const thirtyZeroes = "0".repeat(30); const baseFPT = new BN("1" + thirtyZeroes); const baseTotalFrags = new BN("1" + thirtyZeroes + thirtyZeroes);

var protocolConfirmations = 0;

Page 14: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

14

contract('CleverToken', async (accounts) => { const admin = accounts[0];

// we can't use the real address of the deployed contract because ganache can't

act in its name // so we change the address temporarily for something that can be used const protocol = accounts[0]; const hodler = accounts[1]; const randomBloke = accounts[7];

it('should be allowed only for the owner to make changes', async () => { const tk = await CleverToken.deployed(); const pre_confirmations = await tk.getPastEvents( 'LogProtocolSet', { fromBlock:

0, toBlock: 'latest' } ); const conf_length = pre_confirmations.length;

await tk.setProtocol(protocol, {from: admin}); const confirmations = await tk.getPastEvents( 'LogProtocolSet', { fromBlock: 0,

toBlock: 'latest' } );

assert.equal(confirmations.length, conf_length + 1);

let addError; try { //contract throws error and reverts await tk.setProtocol(randomBloke, {from: randomBloke}); } catch (error) { addError = error; } assert.notEqual(addError, undefined, 'Error must be thrown');

const unchanged_confirmations = await tk.getPastEvents( 'LogProtocolSet', {

fromBlock: 0, toBlock: 'latest' } ); assert.equal(unchanged_confirmations.length, confirmations.length);

protocolConfirmations = confirmations.length; });

it('should be allowed only for the protocol to distribute', async () => { const tk = await CleverToken.deployed();

await assertReverts(() => tk.distribute(1, 5000, {from: randomBloke}));

});

it('distributes not before cycle 1', async () => { const tk = await CleverToken.deployed();

const isLocked = await tk.isLockedSwap.call(); assert.equal(isLocked, false);

const err = await assertReverts(() => tk.distribute(0, 5000, {from: protocol})); assert.equal(err.reason, "Cycle attemptin to be paid out is in the past!");

});

it('mints', async () => { const tk = await CleverToken.deployed();

assert.equal(await tk.isMinter.call(admin), true); await tk.mint(hodler, 2137, {from: admin});

Page 15: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

15

const [totalSupply, fpt, totalFrags] = await getTokenState(tk);

assert.equal(totalSupply, 2137); assertEqualBN(fpt, baseFPT); assertEqualBN(totalFrags, baseTotalFrags);

const trueBalance = await tk.trueBalanceOf.call(hodler); const fauxBalance = await tk.balanceOf.call(hodler); assert.equal(trueBalance.toString(), "2137000000000000000000000000000000"); assert.equal(fauxBalance.toString(), "2137"); });

it("it doesn't burn before swapping phase is over", async () => { const tk = await CleverToken.deployed(); const err = await assertReverts(() => tk.burn(2137, {from: hodler})); assert.equal(err.reason, "Swapping phase is not over!"); });

it.skip('adminBurn fails with DIVISION BY ZERO lol', async () => { const tk = await CleverToken.deployed();

// burning all available tokens held by one account results in DBZ // CAVEAT: I have removed requirement of lockedSwap for that in _burn // to prove a point I should do that _after_ distribution, but it doesn't matter

much const err = await assertReverts(() => tk.adminBurn(hodler, 2137, {from:

admin})); assert.equal(err.reason, "SafeMath: division by zero"); });

it("distributes - doubling tokens", async () => { const tk = await CleverToken.deployed();

const preState = await getTokenState(tk);

await tk.distribute(1, 100000, {from: protocol}); // + 100% assert.equal(await tk.isLockedSwap.call(), true);

const postState = await getTokenState(tk);

assert.deepStrictEqual( preState.map((x) => x.toString()), [ '2137', '1000000000000000000000000000000', '1000000000000000000000000000000000000000000000000000000000000' ] );

assert.deepStrictEqual( postState.map((x) => x.toString()), [ '4274', '500000000000000000000000000000', '2137000000000000000000000000000000' ] );

const [trueBalance, fauxBalance] = await getBalances(tk, hodler); assertEqualBN(trueBalance, "2137000000000000000000000000000000"); assertEqualBN(fauxBalance, "4274"); });

Page 16: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

16

it("burns - burning half", async () => { const tk = await CleverToken.deployed(); await tk.burn(2137, {from: hodler}); const [trueBalance, fauxBalance] = await getBalances(tk, hodler) assertEqualBN(trueBalance, "1068500000000000000000000000000000"); assertEqualBN(fauxBalance, "2137");

const postState = await getTokenState(tk); assertEqualBNArrays( postState, [ '2137', '500000000000000000000000000000', '1068500000000000000000000000000000' ] ) });

});

contract('CleverToken verbose multiparty even', async (accounts) => { const admin = accounts[0]; const protocol = accounts[0]; const hodler_one = accounts[1]; const hodler_two = accounts[2]; const hodler_three = accounts[3]; const hodlers = [hodler_one, hodler_two, hodler_three] const randomBloke = accounts[7];

const freeTokens = 1000; const numberOfParties = hodlers.length;

it("is it at least a new instance?", async () => { const tk = await CleverToken.deployed(); assert.equal(await tk.isLockedSwap.call(), false); assertEqualBNArrays( await getTokenState(tk), [0, baseFPT, baseTotalFrags] ); });

it(`mint ${freeTokens} tokens evenly between ${numberOfParties} parties`, async ()

=> { const tk = await CleverToken.deployed(); await tk.setProtocol(protocol, {from: admin}); await tk.mint(hodler_one, freeTokens, {from: admin}); await tk.mint(hodler_two, freeTokens, {from: admin}); await tk.mint(hodler_three, freeTokens, {from: admin});

const balanceEach = [freeTokens + thirtyZeroes, freeTokens]

await asyncForEach(hodlers, async hodler => { assertEqualBNArrays( await getBalances(tk, hodler), balanceEach, "True number reflects numbers of fragments, faux of the tokens." ); }); });

it("distribute & double the tokens in rewards", async () => { const tk = await CleverToken.deployed();

Page 17: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

17

const preState = await getTokenState(tk); assertEqualBNArrays( preState, [ freeTokens * numberOfParties, baseFPT, baseTotalFrags ], "Base values prior to distribution are mostly meaningless." );

await tk.distribute(1, 100000, {from: protocol}); // + 100% assert.equal(await tk.isLockedSwap.call(), true);

const postState = await getTokenState(tk);

// this might look stupid, but JS will turn this into a number with exp base const halfOfBaseFPT = new BN("5" + "0".repeat(29)); assertEqualBNArrays( postState, [ freeTokens * numberOfParties * 2, halfOfBaseFPT, (freeTokens * numberOfParties) + thirtyZeroes, ], "Total supply increased twice, fragmentsPerToken got halved, amount of

fragments got recalculated." );

const balanceEach = [freeTokens + thirtyZeroes, 2 * freeTokens] await asyncForEach(hodlers, async hodler => { assertEqualBNArrays( await getBalances(tk, hodler), balanceEach, "Only faux balance gets doubled for each hodler." ); }); });

it("globalBurn half", async () => { const tk = await CleverToken.deployed();

const preBalanceEach = [freeTokens + thirtyZeroes, 2 * freeTokens] await asyncForEach(hodlers, async hodler => { assertEqualBNArrays( await getBalances(tk, hodler), preBalanceEach, "We start with double the faux balance each." ); });

await tk.globalBurn(50000, {from: admin}); // burn half

const postState = await getTokenState(tk); assertEqualBNArrays( postState, [ freeTokens * numberOfParties, baseFPT, (freeTokens * numberOfParties) + thirtyZeroes, ], "Total fragments got halved. " + "We return to the state after first distribution." );

Page 18: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

18

const postBalanceEach = [new BN(preBalanceEach[0]), new BN(preBalanceEach[1]) /

2] await asyncForEach(hodlers, async hodler => { assertEqualBNArrays( await getBalances(tk, hodler), postBalanceEach, "Faux balance got halved for each hodler." ); });

});

it("burn half of the tokens for each hodler in sequence", async () => { const tk = await CleverToken.deployed();

const preState = await getTokenState(tk);

const balanceBeforeBurn = await getBalances(tk, hodler_one);

// we should take the "faux" value as this is the number of tokens const halfOfTokens = balanceBeforeBurn[1] / 2;

const balanceAfterBurn = [new BN(balanceBeforeBurn[0]) / 2, new

BN(balanceBeforeBurn[1]) / 2] await asyncForEach(hodlers, async hodler => { await tk.burn((halfOfTokens), {from: hodler}); assertEqualBNArrays( await getBalances(tk, hodler), balanceAfterBurn, "True and faux balance got halved for each hodler." ); });

const predictedPostState = [ new BN(preState[0]) / 2, preState[1], new BN(preState[2]) / 2, ]

const postState = await getTokenState(tk); assertEqualBNArrays( postState, predictedPostState, "Total supply and total fragments got halved." + preState.map(maybeBNToString) ); });

});

contract('CleverToken verbose multiparty uneven', async (accounts) => { const admin = accounts[0]; const protocol = accounts[0]; const hodler_one = accounts[1]; const hodler_two = accounts[2]; const hodler_three = accounts[3]; const hodlers = [hodler_one, hodler_two, hodler_three]; const randomBloke = accounts[7];

const tokensEach = [1000, 10000, 100000]; const numberOfParties = hodlers.length; const sumOfTokens = tokensEach.reduce((accum, tok) => accum + tok, 0);

it(`mint ${tokensEach} tokens between ${numberOfParties} parties`, async () => {

Page 19: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

19

const tk = await CleverToken.deployed(); await tk.setProtocol(protocol, {from: admin});

await asyncForEach(hodlers, tokensEach, async (hodler, tokens) => { await tk.mint(hodler, tokens, {from: admin}); });

await asyncForEach(hodlers, tokensEach, async (hodler, tokens) => { assertEqualBNArrays( await getBalances(tk, hodler), [tokens + thirtyZeroes, tokens] ); }); });

it("distribute & double the tokens in rewards", async () => { const tk = await CleverToken.deployed();

const preState = await getTokenState(tk); assertEqualBNArrays( preState, [ sumOfTokens, baseFPT, baseTotalFrags ] ); await tk.distribute(1, 100000, {from: protocol}); // + 100% assert.equal(await tk.isLockedSwap.call(), true);

const postState = await getTokenState(tk);

const halfOfBaseFPT = new BN(preState[1]) / 2; assertEqualBNArrays( postState, [ sumOfTokens * 2, halfOfBaseFPT, sumOfTokens + thirtyZeroes ], "Total supply increased twice, fragmentsPerToken got halved, amount of

fragments got recalculated." );

});

});

async function globalBurn(hodlers, admin, percentage) { const tk = await CleverToken.deployed();

const percent1e5 = percentage * 1000;

await tk.globalBurn(percent1e5, {from: admin}); // burn half }

async function localBurnForEach(hodlers, admin, percentage) { const tk = await CleverToken.deployed();

await asyncForEach(hodlers, async hodler => { const balance = await tk.balanceOf(hodler); const toBurn = (balance * percentage) / 100; await tk.burn(toBurn, {from: hodler}); });

Page 20: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

20

}

[ // balances, bonus %, intermediate actions [(fn_name, args), ...], balancesAfter,

?tokenState [[1000, 1000, 1000 ], 100, [], [2000, 2000, 2000]], [[1000, 1000, 1000 ], 50, [], [1500, 1500, 1500]], [[1000, 1000, 1000 ], 30, [], [1300, 1300, 1300]],

[[1000, 1000, 1000 ], 100, [[globalBurn, [20]]], [1600, 1600,

1600]], [[1000, 1000, 1000 ], 100, [[localBurnForEach, [20]]], [1600, 1600,

1600]],

// now we are testing the burns // last array is [totalSupply, fragPerToken,

totalFrags] [[1000, 1000, 1000 ], 100, [[globalBurn, [50]]], [1000, 1000, 1000], [3000,'1000000000000000000000000000000','3000000000000000000000000000000000']], [[1000, 1000, 1000 ], 100, [[localBurnForEach, [50]]], [1000, 1000, 1000], [3000,'500000000000000000000000000000','1500000000000000000000000000000000']],

[[1000, 1000, 1000 ], 20, [[globalBurn, [20]]], [960, 960, 960], [2880,'1041666666666666700000000000000','3000000000000000000000000000000000']], [[1000, 1000, 1000 ], 20, [[localBurnForEach, [20]]], [960, 960, 960], [2880,'833333333333333400000000000000','2400000000000000000000000000000000']],

].forEach(([tokensEach, rewardPercentage, actions, afterTokensEach,

expectedPostState],ind) => { contract(`CleverToken multiparty scenario ${ind}`, async (accounts) => { const admin = accounts[0]; const protocol = accounts[0]; // terrible hack const hodler_one = accounts[1]; const hodler_two = accounts[2]; const hodler_three = accounts[3]; const hodlers = [hodler_one, hodler_two, hodler_three]; const numberOfParties = hodlers.length; const sumOfTokens = tokensEach.reduce((accum, tok) => accum + tok, 0);

const percent1e5 = rewardPercentage * 1000; // we shouldn't use floats, but for round numbers and sensible fractions JS

behaves relatively sane const rewardMultiplier = 1 + (rewardPercentage / 100);

const actionContext = [hodlers, admin];

it("it's a fresh instance", async () => { const tk = await CleverToken.deployed(); assert.equal(await tk.isLockedSwap.call(), false); assertEqualBNArrays( await getTokenState(tk), [0, baseFPT, baseTotalFrags], "Sanity check." ); });

it(`mint ${tokensEach} tokens between ${numberOfParties} parties`, async () => { const tk = await CleverToken.deployed(); await tk.setProtocol(protocol, {from: admin});

await asyncForEach(hodlers, tokensEach, async (hodler, tokens) => { await tk.mint(hodler, tokens, {from: admin}); });

Page 21: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

21

await asyncForEach(hodlers, tokensEach, async (hodler, tokens) => { assertEqualBNArrays( await getBalances(tk, hodler), [tokens + thirtyZeroes, tokens], "Everyone gets their share of tokens." ); }); });

it(`distribute & reward with ${rewardPercentage}%`, async () => { const tk = await CleverToken.deployed();

const preState = await getTokenState(tk); assertEqualBNArrays( preState, [ sumOfTokens, baseFPT, baseTotalFrags ] ); await tk.distribute(1, percent1e5, {from: protocol}); assert.equal(await tk.isLockedSwap.call(), true);

const postState = await getTokenState(tk);

const newFPT = new BN(preState[1]) / rewardMultiplier; assertEqualBNArrays( postState, [ sumOfTokens * rewardMultiplier, newFPT, sumOfTokens + thirtyZeroes ], "Total supply increased, fragmentsPerToken decreased, amount of fragments

got recalculated." ); });

actions.forEach(([fn, args], ind) => { it(`${ind}: ${fn.name}`, async () => { await fn(...actionContext, ...args); }); });

it("expect balances for each hodler", async () => { const tk = await CleverToken.deployed();

await asyncForEach(hodlers, afterTokensEach, async (hodler, expectedTokens) =>

{ const observedTokens = await tk.balanceOf(hodler); assert.equal( observedTokens, expectedTokens, "We are comparing what the token reports that users have." + "Observed: " + maybeBNToString(observedTokens) ); }); });

if (expectedPostState) { it("expect token state to match", async () => { const tk = await CleverToken.deployed();

const postState = await getTokenState(tk);

Page 22: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

22

assertEqualBNArrays( postState, expectedPostState, "Comparing internal states of the token." + postState.map(maybeBNToString) + expectedPostState.map(maybeBNToString) ); }); } }); });

Result with the output:

Page 23: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

23

Page 24: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

24

8 Comments and suggestions

• Overwriting OpenZeppelin contracts instead of regular inheritance methods is a trust

issue seen from the get-go. While proper following of these models is not necessary for

being considered an ERC20 Token, this makes it very easy to hide malicious or error-

prone actions. Nothing of this sort has been found, when compared with the original

code taken from OZ repository, but if one decides on using said contracts as a backbone,

then the finished token should work with a drop-in replacement of imports from local

(`import ./ERC20.sol;`) to let’s say github imports on Remix (`import

"http://github.com/OpenZeppelin/openzeppelin-

solidity/contracts/token/ERC20/ERC20.sol";`). This makes it much easier to verify its

correctness “at a glance”. It is advisable to create contract implementations, that differ

in name from those already existing in Zeppelin’s repertoire.

• Arithmetics during distribution are troublesome to follow and verify, because of many

redundant computations performed. Automated checks in this case will be almost

identical in nature to writing manual tests.

• _mintAfterSwap introduces inflation that’s hard to calculate for already existing tokens,

but this might be predicted by the Designers of the Token.

• Naming conventions are misleading. For example, a variable that’s considered constant

by the Creators, TOTAL_FRAGS, is not constant at all, it is being modified during minting

after “lockedSwap” is in effect. Said again: that’s not an error, just makes the job for

future developers much more difficult.

Page 25: Audit report for Clever Token · Clever Token audit report 1 Executive summary The following audit report presents the effect of the research that Blockhunters team conducted on the

25

Thank you!

Contact us at:

[email protected]

www.blockhunters.io