-
Notifications
You must be signed in to change notification settings - Fork 45
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
Core contracts - add & group invariant checks #64
Conversation
ae334b4
to
032a093
Compare
Reviewing this PR. |
There was a problem hiding this 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) { |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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?
Also added a tiny commit fixing a style thing and wording of a comment (make sure to pull) |
There was a problem hiding this 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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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()
There was a problem hiding this 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
fixes #45 (overflow and underflow checks)
fixes #32 (fix nonce check in closeChannel)
fixes #47 (group invariants)
fixes #53 (
unlock_amount
can be0
)Mostly groups and adds invariant checks + some tests to make sure they work as assumed.