Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core contracts - add & group invariant checks #64

Merged
merged 10 commits into from
May 11, 2018

Conversation

loredanacirstea
Copy link
Contributor

@loredanacirstea loredanacirstea commented May 9, 2018

fixes #45 (overflow and underflow checks)
fixes #32 (fix nonce check in closeChannel)
fixes #47 (group invariants)
fixes #53 (unlock_amount can be 0)

Mostly groups and adds invariant checks + some tests to make sure they work as assumed.

@loredanacirstea loredanacirstea changed the title Contract checks Core contracts - add & group invariant checks May 9, 2018
@LefterisJP
Copy link
Contributor

LefterisJP commented May 9, 2018

Reviewing this PR.

Copy link
Contributor

@LefterisJP LefterisJP left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests look good. But the use of require() and assert() needs some work.

@@ -23,7 +23,7 @@ contract SecretRegistry {
/// @return true if secret was registered, false if the secret was already registered.
function registerSecret(bytes32 secret) public returns (bool) {
bytes32 secrethash = keccak256(secret);
if (secrethash_to_block[secrethash] > 0) {
if (secret == 0x0 || secrethash_to_block[secrethash] > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even though this is not part of this PR, just wanna mention we should make this fail with a revert as per this issue.

// Do the transfer
require(token.transferFrom(msg.sender, address(this), added_deposit));
assert(token.transferFrom(msg.sender, address(this), added_deposit));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a require(), not an assert. As per solidity's docs:

The require function should be used to ensure valid conditions, such as inputs, or contract state variables are met, or to validate return values from calls to external contracts.

Specifically note the validate return values from calls to external contracts


emit ChannelNewDeposit(channel_identifier, participant, participant_state.deposit);
assert(participant_state.deposit >= added_deposit);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also a require() here, not an assert. In general assert should be for something that should never ever happen under any circumstances, i.e.: internal errors.

// Signature must be from the channel partner
assert(partner == recovered_partner_address);
// Signature must be from the channel partner
assert(partner == recovered_partner_address);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be a require(), it depends on external input.

// Update the balance proof data for the closing_participant
updateBalanceProofData(channel, closing_participant, nonce, balance_hash);

emit NonClosingBalanceProofUpdated(channel_identifier, closing_participant);

// Channel must be closed
assert(channel.state == 2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be a require(), depends on external input (time of call of the function)

}

emit ChannelSettled(channel_identifier);
// The channel must be open
assert(initial_state == 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be a require() as it depends on external input (time of call of the function)

assert(initial_state == 1);

// The provided addresses must be the same as the recovered ones
assert(participant1 == participant1_address);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be a require() as it depends on external input (provided address)


// The provided addresses must be the same as the recovered ones
assert(participant1 == participant1_address);
assert(participant2 == participant2_address);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be a require() as it depends on external input (provided address)

create_balance_proof,
create_balance_proof_update_signature
):
def get(participant1, locked_amount1, locksroot1, participant2, locked_amount2, locksroot2, settle_timeout=SETTLE_TIMEOUT_MIN):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line too long, should split it


# This should pass, even though the locked amount in storage is bigger.
# But those locked tokens will remain locked in the contract
token_network.transact().unlock(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps here (and in the next test) also add a check for the token balance of the contract to check they are still locked there?

@LefterisJP
Copy link
Contributor

Also added a tiny commit fixing a style thing and wording of a comment (make sure to pull)

Copy link
Contributor

@LefterisJP LefterisJP left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some more requires vs asserts info

secret_registry = SecretRegistry(_secret_registry);
chain_id = _chain_id;

// Make sure the contract is indeed a token contract
assert(token.totalSupply() > 0);
Copy link
Contributor

@LefterisJP LefterisJP May 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also be a require as it depends on external input (token contract not having a total supply function).

@@ -478,6 +472,15 @@ contract TokenNetwork is Utils {
participant1_amount = max(participant1_amount - participant1_locked_amount, 0);
participant2_amount = max(participant2_amount - participant2_locked_amount, 0);

assert(participant1_amount <= total_deposit);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Until this morning I would have left those as asserts as it "should not happen". But I had a talk with Christian today and showed him these lines.

He said these should still be require(). Overflow here can happen if the appropriate input is given to this function. The only way this should be an assert is if we actually somehow in the code path that leads here have already checked that no overflow can happen. Iff we already did then this should be an assert here. If not a require.

The thinking is that there will be automatic tools that could be testing the program for correctness, feeding it with random input. Or trying to verify correctness through formal proofing tools. Asserts should never ever be reached under any input or conditions.


require(computed_locksroot != 0);
require(locked_amount > 0);
assert(locked_amount >= unlocked_amount);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just let me see if I understand correctly. This here can be an assert since at this line we already make sure that unlocked_amount is the min of unlocked_amount and locked_amount so this should always hold true.

require(computed_locksroot != 0);
require(locked_amount > 0);
assert(locked_amount >= unlocked_amount);
assert(locked_amount >= returned_tokens);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is an overflow check for this line: returned_tokens = locked_amount - unlocked_amount;, then with the logic explained above it should still be a require()

require(participant2 == participant2_address);

// The sum of the provided balances must be equal to the total deposit
assert(total_deposit == (participant1_balance + participant2_balance));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the same logic as above, this overflow check should be a require()

Copy link
Contributor

@LefterisJP LefterisJP left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me now

@LefterisJP LefterisJP merged commit 6a20b95 into raiden-network:master May 11, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants