This is the report from a security audit performed on LCX by gorbunovperm.
Smart Contract Audit for Listing on Exchanges
Commit hash: 2274bf5224168da72285399fca9be14471ea7764
In total, 5 issues were reported including:
-
0 critical severity issue.
-
2 high severity issue.
-
0 medium severity issues.
-
2 low severity issues.
-
1 owner privileges.
-
0 note.
- 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
Add into a function transfer(address _to, ... )
following code:
require( _to != address(this) );
- Line 372.
During revoke()
the owner receives vested.totalToken
value minus the unreleased tokens:
uint256 refund = balance.sub(unreleased);
This is wrong because it does not take into account the number of tokens already paid(vested.releasedToken
).
Let's look at an example.
Passed periods | totalToken |
releasedToken |
unreleasedToken |
TokenVesting Balance | Owner Balance |
---|---|---|---|---|---|
0 | 1000 | 0 | 0 | 2000 | 0 |
The total number of tokens Michael should receive is 1000 during 10 vesting periods. After 2 vesting periods he calls the releaseToken
function and receive 300 tokens.
Passed periods | totalToken |
releasedToken |
unreleasedToken |
TokenVesting Balance | Owner Balance |
---|---|---|---|---|---|
2 | 1000 | 200 | 0 | 1800 | 0 |
Then 4 more periods passed.
Passed periods | totalToken |
releasedToken |
unreleasedToken |
TokenVesting Balance | Owner Balance |
---|---|---|---|---|---|
6 | 1000 | 200 | 400 | 1800 | 0 |
The owner decided to cancel the vesting payout and called revoke
function. He receives totalToken
value minus the unreleased tokens 1000 - 400 = 600
:
Passed periods | totalToken |
releasedToken |
unreleasedToken |
TokenVesting Balance | Owner Balance |
---|---|---|---|---|---|
6 | 400 | 200 | 400 | 1200 | 600 |
And if Michael asks for the unreleased amount, the balance of the vesting contract will be less than expected: 1200 - 400 = 800
. But the initial balance was 2000 and 1000 were allocated for Michael.
- Line 374
- Line 341
After the owner calls revoke
function, the value of vested.totalToken
variable is set to number of unreleased tokens.
vested.totalToken = unreleased;
And when beneficiary calls releaseToken
function, the amount of unreleased tokens will be calculated: _vestedAmount(account).sub(vestedUser[account].releasedToken)
. But _vestedAmount
for revoked beneficiary is totalToken
amount. In the example from issue 3.2. if in second step we passed not 4 but 1:
Passed periods | totalToken |
releasedToken |
unreleasedToken |
TokenVesting Balance | Owner Balance | revoked |
---|---|---|---|---|---|---|
6 | 100 | 200 | 100 | 900 | 900 | true |
And during the releaseToken
the calculation will be following: 100 - 200 = -100
and that would lead to throw
.
- Line 600.
Incoming addresses should be checked for an empty value(0x0
address) to avoid loss of funds or blocking some functionality.
- Line 241.
For any reason, the owner can change the address of the token contract at any time, which may lead to the inability to receive vested tokens by beneficiary.
There is also no guarantee that the vesting contract will have enough tokens for payment.
There are some dangerous vulnerabilities were discovered here.