-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: added erc 4626 token standard example (#538) #602
Merged
Merged
Changes from 13 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
4acdef3
update(docs): upgraded OZ packages to latest version 5.0.0
quiet-node 8c26c56
update: removed ERC20SnapshotMock contract
quiet-node ef55ecd
update: replaced OZ/SafeMath.sol to OZ/Math.sol
quiet-node dc15e01
fix: replaced _getImplementation() with ERC1967Utils.getImplementatio…
quiet-node 2de2dcf
fix: passed arguments to the new Ownable base constructor
quiet-node e586193
fix: fixed warnings
quiet-node 20f4858
update: re-compiled all contracts
quiet-node 97966be
WIP. Adding tests around the OZ ERC-4626 Token vault example.
ebadiere 68ecfdb
WIP. Completed coverage. Needs to be updated to run against the loca…
ebadiere ec33d2d
Merge branch 'main' into 538-ERC-4626-token-standard-example
ebadiere 39a9c1f
Updates to contract revert tests to check encoded transaction data un…
ebadiere c010eef
Updated contract revert issue to the relay issue. #1916 getTransactio…
ebadiere 4533527
Applied feedback
ebadiere c5c1493
Set the revert reasons on.
ebadiere 3b3b8a7
Refactored to evaulate the results of eth_estimateGase.
ebadiere 3d9f724
Removed two tests that covered zero deposit and withdraw amounts, as …
ebadiere 54d0d41
Cleaned up label.
ebadiere File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
const { expect } = require("chai"); | ||
const { ethers } = require("hardhat"); | ||
const Constants = require('../../constants') | ||
|
||
describe("@OZTokenValut TokenVault Contract", function () { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: The OZ flag has a typo There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated. |
||
let TokenVault; | ||
let tokenVault; | ||
let ERC20Mock; | ||
let asset; | ||
let owner; | ||
let addr1; | ||
let addr2; | ||
let addrs; | ||
|
||
async function parseRevertReason(txHash) { | ||
const txResponse = await ethers.provider.getTransaction(txHash); | ||
const txReceipt = await ethers.provider.getTransactionReceipt(txHash); | ||
|
||
// Extract the revert reason | ||
const code = txReceipt.logs[txReceipt.logs.length - 1].data; | ||
const reason = ethers.utils.defaultAbiCoder.decode(["string"], code)[0]; | ||
return reason; | ||
} | ||
|
||
beforeEach(async function () { | ||
// Deploy the mock ERC20 token | ||
ERC20Mock = await ethers.getContractFactory("contracts/erc-20/ERC20Mock.sol:ERC20Mock"); | ||
asset = await ERC20Mock.deploy("MockToken", "MTK"); | ||
await asset.deployed(); | ||
|
||
// Deploy the TokenVault contract | ||
TokenVault = await ethers.getContractFactory("TokenVault"); | ||
tokenVault = await TokenVault.deploy(asset.address, "MockToken", "MTK", Constants.GAS_LIMIT_1_000_000); | ||
await tokenVault.deployed(); | ||
|
||
// Get signers | ||
[owner, addr1, addr2, ...addrs] = await ethers.getSigners(); | ||
|
||
// Mint some tokens to the first address | ||
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 fail if deposit is less than zero", async function () { | ||
const depositTxPromise = tokenVault.connect(addr1)._deposit(0); | ||
|
||
// Expect the transaction to be reverted with the specific error message | ||
// Revert reason is not available at this time, https://github.com/hashgraph/hedera-json-rpc-relay/issues/1916 | ||
// await expect(depositTxPromise).to.be.revertedWith("Deposit is zero"); | ||
|
||
let receipt; | ||
try { | ||
|
||
const depositTx = await depositTxPromise; | ||
receipt = await depositTx.wait(); | ||
|
||
} catch (error) { | ||
|
||
// Handle the expected revert here until revert reason is available, https://github.com/hashgraph/hedera-json-rpc-relay/issues/1916 | ||
if (error.code === ethers.errors.CALL_EXCEPTION) { | ||
let reason = "Unknown Error"; | ||
if (error.transaction) { | ||
expect(error.transaction.data).to.equal('0x9213b1240000000000000000000000000000000000000000000000000000000000000000'); | ||
} else { | ||
assert.fail("No transaction data"); | ||
} | ||
} | ||
|
||
} | ||
}); | ||
|
||
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 zero", async function () { | ||
// Expect the transaction to be reverted with the specific error message | ||
// Revert reason is not available at this time, https://github.com/hashgraph/hedera-json-rpc-relay/issues/1916 | ||
// await expect(tokenVault.connect(addr1)._withdraw(0, addr1.address)).to.be.revertedWith("withdraw must be greater than Zero"); | ||
let receipt; | ||
try { | ||
|
||
const depositTx = await tokenVault.connect(addr1)._withdraw(0, addr1.address); | ||
receipt = await depositTx.wait(); | ||
|
||
} catch (error) { | ||
|
||
// Handle the expected revert here until revert reason is available, https://github.com/hashgraph/hedera-json-rpc-relay/issues/1916 | ||
if (error.code === ethers.errors.CALL_EXCEPTION) { | ||
let reason = "Unknown Error"; | ||
if (error.transaction) { | ||
expect(error.transaction.data).to.equal('0x293311ab0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000927e41ff8307835a1c081e0d7fd250625f2d4d0e'); | ||
} else { | ||
assert.fail("No transaction data"); | ||
} | ||
} | ||
} | ||
}); | ||
|
||
it("Should fail if withdraw is to zero address", async function () { | ||
// Expect the transaction to be reverted with the specific error message | ||
// Revert reason is not available at this time, https://github.com/hashgraph/hedera-json-rpc-relay/issues/1916 | ||
// await expect(tokenVault.connect(addr1)._withdraw(1, ethers.constants.AddressZero)).to.be.revertedWith("Zero Address"); | ||
let receipt; | ||
try { | ||
|
||
const depositTx = await tokenVault.connect(addr1)._withdraw(1, ethers.constants.AddressZero); | ||
receipt = await depositTx.wait(); | ||
|
||
} catch (error) { | ||
|
||
// Handle the expected revert here until revert reason is available, https://github.com/hashgraph/hedera-json-rpc-relay/issues/1916 | ||
if (error.code === ethers.errors.CALL_EXCEPTION) { | ||
let reason = "Unknown Error"; | ||
if (error.transaction) { | ||
expect(error.transaction.data).to.equal('0x293311ab00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000'); | ||
} else { | ||
assert.fail("No transaction data"); | ||
} | ||
} | ||
} | ||
}); | ||
|
||
it("Should fail if not a shareholder", async function () { | ||
// Expect the transaction to be reverted with the specific error message | ||
// Revert reason is not available at this time, https://github.com/hashgraph/hedera-json-rpc-relay/issues/1916 | ||
// await expect(tokenVault.connect(addr2)._withdraw(1, addr2.address)).to.be.revertedWith("Not a shareHolder"); | ||
let receipt; | ||
try { | ||
|
||
const depositTx = await tokenVault.connect(addr2)._withdraw(1, addr2.address); | ||
receipt = await depositTx.wait(); | ||
|
||
} catch (error) { | ||
|
||
// Handle the expected revert here until revert reason is available, https://github.com/hashgraph/hedera-json-rpc-relay/issues/1916 | ||
if (error.code === ethers.errors.CALL_EXCEPTION) { | ||
let reason = "Unknown Error"; | ||
if (error.transaction) { | ||
expect(error.transaction.data).to.equal('0x293311ab0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c37f417fa09933335240fca72dd257bfbde9c275'); | ||
} else { | ||
assert.fail("No transaction data"); | ||
} | ||
} | ||
} | ||
}); | ||
|
||
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 the transaction to be reverted with the specific error message | ||
// Revert reason is not available at this time, https://github.com/hashgraph/hedera-json-rpc-relay/issues/1916 | ||
// await expect(tokenVault.connect(addr1)._withdraw(depositAmount.add(1), addr1.address)).to.be.revertedWith("Not enough shares"); | ||
let receipt; | ||
try { | ||
|
||
const depositTx = await tokenVault.connect(addr1)._withdraw(depositAmount.add(1), addr1.address); | ||
receipt = await depositTx.wait(); | ||
|
||
} catch (error) { | ||
|
||
// Handle the expected revert here until revert reason is available, https://github.com/hashgraph/hedera-json-rpc-relay/issues/1916 | ||
if (error.code === ethers.errors.CALL_EXCEPTION) { | ||
let reason = "Unknown Error"; | ||
if (error.transaction) { | ||
expect(error.transaction.data).to.equal('0x293311ab0000000000000000000000000000000000000000000000008ac7230489e80001000000000000000000000000927e41ff8307835a1c081e0d7fd250625f2d4d0e'); | ||
} else { | ||
assert.fail("No transaction data"); | ||
} | ||
} | ||
} | ||
}); | ||
|
||
}); | ||
|
||
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); | ||
}); | ||
}); | ||
|
||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to removed this last time iirc, and Nana told me that we should still keep this until new Cancun EVM release which will remove the support for selfdestruct opcode
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how this got into the review. I may have run a rebase, but I didn't work on that contract.