Skip to content

Commit

Permalink
feat: added erc 4626 token standard example (#538) (#602)
Browse files Browse the repository at this point in the history
* update(docs): upgraded OZ packages to latest version 5.0.0

Signed-off-by: Logan Nguyen <[email protected]>

* update: removed ERC20SnapshotMock contract

Signed-off-by: Logan Nguyen <[email protected]>

* update: replaced OZ/SafeMath.sol to OZ/Math.sol

Signed-off-by: Logan Nguyen <[email protected]>

* fix: replaced _getImplementation() with ERC1967Utils.getImplementation();

Signed-off-by: Logan Nguyen <[email protected]>

* fix: passed arguments to the new Ownable base constructor

Signed-off-by: Logan Nguyen <[email protected]>

* fix: fixed warnings

Signed-off-by: Logan Nguyen <[email protected]>

* update: re-compiled all contracts

Signed-off-by: Logan Nguyen <[email protected]>

* WIP. Adding tests around the OZ ERC-4626 Token vault example.

Signed-off-by: ebadiere <[email protected]>

* WIP. Completed coverage.  Needs to be updated to run against the local node.
Signed-off-by: ebadiere <[email protected]>

* Updates to contract revert tests to check encoded transaction data until a
solution for returning the contract revert reason is found.

Signed-off-by: ebadiere <[email protected]>

* Updated contract revert issue to the relay issue. #1916 getTransactionReceipt

Signed-off-by: ebadiere <[email protected]>

* Applied feedback

Signed-off-by: ebadiere <[email protected]>

* Set the revert reasons on.

Signed-off-by: ebadiere <[email protected]>

* Refactored to evaulate the results of eth_estimateGase.

Signed-off-by: ebadiere <[email protected]>

* Removed two tests that covered zero deposit and withdraw amounts, as they
may be confusing depending on what node they are running against.

Signed-off-by: ebadiere <[email protected]>

* Cleaned up label.

Signed-off-by: ebadiere <[email protected]>

---------

Signed-off-by: Logan Nguyen <[email protected]>
Signed-off-by: ebadiere <[email protected]>
Co-authored-by: Logan Nguyen <[email protected]>
  • Loading branch information
ebadiere and quiet-node authored Dec 12, 2023
1 parent 5804b9d commit 81df819
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 56 deletions.
13 changes: 0 additions & 13 deletions artifacts/contracts/proxy-upgrade/counter/Counter.sol/Counter.json
Original file line number Diff line number Diff line change
Expand Up @@ -252,19 +252,6 @@
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newImplementation",
"type": "address"
}
],
"name": "upgradeTo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,19 +265,6 @@
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newImplementation",
"type": "address"
}
],
"name": "upgradeTo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,19 +311,6 @@
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newImplementation",
"type": "address"
}
],
"name": "upgradeTo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,19 +311,6 @@
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newImplementation",
"type": "address"
}
],
"name": "upgradeTo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
Expand Down
61 changes: 61 additions & 0 deletions contracts/oz/erc-4626/TokenVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.20;

import "../../erc-20/ERC20Mock.sol";

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


contract TokenVault is ERC4626 {

// a mapping that checks if a user has deposited the token
mapping(address => uint256) public shareHolders;

constructor(
IERC20 _asset,
string memory _name,
string memory _symbol
) ERC4626(_asset) ERC20(_name, _symbol) {

}

function _deposit(uint256 _assets) public {
// checks that the deposited amount is greater than zero.
require(_assets > 0, "Deposit is zero");
// calling the deposit function from the ERC-4626 library to perform all the necessary functionality
deposit(_assets, msg.sender);
// Increase the share of the user
shareHolders[msg.sender] += _assets;
}

function _withdraw(uint256 _shares, address _receiver) public {
// checks that the deposited amount is greater than zero.
require(_shares > 0, "withdraw must be greater than Zero");
// Checks that the _receiver address is not zero.
require(_receiver != address(0), "Zero Address");
// checks that the caller is a shareholder
require(shareHolders[msg.sender] > 0, "Not a shareHolder");
// checks that the caller has more shares than they are trying to withdraw.
require(shareHolders[msg.sender] >= _shares, "Not enough shares");
// Calculate 10% yield on the withdraw amount
uint256 percent = (10 * _shares) / 100;
// Calculate the total asset amount as the sum of the share amount plus 10% of the share amount.
uint256 assets = _shares + percent;
// Decrease the share of the user
shareHolders[msg.sender] -= _shares;
// calling the redeem function from the ERC-4626 library to perform all the necessary functionality
redeem(assets, _receiver, msg.sender);
}

// returns total number of assets
function totalAssets() public view override returns (uint256) {
return super.totalAssets();
}

function totalAssetsOfUser(address _user) public view returns (uint256) {
return shareHolders[_user];
}

}
4 changes: 0 additions & 4 deletions contracts/solidity/inhetitance/Main.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,4 @@ contract Main is Base {
function returnSuper() public view virtual returns (string memory) {
return super.classIdentifier();
}

function destroyContract(address recipient) public {
selfdestruct(payable(recipient));
}
}
92 changes: 92 additions & 0 deletions test/oz/erc-4626/TokenVault.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
const { expect } = require("chai");
const { ethers } = require("hardhat");
const Constants = require('../../constants')

describe("@OZ TokenVault Contract", function () {
let TokenVault;
let tokenVault;
let ERC20Mock;
let asset;
let owner;
let addr1;
let addr2;
let addrs;

beforeEach(async function () {
ERC20Mock = await ethers.getContractFactory("contracts/erc-20/ERC20Mock.sol:ERC20Mock");
asset = await ERC20Mock.deploy("MockToken", "MTK", Constants.GAS_LIMIT_1_000_000);
await asset.deployed();

TokenVault = await ethers.getContractFactory("TokenVault");
tokenVault = await TokenVault.deploy(asset.address, "MockToken", "MTK", Constants.GAS_LIMIT_1_000_000);
await tokenVault.deployed();

[owner, addr1, addr2, ...addrs] = await ethers.getSigners();

await asset.mint(addr1.address, ethers.utils.parseUnits("1000", 18));
await asset.mint(addr2.address, ethers.utils.parseUnits("10", 18));

});

describe("Deployment", function () {
it("Should assign the total supply of tokens to the owner", async function () {
const ownerBalance = await tokenVault.balanceOf(owner.address);
expect(await tokenVault.totalSupply()).to.equal(ownerBalance);
});
});

describe("Transactions", function () {
it("Should deposit tokens and update shareHolders mapping", async function () {
const depositAmount = ethers.utils.parseEther("10");
await asset.connect(addr1).approve(tokenVault.address, depositAmount);
await expect(tokenVault.connect(addr1)._deposit(depositAmount))
.to.emit(tokenVault, "Deposit")
.withArgs(addr1.address, addr1.address, depositAmount, depositAmount);

expect(await tokenVault.shareHolders(addr1.address)).to.equal(depositAmount);
});

it("Should withdraw tokens and update shareHolders mapping", async function () {
const depositAmount = ethers.utils.parseEther("10");
const withdrawAmount = ethers.utils.parseEther("5");
const redemedAmount = ethers.utils.parseEther("5.5");

await asset.connect(addr2).approve(tokenVault.address, depositAmount);
await tokenVault.connect(addr2)._deposit(depositAmount);

await expect(tokenVault.connect(addr2)._withdraw(withdrawAmount, addr2.address))
.to.emit(tokenVault, "Withdraw")
.withArgs(addr2.address, addr2.address, addr2.address, redemedAmount, redemedAmount);

expect(await tokenVault.totalAssetsOfUser(addr2.address)).to.equal(depositAmount.sub(withdrawAmount));
});

it("Should fail if withdraw is to zero address", async function () {
expect(await tokenVault.connect(addr1)._withdraw(1, ethers.constants.AddressZero)).to.be.revertedWith("Zero Address");
});

it("Should fail if not a shareholder", async function () {
expect(await tokenVault.connect(addr2)._withdraw(1, addr2.address)).to.be.revertedWith("Not a shareHolder");
});

it("Should fail if not enough shares", async function () {
const depositAmount = ethers.utils.parseEther("10");
await asset.connect(addr1).approve(tokenVault.address, depositAmount);
await tokenVault.connect(addr1)._deposit(depositAmount);
expect(await tokenVault.connect(addr1)._withdraw(depositAmount.add(1), addr1.address)).to.be.revertedWith("Not enough shares");
});

});

describe("Views", function () {

it("Should return the total assets of a user", async function () {
const depositAmount = ethers.utils.parseEther("10");
await asset.connect(addr1).approve(tokenVault.address, depositAmount);
await tokenVault.connect(addr1)._deposit(depositAmount);

expect(await tokenVault.totalAssetsOfUser(addr1.address)).to.equal(depositAmount);
});
});

});

0 comments on commit 81df819

Please sign in to comment.