Skip to content
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

💥 Implement ERC-4626 Tokenised Vault #74

Merged
merged 34 commits into from
Feb 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9960760
🔨 started implementing ERC4626
pcaversaccio Feb 15, 2023
bb2e9f5
📖 add README and CHANGELOG entries
pcaversaccio Feb 16, 2023
7b621eb
🔨 mul_div function
pcaversaccio Feb 16, 2023
5cad78d
🔨 mul_div tests
pcaversaccio Feb 16, 2023
d2acac9
🔨 fix overflow failing test
pcaversaccio Feb 16, 2023
cedc942
🔨 further small optimisation in mul_div
pcaversaccio Feb 17, 2023
58a5824
♻️ fix yarn.lock & update submodules
pcaversaccio Feb 17, 2023
61862fd
Merge branch 'main' into feat/erc4626
pcaversaccio Feb 17, 2023
478d087
🕵️ change `~empty(uint256)` to `max_value(uint256)` for gas optimisation
pcaversaccio Feb 19, 2023
0ad5cf4
♻️ cleanup
pcaversaccio Feb 19, 2023
e2a5b8f
Merge branch 'main' into feat/erc4626
pcaversaccio Feb 19, 2023
c3af8cf
Merge branch 'main' into feat/erc4626
pcaversaccio Feb 20, 2023
3b86485
♻️ bump @types/node
pcaversaccio Feb 20, 2023
b2cb791
Merge branch 'main' into feat/erc4626
pcaversaccio Feb 20, 2023
e8d43ca
👀 first version
pcaversaccio Feb 20, 2023
1d50a47
⛏ fix link in ERC20
pcaversaccio Feb 21, 2023
0457787
✍🏽 code comments
pcaversaccio Feb 21, 2023
7a1f0ca
✍🏽 further code comments
pcaversaccio Feb 21, 2023
c785a4c
✍🏽 further code comments
pcaversaccio Feb 21, 2023
f4bed69
Merge branch 'main' into feat/erc4626
pcaversaccio Feb 22, 2023
f2f8d9e
🔨 erc4626 test setup
pcaversaccio Feb 22, 2023
328a36e
forge install: erc4626-tests
pcaversaccio Feb 22, 2023
de44bef
🔨 erc4626 fuzz tests
pcaversaccio Feb 22, 2023
7046a10
🔨 increase max rejects
pcaversaccio Feb 22, 2023
66ab83a
🔨 initial setup tests
pcaversaccio Feb 23, 2023
f3bc2e0
🔨 testEmptyVaultDeposit
pcaversaccio Feb 23, 2023
f639e9c
🔨 tests EmptyVault
pcaversaccio Feb 24, 2023
d666483
🔨 furter ERC4626 tests
pcaversaccio Feb 24, 2023
cc15b96
🔨 invariant tests
pcaversaccio Feb 24, 2023
896f110
📖 add comments on external code size checks
pcaversaccio Feb 24, 2023
1e6783b
⬆️ update to solc 0.8.19
pcaversaccio Feb 25, 2023
b3e09e3
♻️ bump eslint and submodules
pcaversaccio Feb 26, 2023
5319c91
♻️ refactors and gas snapshot
pcaversaccio Feb 26, 2023
4e8ba3f
♻️ correct test code comments
pcaversaccio Feb 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
📖 add comments on external code size checks
Signed-off-by: Pascal Marco Caversaccio <pascal.caversaccio@hotmail.ch>
  • Loading branch information
pcaversaccio committed Feb 24, 2023
commit 896f110f089e2021a6dddf1193c051f073b90629
17 changes: 15 additions & 2 deletions src/extensions/ERC4626.vy
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,9 @@ def _try_get_underlying_decimals(underlying: ERC20) -> (bool, uint8):
# 32 bytes for the return data in the return expression at the
# end, we also return `False` for EOA wallets instead of reverting
# (remember that the EVM always considers a call to an EOA as
# successful with return data `0x`).
# successful with return data `0x`). Furthermore, it is important
# to note that an external call via `raw_call` does not perform an
# external code size check on the target address.
success, return_data = raw_call(underlying.address, method_id("decimals()"), max_outsize=32, is_static_call=True, revert_on_failure=False)
if (success and (len(return_data) == 32) and (convert(return_data, uint256) <= convert(max_value(uint8), uint256))):
return (True, convert(return_data, uint8))
Expand Down Expand Up @@ -863,6 +865,11 @@ def _deposit(sender: address, receiver: address, assets: uint256, shares: uint25
# - https://github.com/vyperlang/vyper/pull/2839,
# - https://github.com/vyperlang/vyper/issues/2812,
# - https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca.

# It is important to note that an external call via interface casting
# always performs an external code size check on the target address unless
# you add the kwarg `skip_contract_check=True`. If the check fails (i.e.
# the target address is an EOA), the call reverts.
assert asset.transferFrom(sender, self, assets, default_return_value=True), "ERC4626: transferFrom operation did not succeed"
self._mint(receiver, shares)
log Deposit(sender, receiver, assets, shares)
Expand All @@ -885,18 +892,24 @@ def _withdraw(sender: address, receiver: address, owner: address, assets: uint25
# If `asset` is an ERC-777, `transfer` can trigger a reentrancy
# after the transfer happens through the `tokensReceived` hook.
# On the other hand, the `tokensToSend` hook, that is triggered
# before the transfer, calls the vault, which is assumed not to
# before the transfer, calls the vault which is assumed not to
# be malicious. Thus, we need to do the transfer after the burn
# so that any reentrancy would happen after the shares are burned
# and after the assets are transferred, which is a valid state.
self._burn(owner, shares)

# To deal with (potentially) non-compliant ERC-20 tokens that do have
# no return value, we use the kwarg `default_return_value` for external
# calls. This function was introduced in Vyper version 0.3.4. For more
# details see:
# - https://github.com/vyperlang/vyper/pull/2839,
# - https://github.com/vyperlang/vyper/issues/2812,
# - https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca.

# It is important to note that an external call via interface casting
# always performs an external code size check on the target address unless
# you add the kwarg `skip_contract_check=True`. If the check fails (i.e.
# the target address is an EOA), the call reverts.
assert asset.transfer(receiver, assets, default_return_value=True), "ERC4626: transfer operation did not succeed"
log Withdraw(sender, receiver, owner, assets, shares)

Expand Down
8 changes: 8 additions & 0 deletions src/utils/BatchDistributor.vy
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ def distribute_ether(data: Batch):
@notice In the event that excessive ether is sent,
the residual amount is returned back to the
`msg.sender`.

Furthermore, it is important to note that an
external call via `raw_call` does not perform
an external code size check on the target address.
@param data Nested struct object that contains an array
of tuples that contain each a recipient address &
ether amount in wei.
Expand Down Expand Up @@ -88,6 +92,10 @@ def distribute_token(token: ERC20, data: Batch):
for txn in data.txns:
total += txn.amount

# It is important to note that an external call via interface casting
# always performs an external code size check on the target address unless
# you add the kwarg `skip_contract_check=True`. If the check fails (i.e.
# the target address is an EOA), the call reverts.
assert token.transferFrom(msg.sender, self, total, default_return_value=True), "BatchDistributor: transferFrom operation did not succeed"

for txn in data.txns:
Expand Down
17 changes: 17 additions & 0 deletions src/utils/Multicall.vy
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ def multicall(data: DynArray[Batch, max_value(uint8)]) -> DynArray[Result, max_v
function returns successfully if required.
Since this function uses `CALL`, the `msg.sender`
will be the multicall contract itself.
@notice It is important to note that an external call
via `raw_call` does not perform an external code
size check on the target address.
@param data The array of `Batch` structs.
@return DynArray The array of `Result` structs.
"""
Expand Down Expand Up @@ -96,6 +99,9 @@ def multicall_value(data: DynArray[BatchValue, max_value(uint8)]) -> DynArray[Re
if required. Since this function uses `CALL`,
the `msg.sender` will be the multicall contract
itself.
@notice It is important to note that an external call
via `raw_call` does not perform an external code
size check on the target address.
@param data The array of `BatchValue` structs.
@return DynArray The array of `Result` structs.
"""
Expand Down Expand Up @@ -131,6 +137,10 @@ def multicall_self(data: DynArray[BatchSelf, max_value(uint8)]) -> DynArray[Resu
calls in one transaction. Since the `msg.sender` is
preserved, it's equivalent to sending multiple transactions
from an EOA (externally-owned account, i.e. non-contract account).

Furthermore, it is important to note that an external
call via `raw_call` does not perform an external code
size check on the target address.
@param data The array of `BatchSelf` structs.
@return DynArray The array of `Result` structs.
"""
Expand Down Expand Up @@ -164,6 +174,10 @@ def multicall_value_self(data: DynArray[BatchValueSelf, max_value(uint8)]) -> Dy
calls in one transaction. Since the `msg.sender` is
preserved, it's equivalent to sending multiple transactions
from an EOA (externally-owned account, i.e. non-contract account).

Furthermore, it is important to note that an external
call via `raw_call` does not perform an external code
size check on the target address.
@param data The array of `BatchValueSelf` structs.
@return DynArray The array of `Result` structs.
"""
Expand Down Expand Up @@ -192,6 +206,9 @@ def multistaticcall(data: DynArray[Batch, max_value(uint8)]) -> DynArray[Result,
"""
@dev Aggregates static function calls, ensuring that each
function returns successfully if required.
@notice It is important to note that an external call
via `raw_call` does not perform an external code
size check on the target address.
@param data The array of `Batch` structs.
@return DynArray The array of `Result` structs.
"""
Expand Down
5 changes: 4 additions & 1 deletion src/utils/SignatureChecker.vy
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ def _is_valid_ERC1271_signature_now(signer: address, hash: bytes32, signature: B
# check of 32 bytes for the return data in the return expression
# at the end, we also return `False` for EOA wallets instead
# of reverting (remember that the EVM always considers a call
# to an EOA as successful with return data `0x`).
# to an EOA as successful with return data `0x`). Furthermore,
# it is important to note that an external call via `raw_call`
# does not perform an external code size check on the target
# address.
success, return_data = \
raw_call(signer, _abi_encode(hash, signature, method_id=IERC1271_ISVALIDSIGNATURE_SELECTOR), max_outsize=32, is_static_call=True, revert_on_failure=False)
return (success and (len(return_data) == 32) and (convert(return_data, bytes32) == convert(IERC1271_ISVALIDSIGNATURE_SELECTOR, bytes32)))
Expand Down