Skip to content

Latest commit

 

History

History
129 lines (82 loc) · 6.73 KB

CONTRIBUTING.md

File metadata and controls

129 lines (82 loc) · 6.73 KB

Raiden Smart Contracts Development Guide

Code Style

Solidity

For solidity we generally follow the style guide as shown in the solidity documentation with some exceptions:

Variable Names

All variable name should be in snake case, just like in python. Function names on the other hand should be mixedCase. MixedCase is essentially like CamelCase but with the initial letter being a small letter. This helps us to easily determine which function calls are smart contract calls in the python code side.

function iDoSomething(uint awesome_argument) {
    doSomethingElse();
}

Reentrance Problems

Calls into other contracts might call back into our contract. This causes problems when storage accesses and an external call interleave. For example,

   check(storage[address])
   other_contract.method()
   storage[address] = new_value

possibly has a bug, where an attacker can set up other_contract.method() so that it calls back into this piece of code. Then, the check() still sees an old value.

assert() and require()

When you write Solidity code, be aware of the distinction between assert(cond) and require(cond).

assert(cond) and require(cond) both cause a failure in the EVM execution when cond evaluates to 0. They use different EVM opcodes that cause different gas consumptions. More importantly, a convention dictates when to use which. Use assert(cond) only when you are confident that cond is always true. When an assert fires, that's considered as a bug in the Solidity program (or the Solidity compiler). For detecting invalid user inputs or invalid return values from other contracts, use require().

Returning a Boolean Indicating Success

We currently check the return values from all external function calls. In the Solidity code, all external function calls should happen within require(...) unless the function returns nothing.

When we implement a function that has nothing to return, we make the function always return true. So we have a more consistent visual look without naked calls.

Signature Convention

A signature should be useful only in one context. For this purpose, we follow a convention dictating the format of signed messages. The first fields of a signed message must look like::

address destination_of_the_message, uint256 chain_id_of_the_destination, uint256 message_type

following the usual prefix of Ethereum signatures \x19Ethereum Signed Message:\n<message_length>.

Resources

Python

This repository follows the same guidelines as the Raiden Client, regarding the Python code used in tests and scripts: https://github.com/raiden-network/raiden/blob/master/CONTRIBUTING.md#coding-style.

We consider it's good to have one function just doing one thing.

Pull-Request Checklist

Moved to the PR template

Testing

Read our Test Guide

Adding a New Smart Contract

Location

Currently, our setup is:

  • for core contracts: ./raiden_contracts/data/source/raiden
  • for 3rd party services: ./raiden_contracts/data/source/services
  • libraries: ./raiden_contracts/data/source/lib
  • non-production test contracts: ./raiden_contracts/data/source/test

Constants

Update package constants with the new contract's name, events, gas requirements etc.

Note: gas requirements are currently calculated using ./raiden_contracts/tests/test_print_gas.py, so the contract needs to be added here. This is not optimal: #16

Tests

Compilation

We need to add the new contract to the precompiled data. If the new contract is located in one of the existing directories, then it will automatically be added. If a new contracts directory is created, then it must be added to the compilation process:

def contracts_source_path():
return {
'raiden': _BASE.joinpath('contracts'),
'test': _BASE.joinpath('contracts', 'test'),
'services': _BASE.joinpath('contracts', 'services'),
}
def contracts_data_path(version: Optional[str] = None):
if version is None or version == CONTRACTS_VERSION:
return _BASE.joinpath('data')
else:
return _BASE.joinpath(f'data_{version}')
def contracts_precompiled_path(version: Optional[str] = None):
data_path = contracts_data_path(version)
return data_path.joinpath('contracts.json')

Deployment

Make sure the new contract's precompiled data and deployment info is included in ./raiden_contracts/data, by adding the contract to the deployment process

Check if the deployment info file path still stands:

def contracts_deployed_path(chain_id: int, version: Optional[str] = None, services: bool = False):
data_path = contracts_data_path(version)
chain_name = ID_TO_NETWORKNAME[chain_id] if chain_id in ID_TO_NETWORKNAME else 'private_net'
return data_path.joinpath(f'deployment_{"services_" if services else ""}{chain_name}.json')

Package Release

We need to make sure the contract and related data is part of the release process: