Beware of coding an invariant that strictly checks the balance of a contract.
An attacker can forcibly send ether to any account using selfdestruct and this cannot be prevented (not even with a fallback function that does a revert()).
Also, since contract addresses can be precomputed, ether can be sent to an address before the contract is deployed.
Speed Bumps
Speed bumps slow down actions, so that if malicious actions occur, there is time to recover.
structRequestedWithdrawal{uint amount;uint time;}mapping(address=>uint)private balances;mapping(address=> RequestedWithdrawal)private requestedWithdrawals;uintconstant withdrawalWaitPeriod =28days;// 4 weeksfunctionrequestWithdrawal()public{if(balances[msg.sender]>0){uint amountToWithdraw = balances[msg.sender]; balances[msg.sender]=0;// for simplicity, we withdraw everything;// presumably, the deposit function prevents new deposits when withdrawals are in progress requestedWithdrawals[msg.sender]=RequestedWithdrawal({ amount: amountToWithdraw, time:block.timestamp});}}functionwithdraw()public{if(requestedWithdrawals[msg.sender].amount >0&&block.timestamp > requestedWithdrawals[msg.sender].time + withdrawalWaitPeriod){uint amountToWithdraw = requestedWithdrawals[msg.sender].amount; requestedWithdrawals[msg.sender].amount =0;require(msg.sender.send(amountToWithdraw));}}
Rate Limiting
Rate limiting halts or requires approval for substantial changes. E.g. limit deposit withdrawals, tokens issued by contract.
Deployment
Contracts should have a substantial and prolonged testing period - before substantial money is put at risk. Ideally one should have:
A full test suite with 100% test coverage (or close to it)
Deploy on your own testnet
Deploy on the public testnet with substantial testing and bug bounties
Exhaustive testing should allow various players to interact with the contract at volume
Deploy on the mainnet in beta, with limits to the amount at risk
Automatic Deprecation
During testing, you can force an automatic deprecation by preventing any actions, after a certain time period. For example, an alpha contract may work for several weeks and then automatically shut down all actions, except for the final withdrawal.
Restrict amount of ether per user/contract
Reduces risk in case of insecure contracts
Assert, Require, Revert
Enforce invariants with assert()
An assert guard triggers when an assertion fails. Assert guards should often be combined with other techniques, such as pausing the contract and allowing upgrades, else you may end up stuck, with an assertion that is always failing.
**Use assert(), require(), revert() properly
The assert function should only be used to test for internal errors, and to check invariants.
Following this paradigm allows formal analysis tools to verify that the invalid opcode can never be reached: meaning no invariants in the code are violated and that the code is formally verified.
Modifiers as Guards
The code inside a modifier is usually executed before the function body, so any state changes or external calls will violate the Checks-Effects-Interactions pattern. Moreover, these statements may also remain unnoticed by the developer, as the code for modifier may be far from the function declaration. For example, an external call in modifier can lead to the reentrancy attack:
In this case, the Registry contract can make a reentrancy attack by calling Election.vote() inside isVoter().
Integer Division
All integer division rounds down to the nearest integer. If you need more precision, consider using a multiplier, or store both the numerator and denominator.
Locking Pragmas
Contracts should be deployed with the same compiler version and flags that they have been tested the most with. Locking the pragma helps ensure that contracts do not accidentally get deployed using, for example, the latest compiler which may have higher risks of undiscovered bugs.
Event Monitoring
Events can be used to monitor the contract's activity after it is deployed. Transaction history itself may be insufficient, as message calls between contracts are not recorded in the blockchain.
Events also show input parameters, and can be used to trigger functions in the user interface. Events that were emitted stay in the blockchain along with the other contract data and are available for future audit.
Complex Inheritance
When a contract is deployed, the compiler will linearize the inheritance from right to left (after the keyword is the parents are listed from the most base-like to the most derived).
The consequence of the linearization will yield a fee value of 5, since C is the most derived contract.
EXTCODESIZE Checks
Avoid using extcodesize to check for Externally Owned Accounts.
The following modifier (or a similar check) is often used to verify whether a call was made from an externally owned account (EOA) or a contract account:
The idea is straightforward: if an address contains code, it's not an EOA but a contract account. However, a contract does not have source code available during construction. This means that while the constructor is running, it can make calls to other contracts, but extcodesize for its address returns zero. Below is a minimal example that shows how this check can be circumvented:
Because contract addresses can be pre-computed, this check could also fail if it checks an address which is empty at block n, but which has a contract deployed to it at some block greater than n.
uint internal period; // how many blocks before limit resets
uint internal limit; // max ether to withdraw per period
uint internal currentPeriodEnd; // block which the current period ends at
uint internal currentPeriodAmount; // amount already withdrawn this period
constructor(uint _period, uint _limit) public {
period = _period;
limit = _limit;
currentPeriodEnd = block.number + period;
}
function withdraw(uint amount) public {
// Update period before proceeding
updatePeriod();
// Prevent overflow
uint totalAmount = currentPeriodAmount + amount;
require(totalAmount >= currentPeriodAmount, 'overflow');
// Disallow withdraws that exceed current rate limit
require(currentPeriodAmount + amount < limit, 'exceeds period limit');
currentPeriodAmount += amount;
msg.sender.transfer(amount);
}
function updatePeriod() internal {
if(currentPeriodEnd < block.number) {
currentPeriodEnd = block.number + period;
currentPeriodAmount = 0;
}
}
modifier isActive() {
require(block.number <= SOME_BLOCK_NUMBER);
_;
}
function deposit() public isActive {
// some code
}
function withdraw() public {
// some code
}
pragma solidity ^0.5.0;
contract Sharer {
function sendHalf(address payable addr) public payable returns (uint balance) {
// require() can have an optional message string
require(msg.value % 2 == 0, "Even value required.");
uint balanceBeforeTransfer = address(this).balance;
(bool success, ) = addr.call.value(msg.value / 2)("");
require(success);
// Since we reverted if the transfer failed, there should be
// no way for us to still have half of the money.
assert(address(this).balance == balanceBeforeTransfer - msg.value / 2); // used for internal error checking
return address(this).balance;
}
}
// bad
// Result is 2, all integer division rounds DOWN to the nearest integer
uint x = 5 / 2;
// good
// using a multiplier prevents rounding down, this multiplier needs to be accounted for when working with x in the future
uint multiplier = 10;
uint x = (5 * multiplier) / 2;
// good
// store the numerator and denominator to calculate the result off-chain
uint numerator = 5;
uint denominator = 2;
contract Final {
uint public a;
function Final(uint f) public {
a = f;
}
}
contract B is Final {
int public fee;
function B(uint f) Final(f) public {
}
function setFee() public {
fee = 3;
}
}
contract C is Final {
int public fee;
function C(uint f) Final(f) public {
}
function setFee() public {
fee = 5;
}
}
contract A is B, C {
function A() public B(3) C(5) {
setFee();
}
}