Pointers for incorporating Smart Contract’s safety elements: Best Practices

Harbor
Coinmonks
6 min readMar 8, 2023

--

After internalizing the smart contract security mentality and becoming familiar with the EVM’s quirks, it’s time to think about some security patterns unique to the Solidity programming language. This summary will center on safe programming practices for building Solidity applications, which may be applicable to other languages used to create smart contracts.

Use modifiers only for checks

Any state changes or external calls made inside a modifier will violate the Checks-Effects-Interactions pattern because the code inside the modifier is typically run before the function body. The developer may also miss these statements if the code for the modifier has located some distance from the function declaration. An external call-in modifier is one potential source of reentrancy assault.

contract Registry {
address owner;

function isVoter(address _addr) external returns(bool) {
// Code
}
}

contract Election {
Registry registry;

modifier isEligible(address _addr) {
require(registry.isVoter(_addr));
_;
}

function vote() isEligible(msg.sender) public {
// Code
}
}
Code language: JavaScript (javascript)

By invoking Election.vote() from within isVoter(), the Registry contract can conduct a reentracy assault.

Note: If you need to substitute duplicate condition checks in numerous functions, such as isOwner(), use modifiers; otherwise, use require or revert inside the function. This improves the readability and audibility of your smart contract code.

When using integer division, avoid rounding.

Integer division always rounds to the closest integer. Consider using a multiplier or storing both the numerator and denominator if you need more accuracy.

(In the future, Solidity will support fixed-point types, making this simpler.)

// bad
uint x = 5 / 2; // Result is 2, all integer divison rounds DOWN to the nearest integer
Code language: JavaScript (javascript)

Using a multiplier keeps numbers from being rounded down. When working with x in the future, this multiplier will need to be taken into account:

// good
uint multiplier = 10;
uint x = (5 * multiplier) / 2;
Code language: JavaScript (javascript)

Storing the numerator and denominator allows you to compute the numerator/denominator result off-chain:

// good
uint numerator = 5;
uint denominator = 2;
Code language: JavaScript (javascript)

Be mindful of the tradeoffs between abstract contracts and interfaces.

Customizable and reusable approaches to smart contracts can be found in both interfaces and abstract contracts. In Solidity 0.4.11, interfaces were introduced, and they are comparable to abstract contracts except that they cannot have any functions implemented. Since interfaces can’t access storage or derive from other interfaces, abstract contracts are usually more practical. Interfaces are helpful for designing contracts before they are implemented, though. Keep in mind that if a contract inherits from an abstract contract, it must override all unimplemented functions in order to avoid becoming abstract itself.

Fallback functions

If a contract receives a message with no arguments (or if no function matches), it will invoke one of its fallback functions. However, when called from a .send() or .transfer(), these functions will only have access to 2,300 gas. The most you can do in a fallback function if you want to be able to accept Ether from a .send() or .transfer() is log an event. If calculating more gas is necessary, use the appropriate function.

// bad
function() payable { balances[msg.sender] += msg.value; }

// good
function deposit() payable external { balances[msg.sender] += msg.value; }

function() payable { require(msg.data.length == 0); emit LogDepositReceived(msg.sender); }
Code language: JavaScript (javascript)

In fallback functions, check the length of the data

If the fallback function is only meant to be used for the purpose of logging received Ether, you should make sure the data is empty because it is not only called for plain ether transfers (without data), but also when no other function matches.

// bad
function() payable { emit LogDepositReceived(msg.sender); }

// good
function() payable { require(msg.data.length == 0); emit LogDepositReceived(msg.sender); }
Code language: JavaScript (javascript)

Explicitly mark payable functions and state variables

Every function that receives ether starting with Solidity 0.4.0 must use the payable modifier; otherwise, the transaction will reverse if the msg.value is greater than 0. (except when forced).

It may not be immediately clear, but the payable modifier only pertains to calls from outside contracts. Even though msg.value is still set, the non-payable function won’t fail if I execute it in the payable function of the same contract.

Explicitly mark visibility in functions and state variables

Label functions’ and state variables’ visibility in explicit terms. The external, public, internal, or private status of a function can be defined. External is not feasible for state variables. Inaccurate presumptions about who can call the function or access the variable will be simpler to spot if the visibility is labeled explicitly.

Monitoring contract action through events

Having a method to keep track of the contract’s activity after it was implemented can be helpful. Looking at all of the contract’s transactions is one way to do this, but since message calls between contracts are not logged in the blockchain, that approach might not be adequate. Furthermore, it does not display the real state changes that are being made; only the input parameters. Events may also be used to initiate user interface functions.

contract Charity {
mapping(address => uint) balances;

function donate() payable public {
balances[msg.sender] += msg.value;
}
}

contract Game {
function buyCoins() payable public {
// 5% goes to charity
charity.donate.value(msg.value / 20)();
}
}
Code language: JavaScript (javascript)

At this point, the Game contract will make an internal call to Charity.donate(). This transaction will not show in Charity’s external transaction list, but will only be visible in the internal transactions.

An event is a simple method to record something that occurred in the contract. Emitted events are stored in the blockchain alongside other contract data and are auditable in the future. Here’s an improvement on the previous example, which uses events to provide a history of Charity’s contributions.

contract Charity {
// define event
event LogDonate(uint _amount);

mapping(address => uint) balances;

function donate() payable public {
balances[msg.sender] += msg.value;
// emit event
emit LogDonate(msg.value);
}
}

contract Game {
function buyCoins() payable public {
// 5% goes to charity
charity.donate.value(msg.value / 20)();
}
}
Code language: JavaScript (javascript)

All transactions that pass through the Charity contract, whether directly or indirectly, will appear in the event list of that contract, along with the amount of charity given.

Keep in mind that ‘Built-ins’ can be shaded

There is presently support for shadowing globals that come predefined in Solidity. By doing so, contracts can modify the functionality of predefined built-ins like msg and revert(). However intended, this can make contract users uncertain of the contract’s true behavior.

Make appropriate use of assert(), require(), and revert()

The assert and require convenience functions can be used to verify for conditions and throw an exception if the condition is not met.

You should only use the assert function to verify for invariants and internal errors.

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

By adhering to this paradigm, formal analysis tools are able to validate that the invalid opcode can never be reached, which indicates that none of the code’s invariants are violated and that the code has been formally verified.

pragma solidity ^0.5.0;

contract Sharer {
function sendHalf(address payable addr) public payable returns (uint balance) {
require(msg.value % 2 == 0, "Even value required."); //Require() can have an optional message string
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;
}
}
Code language: JavaScript (javascript)

New to trading? Try crypto trading bots or copy trading on best crypto exchanges

Join Coinmonks Telegram Channel and Youtube Channel get daily Crypto News

Also, Read

--

--

Harbor
Coinmonks

A testing infrastructure company that provides production ready staging environments for web3.0 companies.