Construct and Deploy an ERC20 Vault on Shardeum

Build and Deploy an ERC20 Vault

In this guide, we are going to learn how to create and deploy a customized decentralized vault smart contract on Shardeum. A Vault is a DeFi product where users can stake their tokens. The vault then implements various lending strategies to generate a profit and subsequently distribute the profits among all the depositors. (Side note: Don't confuse the topic with Crypto Vault, which is a type of wallet)

For this tutorial, we are going to be using Remix IDE, a popular browser-based Solidity IDE.

Smart Contract for a Vault

The fundamental strategy behind the vault is that when a user deposits ERC20 tokens into the Vault smart contract, we will create shares (representing the ownership of the respective tokens that this user has deposited into the vault contract). When a user withdraws tokens, we will destroy the corresponding number of shares.

With the strategy established, let's begin with the basic structure of the smart contract.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;
import "./IERC20.sol";


 contract Vault{
   IERC20 public immutable token;
   uint public totalSupply;
   mapping(address => uint) public balanceOf;


   constructor(address _token){
       token = IERC20(_token);
   }
}

In the code above, we are importing the IERC20 interface (Interface for ERC20 tokens, which provides us with the functions and events required for the ERC20 token standard).

  • Create a new file named IERC20.sol in the Remix Workspace and copy the entire contract from OpenZeppelin's IERC20.

  • Next, we will add two internal functions: mint() and burn(). The mint() function takes input for the address to which the shares will be minted and the amount to be minted.

  • Similarly, the burn() function takes input for the address whose shares need to be burned.

 function _mint(address _to,uint _amount) private{
       totalSupply += _amount;
       balanceOf[_to] += _amount;
   }
    function _burn(address from,uint _amount) private{
       totalSupply -= _amount;
       balanceOf[from] -= _amount;
   }
  • Now, let's write the functions to deposit and withdraw from the Vault. In the deposit() function, we handle the case when totalSupply == 0 separately because we don't want the function to divide by zero. This function essentially calculates the number of shares that need to be minted based on the number of tokens being deposited.

  • In the withdraw() function, we perform a similar calculation to determine the number of shares to be burned, and we send the tokens back to the user.

 function deposit(uint _amount) external {
       uint shares;
       if(totalSupply== 0){
           shares = _amount;
       }
       else{
           shares = _amount*totalSupply /token.balanceOf(address(this));
       }
       _mint(msg.sender, shares);
       token.transferFrom(msg.sender,address(this), _amount);
   }
 function withdraw(uint _shares) external {
       uint amount = (_shares*token.balanceOf(address(this)))/ totalSupply;
       _burn(msg.sender, _shares);
       token.transfer(msg.sender,amount);
   }

Create and Deploy an ERC20 Token

Now, let's create a standard ERC20 token with the name "VAULT" and the token symbol "VLT," which we will use to deposit into our vault.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "./IERC20.sol";


contract ERC20 is IERC20 {
   uint public totalSupply;
   mapping(address => uint) public balanceOf;
   mapping(address => mapping(address => uint)) public allowance;
   string public name = "VAULT";
   string public symbol = "VLT";
   uint8 public decimals = 18;


   function transfer(address recipient, uint amount) external returns (bool) {
       balanceOf[msg.sender] -= amount;
       balanceOf[recipient] += amount;
       emit Transfer(msg.sender, recipient, amount);
       return true;
   }


   function approve(address spender, uint amount) external returns (bool) {
       allowance[msg.sender][spender] = amount;
       emit Approval(msg.sender, spender, amount);
       return true;
   }


   function transferFrom(
       address sender,
       address recipient,
       uint amount
   ) external returns (bool) {
       allowance[sender][msg.sender] -= amount;
       balanceOf[sender] -= amount;
       balanceOf[recipient] += amount;
       emit Transfer(sender, recipient, amount);
       return true;
   }


   function mint(uint amount) external {
       balanceOf[msg.sender] += amount;
       totalSupply += amount;
       emit Transfer(address(0), msg.sender, amount);
   }


   function burn(uint amount) external {
       balanceOf[msg.sender] -= amount;
       totalSupply -= amount;
       emit Transfer(msg.sender, address(0), amount);
   }
}

Now, it's time to deploy!

Now that we have all the smart contracts, let's begin deploying them on the Shardeum testnet.

  • Configure your Metamask Wallet with the Shardeum Sphinx testnet and request some testnet SHM from the faucet.

  • Compile all three smart contracts: ERC20.sol, IERC20.sol, and Vault.sol from the Solidity Compile section.

  • Next, navigate to the Deploy and Run Transactions section of Remix and change the Environment from Remix VM to Injected Web3. If your Metamask is correctly set up, you should see 'Custom (8082) Network.' Now, you are all set!

Create and Deploy an ERC20 Token

Now, it's time to test the practical functionality of what we've just built.

  • Deploy ERC20.sol and copy the token address once it's deployed.

  • Select Vault.sol and paste the previously copied address next to the deploy button, then deploy the contract.

  • Next, mint 100 VLT tokens from the ERC20 token using the mint function.

  • Paste your wallet address and the number of tokens next to the approve() function to allow us to spend it in our Vault contract.

  • Now, deposit 100 tokens into the Vault contract using the deposit() function.

  • If you call the balanceOf() function in Vault, passing your wallet address, you will see 100 tokens.

For this example, you can send 100 tokens directly to the Vault contract, assuming that the Vault made some profit.

  • Now, if you call the withdraw() function and check your balance on your ERC20 token contract, you will see 200 tokens.

That's it! We've just created a vault, deposited 100 tokens, and eventually withdrew 200 tokens with the profit assumption.

This was a simplified and high-level implementation of DeFi Vaults. In real-world products, many more factors are integrated with optimal business logic to help generate profits using various trading and lending strategies.