My Image
CoursesQuizzesProblemsContestsSmartBooks
Contest!

No results found

LOGINREGISTER
My ProgressCoursesQuizzesProblemsContestsSmartbooks
Published on 16 Feb 2023
Designing Vault Smart Contract
Creating a smart contract where user can deposit the ethereums. This is link based structure where a new joiner amount will be equally distributed to the already existing users.
img
Rahul Jaglan
0
Like
222

Hardhat is an Ethereum development environment just like Truffle, which we'll use to develop our contract and deploy it on our local network and EtherJs is a library for interacting with the Ethereum blockchain that we'll use in our test suits to interact with the deployed contract.

Installation and setup

Let’s go over the necessary step for our project.

So let’s run the following command

npm install --save-dev hardhat && npx hardhat

This will install the package and launch the interactive interface to create the project. We’ll select > Create an advanced sample project and proceed.

Hardhat will generate a couple of files and folders, but we are only interested in a few of them.

The contracts folder will host our smart contract and the test folder our test and hardhat.config.js the generated configuration from hardhat — for which we need to make sure that the solidity version defined here matches the specified version of our contract.

Writing the smart contract

We need these basic specifics for this smart contract:

  1. The smart contract will have the owner defined while deploying it.
  2. A new user can deposit any amount of money to join the vault.
  3. To join the vault there is a 10% fees
  4. This 10 % fee will be equally distributed among the existing vault users.
  5. There is a specific duration after which the locked amount will be released and then user can
    withdraw.
  6. Even after withdrawing the user will still be able to receive the tokens when a new user joins.
  7. When an existing user deposits the Ethereum again, he will be considered a new user.

 

First, we need to define the shape of our Vault

The contract is governed by an owner, who is specified in the constructor function when the contract is deployed. The owner has the ability to change the ownership of the contract and withdraw funds from the contract. The contract also includes a feePercent variable, which is set to 10 by default and is used to calculate the transaction fee for each deposit.

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

contract Vault {
    address payable public owner;
    uint256 public feePercent = 10;
    uint256 public lockDuration = 86400; // duration in seconds (1 day)
    }

    constructor() {
        owner = payable(msg.sender);
    }

 

Next, we’ll need to declare a variable that will help store and map.

The purpose of the `balance` mapping is to keep track of the amount of Ethers deposited by each user in the `Vault` contract. The `public` visibility modifier is used to allow external contracts and accounts to read the `balances` mapping.

This sort of mapping helps to manipulate and access our data.

contract Vault {
    mapping(address => uint256) public balances;
}

 

The deposit function

This function allows users to deposit funds into the Vault. It checks that the deposited amount is positive, calculates the fee, transfers the fee to the owner, and adds the remaining amount to the user's balance.

contract Vault {

    constructor() {
        owner = payable(msg.sender);
    }

    function deposit() public payable {
        require(msg.value > 0, "Amount must be positive");
        uint256 fee = (msg.value * feePercent) / 100;
        address payable ownerPayable = owner;
        ownerPayable.transfer(fee);
        uint256 depositAmount = msg.value - fee;
        balances[msg.sender] += depositAmount;
    }
}

 

Check balance function

This function returns the balance of the calling user.

contract Vault {
  
    function checkBalance() public view returns (uint256) {
        return balances[msg.sender];
    }
}

 

Change owner function

This function allows the owner to change to a new owner.

contract Vault {

    function changeOwner(address payable newOwner) public {
        require(msg.sender == owner, "Only owner can change it");
        owner = newOwner;
    }
}

 

Owner withdraw function

This function allows the owner to withdraw funds from the Vault. It checks that the caller is the owner and that the owner has enough balance.

contract Vault {

    function ownerWithdraw(uint256 amount) public payable {
        require(msg.sender == owner, "Only owner can withdraw");
        require(balances[owner] >= amount, "Not enough balance");
        owner.transfer(amount);
        balances[owner] -= amount;
    }
}

 

Release amount function

This function allows the user to release their funds after a lock duration. It checks that the funds are not locked and that the user has enough balance to withdraw. It then transfers the funds to the user and sets the balance to 0.

contract {

    function releaseAmount() public payable {
        require(block.timestamp >= lockDuration, "Amount is still locked");
        require(balances[msg.sender] > 0, "Balance is low to withdraw");
        payable(msg.sender).transfer(balances[msg.sender]);
        balances[msg.sender] = 0;
    }
}

 

Testing the smart contract

The test cases for the Vault contract are written using the Hardhat testing framework and the Chai assertion library. The tests ensure that the contract behaves as expected by checking the values of various variables and testing different functions of the contract.

Now we’ll create a file inside the test folder as Vault.test.js then import from chai, ethers, and hardhat.

const { ethers } = require('hardhat');
const { assert, expect } = require('chai');
const { utils } = require('ethers');

Next, we’ll declare our test suit and deploy our contract in beforeEach. This will deploy a new contract every time we run a new test case so that we always have a blank contract to work with.

describe('Vault', (accounts) => {
  let vault, vaultFactory;
  let depositAmount = utils.parseEther('1');
  let alice;

  beforeEach(async () => {
    vaultFactory = await ethers.getContractFactory('Vault');
    vault = await vaultFactory.deploy();
    owner = await web3.eth.getAccounts()[0];
  });

 

Should have the correct feePercent value

  • This test case checks if the `feePercent` value of the `Vault` contract is set to 10.
  • It expects the retrieved value to be equal to the string value '10'.
  it('should have the correct feePercent value', async () => {
    const feePercent = await vault.feePercent();
    expect(feePercent.toString()).to.equal('10');
  });

Should have the correct lockDuration value

  • This test case checks if the `lockDuration` value of the `Vault` contract is set to 86400.
  • It expects the retrieved value to be equal to the string value '86400'.
  it('should have the correct lockDuration value', async () => {
    const lockDuration = await vault.lockDuration();
    expect(lockDuration).to.equal('86400');
  });

Should have the correct owner

  • This test case checks if the `owner` address of the `Vault` contract is the same as the deployer address.
  • It expects the retrieved owner address to be equal to the deployer address.
  it('should have the correct owner', async () => {
    const owner = await vault.owner();
    assert.equal(owner.toString(), owner);
  });

Should deposit the funds

  • This test case checks if a deposit can be made to the `Vault` contract, and the correct balance is recorded.
  • It expects the balance to be equal to the deposit amount minus 10% fee.
  it('Should deposit the funds', async function () {
    await vault.deposit({ value: depositAmount });
    const balance = await vault.checkBalance();
    assert.equal(
      balance.toString(),
      depositAmount - (depositAmount * 0.1).toString()
    );
  });

Should change the owner

  • This test case checks if the `owner` of the `Vault` contract can be changed to a new address.
  • It expects the `owner` to be updated to the new address.
  it('Should change the owner', async function () {
    const accounts = await ethers.getSigners();
    const newOwner = await accounts[1].getAddress();
    await vault.changeOwner(newOwner);
    const ownerAddress = await vault.owner();
    assert.equal(ownerAddress, newOwner);
  });

Should allow the owner to withdraw funds

  • This test case checks if the `owner` of the `Vault` contract can withdraw funds.
  • It deposits some funds to the contract, withdraws a portion, and checks if the balance is updated correctly.
  • It expects the balance after withdrawal to be equal to the balance before withdrawal minus the withdrawal amount.
  it('Should allow owner to withdraw funds', async function () {
    const accounts = await ethers.getSigners();
    const owner = await vault.owner();
    assert.equal(owner, await accounts[0].getAddress());
    const depositAmount = 1000;
    await vault.deposit({ value: depositAmount });
    const balanceBeforeWithdraw = await vault.balances(owner);
    const withdrawAmount = 100;
    await vault.ownerWithdraw(withdrawAmount);
    const balanceAfterWithdraw = await vault.balances(owner);
    assert.equal(
      balanceBeforeWithdraw.sub(withdrawAmount).toString(),
      balanceAfterWithdraw.toString()
    );
  });

Should not allow non-owner to withdraw funds

  • This test case checks if a non-owner address is not able to withdraw funds from the `vault` contract.
  • It deposits some funds to the contract with the `alice` address, then tries to withdraw funds using the same address.
  • It expects an error to be thrown with the message 'Only owner can withdraw'.
  it('Should not allow non-owner to withdraw funds', async () => {
    const depositAmount = ethers.utils.parseEther('1');
    const withdrawAmount = ethers.utils.parseEther('0.5');

    await vault.deposit({ value: depositAmount, from: alice });

    try {
      await vault.ownerWithdraw(withdrawAmount, { from: alice });
    } catch (error) {
      assert.include(error.message, 'Only owner can withdraw');
    }
  });

Should allow the user to release their funds after the lock duration

  • This test case checks if a user is able to release their deposited funds after the lock duration has passed.
  • It deposits some funds to the contract, waits for the lock duration to pass, and then releases the funds.
  • It expects the final balance to be greater than the initial balance.
  it('should allow the user to release their funds after lock duration', async function () {
    const accounts = await ethers.getSigners();
    const owner = await vault.owner();
    assert.equal(owner, await accounts[0].getAddress());
    const depositAmount = 1000;
    await vault.deposit({ value: depositAmount });
    await time.increase(86400);
    const initialBalance = await vault.balances(owner);
    await vault.releaseAmount();
    const finalBalance = await vault.balances(owner);

    expect(finalBalance > initialBalance);
  });

 

Overall, the Vault contract and its test cases demonstrate the basic functionality of a smart contract that allows users to deposit and withdraw funds securely. The tests ensure that the contract behaves as expected and that users' funds are protected from unauthorized access.

Thank you!

Enjoyed the SmartBook?
Like
logo
contact@dapp-world.com
Katraj, Pune, Maharashtra, India - 411048

Follow Us

linkedintwitteryoutubediscordinstagram

Products

  • SmartBooks
  • Courses
  • Quizzes
  • Assessments

Support

  • Contact Us
  • FAQ
  • Privacy Policy
  • T&C

Backed By

ah! ventures

Copyright 2023 - All Rights Reserved.

Recommended from DAppWorld
img
1 May 2021
How to connect Ganache with Metamask and deploy Smart contracts on remix without
Set up your development environment with (Metamask + Ganache + Remix) and skip truffle :)
3 min read
11509
5
img
8 Jul 2021
How to interact with smart contarct from backend node js
call and send functions from backend server side using nodejs
3 min read
8103
2
img
18 Aug 2021
Send transaction with web3 using python
Introduction to web3.py and sending transaction on testnet
3 min read
6229
5
img
5 Aug 2021
Deploy Smart Contract on Polygon POS using Hardhat
how to deploy smart contracts on polygon pos chain using hardhat both mainnet and testnet ?
3 min read
5540
3