#2 Basic Vault
https://github.com/yieldprotocol/mentorship2022/issues/2
Problem statement: https://github.com/calnix/Basic-Vault
Objectives: multi-user safe
This is a single-token Vault that holds a pre-specified erc-20 token.
Users can send tokens to the Vault contract.
Vault contract records the user's token deposits.
Users can withdraw their tokens only to the address they deposited from.
Contracts
ERC20Mock.sol
BasicVault.sol
Vault contract
Deposit: After approval (handles by front-end), token is deposited via transferFrom.
Withdraw: Token is returned, and the Vault updates its user records.
Vault contract should import the IERC20 interface, take the Token address in the Vault constructor, and cast it into an IERC20 state variable
ERC2Mock contract
Public, unrestricted mint() function (anyone should be able to mint)
Use Yield's version:
import https://github.com/yieldprotocol/yield-utils-v2/blob/main/contracts/mocks/ERC20Mock.sol
Additional Details
Add full NatSpec
contracts should have @title, @notice, @dev, @author.
functions should have @notice, @dev, @param, @return.
events should have @notice (no need param or return).
2. Implement CI so when you push to Github, your tests are run automatically.
https://gist.github.com/clifton/b5ee5286bb229281fb31d7c4b15e6f31
https://book.getfoundry.sh/config/continous-integration.html
Code
Checks-effect-interaction pattern
Why does it matter?
When calling an external address, for example when transferring tokens to another account, the calling contract is also transferring the control flow to the external entity.
Assuming this external entity is a smart contract as well, the external entity is now in charge of the control flow and can execute any inherent code within it.
This can leave your contract open to re-entrancy attacks.
The high-level idea is as follows:
check & update the internal states (balances)
keep external interactions (function calls) to the last step
Essentially we will update our mappings balances to reflect any outflow, before initiating the transfer.
Further reading: https://www.securing.pl/en/reentrancy-attack-in-smart-contracts-is-it-still-a-problem/
Testing
Vault contract should be fully tested.
No need to test ERC20Mock. Assume it has been battle-tested.
WMDTokenWithFailedTransfers.sol is used to simulate token transfer failure, so that we can ensure our Vault functions behave accordingly in such situations.
Testing States
StateZero (user has no tokens)
cannot deposit
(testUserCannotWithdraw)cannot withdraw
(testUserCannotDeposit)fuzz testing
(testUserMintApproveDeposit)
StateTokensMinted (user has minted 100 wmd tokens - only action available is deposit)
if transfer fails, deposit should revert
(testDepositRevertsIfTransferFails)deposit tokens into vault
(testDeposit)
StateTokensDeposited (user has deposited tokens into Vault)
cannot withdraw if transfer fails
(testWithdrawRevertsIfTransferFails)cannot withdraw more than deposit
(testUserCannotWithdrawExcessOfDeposit)partial withdrawal
(testUserWithdrawPartial)full withdrawal
(testUserWithdrawAll)
It’s better to test for a specific error type, than use vm.expectRevert as a catch-all leading to false positives.
As such, with regards to failing token transfers, observe the use of stdError.arithmeticError in both
testFuzzUserCannotWithdraw
testUserCannotWithdrawExcessOfDeposit
https://book.getfoundry.sh/reference/forge-std/arithmeticError.html
Testing Guidelines
All state variable changes in the contracts that you code.
All state variable changes in other contracts caused by calls from contracts that you code.
All require or revert in the contracts that you code.
All events being emitted.
All return values in contracts that you code.
I realized that for future testing perhaps splitting each state into a different file would be more sensible and accessible for the reader.
Github CI
Its interesting to note that I could not apply this with windows-latest container. The action Install Foundry will return an Error: Unexpected HTTP response: 404)
Deployment
On Rinkeby
WMD Token: https://rinkeby.etherscan.io/address/0x944403ee436a6dff974983a2fa84ff37c587bad1#writeContract
Vault: https://rinkeby.etherscan.io/address/0xc5a93d9c0337352f0c2fd2743a2ffffd69818486#writeContract
As previously mentioned, the values of contract address and GUID have to be updated in .env as we flow through the make commands.
When deploying the Vault contract, we need to pass the WMDToken contract address together with the flag, like so:
Subsequently, when verifying we need to pass the abi-encoded constructor arguments together with the --constructor-args flag
Take the output from above and pass it into your verify command, like so:
Last updated