Security Tips For Writing Smart Contracts with the Practice

Summary

0xandyeth
CoinsBench

--

We believe and trust the smart contract now. As well many specialists and biggest influencers of the world in crypto space say our business will run on smart contract in at least 10 years from now. Good services and million money of the business will be live on the smart contract in the future. It is reality now too. As everyone knows well, there is no middle man to verify the transaction on cutting-edge-technology of Blockchain in Defi ecosystem. Everything is running on the smart contract. It is programming peer to peer. So here is what we are missing. What if the smart contract programme has many bugs and issues ? Where are our million many then? What a hell it is ! That’s why we have to build well secured smart contract. In this article , I am going to introduce some tips for writing well secured smart contract. Of course, there are many articles and documents if you research them. But I want to fly with detailed examples based on my last experiences. I will focus the solidity programming language which targets to write the smart contract on EVM that we have known well already.

Security Tips

  1. Always thinking several times for making a business logics.

Thinking several times help us to make well-designed project for our good customers. As well it helps us to write a professional code with the trusted algorithm. So we have to think and think how well we will build the project logic. If I introduce a tip for this , I would like to recommend you can try to draw the architecture or diagram before you start a coding quickly. These are really helpers to be professional developer yourself.

2. Always using the latest version of solidity.

We always need to use the latest version of whatever the application is in our development. The latest updated versions has an advantage than old version such as security, more libraries, optimization of code, more comfortable solutions ,syntax and so on. So if we use the latest version, we are in comfort zone with our code.

3. Always avoiding to call the untrusted contracts.

Calling into the untrusted contract is very dangerous. Here is an example for calling the untrusted contract. I am describing two contracts which named A and B.

//SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
contract B{
uint public num;
address public sender;
uint public value;

function setVars(uint _num) public payable{
num = _num;
sender = msg.sender;
value = msg.value;
}
}
contract A{
uint public num;
address public sender;
uint public value;

function setVars(address _contract, uint _num) public payable returns(bool, bytes memory){

(bool success, bytes memory data) = _contract.delegatecall(
abi.encodeWithSignature("setVars(uint256)",_num)
);
return (success,data);
}
}

As we can see above code , we need to ensure the delegatecall is used securely. The delegatecall is low-level call like call functionality. When we care the contracts, If A contract executes delegatecall to contract B, contract B ‘s code will run with the parameters of contract A. In this case , we need to deploy the contract B first so that we can use the deployed address in contract A. In other hand, if contract B is not modified by owner , any external contracts will attack the contract B. Because contract B is untrusted contract in this case.

4. Always avoiding state change after an external call.

Basically, we need to set up Check-Effects-Interaction pattern to avoid this. It is very important. Here is the one example with withdraw function. I am going to describe the safe code and unsafe code. Then obviously , you can understand what I am saying here.

contract Escrow {mapping(address => uint) public balances;function deposit() external payable{  require(_amount > 0,"The amount can not be zero.");
balances[msg.sender] += msg.value;
....
}function unSafeWithdraw(uint amount) external{
require(amount <= balances[msg.sender],"Invalid amount"); // Check
uint amount = balances[msg.sender];
require(msg.sender.call.value(_amount)()); // Interaction
balances[msg.sender] = 0; // Effects
}
function safeWithdraw(uint amount) external{
require(amount <= balances[msg.sender],"Invalid amount"); // Check
uint amount = balances[msg.sender];
balances[msg.sender] = 0; // Effects
require(msg.sender.call.value(_amount)()); // Interaction

}
}

In this case, we can see two withdraw functions in the Escrow contract. The one is unSafeWithdraw function and another is safeWithdraw function. These are covered by Check-Effects-Interaction pattern. As we can see unSafeWithdraw function code, if we don’t keep the rule of the pattern, users can withdraw the over amount more than their deposited amount from Escrow contract. In other hand, line 4 has to move into line 3. The safeWithdraw function is right here.

5. Always Reentrancy and Ownership Takeover.

A reentrancy attack can occur when you create a function that makes an external call to another untrusted contract before it resolves any effects. There are two main types of reentrancy attacks:single function and cross-function reentrancy.

function withdraw() external{
uint amount = balances[msg.sender];
require(msg.sender.call.value(amount)());
balances[msg.sender] = 0;
}

Above code shows single function reentrancy attack. The all attackers can make a recursive call back to the original function, repeating interactions that would have otherwise not run after the effects were resolved.

contract Attacker{  function sendMoney() external{
Escrow.withdraw();
}
}

The attacker run the sendMoney function multiple times before the effects are resolved in above code . So the attacker can have more amount than the deposited amount. There are two ways to prevent this here.

  • Ensure all state changes happen before calling external contracts
  • Use function modifiers that prevent re-entrancy

6. Always avoiding gas fee.

Ethereum network requires expensive gas fee than other blockchain networks. Because it is very important problem to reduce gas fee on the application of Ethereum infrastructure.

  • Don’t write unnecessary codes and unused codes .
  • Use external instead of public to call .

7. Always avoiding the style guide violation.

Solidity programming requires stand style to build well-designed smart contract. We always have to be care of the code line . In my experience, It would be good to keep min 79 letters in one line of code.

8. Always checking the overflow.

The overflow occurs serious problems . For example ,In some functionality , a> b requires in solution. Both of two variables is defined by uint256 type . If B is overflow , It presents in 2 ^256 -(a-b) >0 . It will occur high gas fee finally and it will not run the application correctly. So we need to check the overflow surely.

9. Always using appropriate access control and modifiers.

We always have to define modifiers. Because some functions only has to run by owner. For example, emergencyWithdraw works only by owner to withdraw total deposited amounts from contract. But if this function calls by all peoples , the contract is nothing to use in our life.

modifier onlyOwner{  require(msg.sender !== owner,"This user is not owner");  _;}

10. Always using the meaningful names of the function and variable simply

In the end, I would like to recommend to use meaningful names of the functions and variables in the smart contract. so we can reduce many code lines and gas fee.

Conclusion

The attacks will get increasingly advanced and involve more complex interactions between functions and contracts to effect state.The best thing we can do to stay ahead is to keep interactions as simple as possible and employ best practices using the checks-effects-interactions pattern to structure our functions.Hopefully , 10 tips are clear for everyone and these will help you to write advanced smart contract and more secured contracts. Thanks!!

--

--