eatthecode.com

Solidity smart contract fund and withdraw

Solidity smart contract fund and withdraw

Introduction

The smart contract is the backbone of many blockchain businesses nowadays. In this post, I will explore how to implement the solidity smart contract to fund and withdraw assets from a smart contract. The post will provide also some security and best practices tips as securing the smart contracts, and the different methods of withdrawing from a smart contract.

Solidity smart contract fund and withdraw

Withdrawal from contracts

To withdraw from a wallet or a smart contract there are three ways: transfer, send and call

Transfer

The transfer method is not recommended to use in transferring assets between wallets as a reentrance attach can be executed with the transfer method. The transfer will throw an error if it fails. The following code snippet shows how the “transfer” method can be used.

payable(msg.sender).transfer(address(this).balance);

The above code transfer assets from the current smart contract (address(this)) to the smart contract caller(msg.sender).

Send

The send method is not recommended to use in transferring assets between wallets as a reentrance attach can be also executed with the send method. The send will return a boolean, so you have to use “require” to handle the exception. The following code snippet shows how the “send” method can be used.

bool sendSuccess = payable(msg.sender).send(address(this).balance);
require(sendSuccess, "Send failed");

The above code sends assets from the current smart contract (address(this)) to the smart contract caller(msg.sender).

Call

The call method is recommended to use in transferring assets between wallets as it uses the low-level transfer of the network and hence the reentrance attacks can’t be executed with the call method straighforward. The “call” will return a boolean, so you have to use “require” to handle the exception. The following code snippet shows how the “call” method can be used.

bool sendSuccess =         (bool callSuccess, bytes memory dataReturned) = payable(msg.sender).call{value: address(this).balance}("");
require(callSuccess, "Call failed");
revert();

The above code sends assets from the current smart contract (address(this)) to the smart contract caller(msg.sender).

Securing smart contract

The smart contract can be secured using some methods and techniques such as: changing the contract state variables before the external call and using openzeppelin reentrancy guard.

Reentrancy attacks

a reentrancy attack occurs between two smart contracts, where an attacking smart contract exploits the code in a vulnerable contract to drain it of its funds.

Restricting access

You can restrict access of a smart contract using a modifier, for example, a custom “onlyOwner” modifier or using openzeppelin  “onlyOwner” modifier can be applied on withdrawing function, where the owner of the smart contract only who can withdraw the contract assets.

// Custom modifier
address public owner;
modifier onlyOwner() {
      require(msg.sender == owner);
      _;
}

 

Optimize gas cost

There are some tactics that can be used to reduce the gas fees in the Ethereum and solidity world, I will mention some of those tactics here.

using constant

using constant will reduce the gas fee of the whole smart contract deploying and the call of any variable flagged with a constant keyword. The following two snippets show clarifying variables using a constant keyword or without and Undoubtedly using a constant keyword will reduce the gas fee.

uint256 public MINIMUM_ETH = 1;

 

uint256 public constant MINIMUM_ETH = 1;

 immutable keyword

address public immutable i_owner;

 

address public owner;

error keyword

Instead of using require which can cause a higher gas fee, you can use the error keyword. lets see with an example. The popular require function in solidity can be something like the following snippet code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

contract TransferAssets{
    address public immutable i_owner;

    constructor() {
        i_owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == i_owner, "Sender is not the owner!");
        _;
    }
    
    function withdraw() public onlyOwner {
       // withdraw logic
    }

}

but using the error keyword we can use something like the following code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

error NotOwner();
contract TransferAssets{
    address public immutable i_owner;

    constructor() {
        i_owner = msg.sender;
    }

    modifier onlyOwner() {
         if (msg.sender != i_owner) {
            revert NotOwner();
        }
        _;
    }
    
    function withdraw() public onlyOwner {
       // withdraw logic
       
       revert();
    }

}

We can notice, that I used in the above snippet the error keyword. An important note to say here is that when using the error keyword instead of the “require” keyword, we have to revert after the “require” keyword in case we use require in the code and we replaced it with error.

Full contract

The following snippet shows the full smart contract for the fund and withdrawal from a smart contract with two important default methods “receive” and “fallback” which call the “fund” method with implementing the fund business cycle.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "./libraries/PriceConverter.sol";

/*
    1- Nonce     -> trx count for the account
    2- Gas price -> price per unit of gas (in wei)
    3- Gas limit -> 21000
    4- To        -> address that the trx is sent to
    5- Value     -> amount of wei to send
    6- Data      -> empty
    7- v, r, s   -> components of trx signature
*/

error NotOwner();

contract FundMe {
    address public immutable i_owner;

    constructor() {
        i_owner = msg.sender;
    }

    using PriceConverter for uint256;
    uint256 public constant MINIMUM_USD = 50 * 1e18;

    address[] public funders;
    mapping(address => uint256) public addressToAmountFunded;

    function fund() public payable {
        require(msg.value.getConversionRate() >= MINIMUM_USD);
        funders.push(msg.sender);
        addressToAmountFunded[msg.sender] = msg.value;
    }

    function withdraw() public onlyOwner {

        for (uint256 i = 0; i < funders.length; i++) {
            address funder = funders[i];
            addressToAmountFunded[funder] = 0;
        }

        // reset the array
        funders = new address[](0);

        /*
           To withdraw the funds
           1- transfer
           2- send
           3- call
        */

        (bool callSuccess, bytes memory dataReturned) = payable(msg.sender)
            .call{value: address(this).balance}("");
        require(callSuccess, "Call failed");
        revert();
    }

    modifier onlyOwner() {
        if (msg.sender != i_owner) {
            revert NotOwner();
        }

        //require(msg.sender == owner, "Sender is not the owner!");
        _;
    }

    receive() external payable {
        fund();
    }

    fallback() external payable {
        fund();
    }
}

Conclusion

The solidity smart solidity fund and withdrawal operations are essential in the nowadays smart contract development process. In this post, I provided the basic operations of smart contract funds and withdrawals. Applying security tips also is inevitable in smart contract development, so the post also provides some tips on smart contract security and how to withdraw assets from a smart contract. 

You can find sample solidity source codes at github Enjoy…!!!

I can help you to build such as software tools/snippets, you contact me from here

 
Exit mobile version