Skip to content

Commit

Permalink
added initial vault contract
Browse files Browse the repository at this point in the history
  • Loading branch information
yashgo0018 committed Dec 6, 2023
1 parent 504aa13 commit 52cdc27
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 2 deletions.
97 changes: 97 additions & 0 deletions contracts/Vault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "./VaultShareToken.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract Vault {
VaultShareToken public shareToken;
IERC20 public stableToken;

address[] public supportedTokens;

event Deposit(address indexed from, uint256 amount);
event Withdraw(address indexed to, uint256 amount);
event AddSupportedToken(address indexed token);
event RemoveSupportedToken(address indexed token);

constructor(IERC20 _stableToken) {
stableToken = _stableToken;
shareToken = new VaultShareToken();
supportedTokens.push(address(stableToken));

emit AddSupportedToken(address(stableToken));
}

function addSupportedToken(address _token) public {
supportedTokens.push(_token);

emit AddSupportedToken(_token);
}

function removeSupportedToken(address _token) public {
uint256 totalSupportedTokens = supportedTokens.length;
for (uint256 i = 0; i < totalSupportedTokens; i++) {
if (supportedTokens[i] == _token) {
supportedTokens[i] = supportedTokens[totalSupportedTokens - 1];
supportedTokens.pop();
emit RemoveSupportedToken(_token);
return;
}
}

revert("Token not found");
}

function deposit(uint256 amount) public {
if (stableToken.allowance(msg.sender, address(this)) < amount)
revert("Insufficient allowance");

if (stableToken.balanceOf(msg.sender) < amount)
revert("Insufficient balance");

uint256 shares = calculateShares(amount);

shareToken.mint(msg.sender, shares);

stableToken.transferFrom(msg.sender, address(this), amount);
emit Deposit(msg.sender, amount);
}

function withdraw(uint256 amount) public {
if (shareToken.balanceOf(msg.sender) < amount)
revert("Insufficient balance");
uint256 totalSupply = shareToken.totalSupply();
shareToken.burn(msg.sender, amount);

uint256 totalSupportedTokens = supportedTokens.length;
for (uint256 i = 0; i < totalSupportedTokens; i++) {
address tokenAddress = supportedTokens[i];
IERC20 token = IERC20(tokenAddress);
uint256 share = (token.balanceOf(address(this)) * amount) /
totalSupply;
token.transfer(msg.sender, share);
}

emit Withdraw(msg.sender, amount);
}

function calculateShares(
uint256 amount
) public view returns (uint256 share) {
uint256 totalValue = calculateTotalValue();
uint256 currentTotalSupply = shareToken.totalSupply();
if (totalValue == 0 || currentTotalSupply == 0) share = amount;
else share = (currentTotalSupply * amount) / totalValue;
}

function calculateTotalValue() public view returns (uint256 total) {
total = IERC20(supportedTokens[0]).balanceOf(address(this));

uint256 totalSupportedTokens = supportedTokens.length;
for (uint256 i = 1; i < totalSupportedTokens; i++) {
address tokenAddress = supportedTokens[i];
total += IERC20(tokenAddress).balanceOf(address(this)) * 1;
}
}
}
18 changes: 18 additions & 0 deletions contracts/VaultShareToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/// @custom:security-contact [email protected]
contract VaultShareToken is ERC20, Ownable {
constructor() ERC20("Vault Share Token", "VST") Ownable(msg.sender) {}

function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}

function burn(address from, uint256 amount) public onlyOwner {
_burn(from, amount);
}
}
114 changes: 114 additions & 0 deletions test/Vault.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {
Account,
Chain,
GetContractReturnType,
PublicClient,
Transport,
WalletClient,
parseEther,
} from "viem";
import { Vault$Type } from "../artifacts/contracts/Vault.sol/Vault";
import { viem } from "hardhat";
import { MockERC20$Type } from "../artifacts/contracts/test/MockERC20.sol/MockERC20";
import { IERC20$Type } from "../artifacts/@openzeppelin/contracts/token/ERC20/IERC20.sol/IERC20";
import { expect } from "chai";

describe("Vault", function () {
let wallets: WalletClient[];
let publicClient: PublicClient;
let vault: GetContractReturnType<
Vault$Type["abi"],
PublicClient<Transport, Chain>,
WalletClient<Transport, Chain, Account>
>;
let usdc: GetContractReturnType<
MockERC20$Type["abi"],
PublicClient<Transport, Chain>,
WalletClient<Transport, Chain, Account>
>;
let vaultShare: GetContractReturnType<
IERC20$Type["abi"],
PublicClient<Transport, Chain>,
WalletClient<Transport, Chain, Account>
>;
const accounts: Account[] = [];

this.beforeAll(async () => {
wallets = await viem.getWalletClients();
publicClient = await viem.getPublicClient();

for (const wallet of wallets) {
if (wallet.account) accounts.push(wallet.account);
}

usdc = await viem.deployContract("MockERC20", []);

vault = await viem.deployContract("Vault", [usdc.address]);

const shareTokenAddress = await vault.read.shareToken();

vaultShare = await viem.getContractAt("IERC20", shareTokenAddress);
});

it("Should mint some usdc tokens", async () => {
await usdc.write.mint([accounts[0].address, parseEther("1000000")]);
await usdc.write.mint([accounts[1].address, parseEther("1000000")]);
await usdc.write.mint([accounts[2].address, parseEther("1000000")]);
await usdc.write.mint([accounts[3].address, parseEther("1000000")]);
await usdc.write.mint([accounts[4].address, parseEther("1000000")]);
await usdc.write.mint([accounts[5].address, parseEther("1000000")]);
});

it("Should not deposit if the usdc is not approved", async () => {
await expect(vault.write.deposit([parseEther("1000")])).to.be.rejectedWith(
"Insufficient allowance"
);
});

it("Should not deposit if insufficient usdc balance", async () => {
await usdc.write.approve([vault.address, parseEther("100000000")]);

await expect(
vault.write.deposit([parseEther("100000000")])
).to.be.rejectedWith("Insufficient balance");
});

it("Should deposit some usdc in the vault contract", async () => {
await vault.write.deposit([parseEther("100000")]);

expect(await vaultShare.read.balanceOf([accounts[0].address])).to.be.eq(
parseEther("100000")
);
});

it("Should deposit some more tokens in the vault contract", async () => {
await usdc.write.approve([vault.address, parseEther("100000000")], {
account: accounts[1],
});
await vault.write.deposit([parseEther("100000")], { account: accounts[1] });

expect(await vaultShare.read.balanceOf([accounts[1].address])).to.be.eq(
parseEther("100000")
);

expect(await vault.read.calculateTotalValue()).to.be.eq(
parseEther("200000")
);
});

it("Should withdraw some tokens from the vault contract", async () => {
await vault.write.withdraw([parseEther("10000")]);

expect(await vaultShare.read.balanceOf([accounts[0].address])).to.be.eq(
parseEther("90000")
);

expect(await vault.read.calculateTotalValue()).to.be.eq(
parseEther("190000")
);

expect(await usdc.read.balanceOf([accounts[0].address])).to.be.eq(
parseEther("910000")
);
});
});
2 changes: 0 additions & 2 deletions test/chainlink-functions-simulators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export async function startSimulator({
}) {
const functionsRouter = await viem.deployContract("MockFunctionsRouter");

// const eventFilter = functionsRouter.createEventFilter.RequestCreated();
functionsRouter.watchEvent.RequestCreated({
onLogs: async (logs) => {
for (const log of logs) {
Expand All @@ -42,7 +41,6 @@ export async function startSimulator({

decodedData[tag] = value;
}

const code = `
class Functions {
static encodeString(s) {
Expand Down

0 comments on commit 52cdc27

Please sign in to comment.