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

ERC4626 #3171

Merged
merged 39 commits into from Jun 2, 2022
Merged

ERC4626 #3171

merged 39 commits into from Jun 2, 2022

Conversation

Amxx
Copy link
Collaborator

@Amxx Amxx commented Feb 7, 2022

Fixes #3393

Reference EIP.

We still have not decided the scope of customization we want to include in this implementation (fees?)
Should ERC165 be included? The ERC does not mention it.

Feel free to discuss/ask for features.

PR Checklist

  • Tests
  • Documentation
  • Changelog entry
  • Check ERC compliance

@DanielVF
Copy link
Contributor

We have a rebasing currency, one thing we've done to checkup on exchange's support for it is to create a pair of accounts with matching amounts of our currency, one on-chain, and one on the exchange, then watch the balance changes between them and totals over time.

I've taken a similar approach to testing the both the solmate and this OZ ERC4626 implementation as a wrapper to our rebasing currency. Using a mainnet fork, I've fired up several pairs of accounts - one account of each pair never touches the wrapper, and one use the wrapper. Then at each step, the designated one of random twin pair does a random deposit or withdraw to the wrapper. Then after each step, the underlying currency rebases up (sometimes by up to 5x).

Looks like both solmate and OZ ERC4626 handle this well. There's a small drift between twins from the rounding, but it seems to be the correct size considering the number of transactions, and the overall 10,000,000x'ing of the base supply over the course of the testing. This rounding dust flows nicely to the control account that is just holding the wrapped currency without transacting.

This doesn't really test anything beyond the deposit/withdraw math, but it's comforting to see

@Amxx Amxx marked this pull request as ready for review May 19, 2022 21:30
@frangio
Copy link
Contributor

frangio commented May 20, 2022

I'm thinking we should add fees. Isn't the contract otherwise vulnerable to sandwich attacks of the transaction where interest is accrued? I feel like I must be missing something there. Maybe the interest would just be small enough that a sandwich attack wouldn't really be profitable.

In any case projects might want fees so I feel it's something we should have by default (with a default of 0 fees like the flash mint) without requiring further customization.

@Amxx
Copy link
Collaborator Author

Amxx commented May 21, 2022

It's true that in case the interest is released at once and not using a flash bot, then a sandwich attack can drain the interests. We should document best practices, in particular, if the interest is per block (like vesting) then this attack is not possible.

If we have a clear way of formulating fees, then I would agree we can add it, but it not clear to me that the fees are that simple to set:

  • fees on buy? fees on sell? both?
  • percentage fees? flat fees?

AFAIK, the fees are as simple as "override the previewXxx" function.

@frangio
Copy link
Contributor

frangio commented May 21, 2022

It's not so simple because you need to override the two pairs of functions.

@robercano
Copy link

robercano commented May 22, 2022 via email

@frangio
Copy link
Contributor

frangio commented May 30, 2022

On the question of fees we've decided to release initially without fees. They may be added later.

CHANGELOG.md Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
test/utils/math/Math.test.js Outdated Show resolved Hide resolved
contracts/token/ERC20/extensions/ERC20TokenizedVault.sol Outdated Show resolved Hide resolved
contracts/token/ERC20/extensions/ERC20TokenizedVault.sol Outdated Show resolved Hide resolved
contracts/utils/math/Math.sol Outdated Show resolved Hide resolved
test/token/ERC20/extensions/ERC20TokenizedVault.test.js Outdated Show resolved Hide resolved
test/token/ERC20/extensions/ERC20TokenizedVault.test.js Outdated Show resolved Hide resolved
Comment on lines +7 to +8
const parseToken = (token) => (new BN(token)).mul(new BN('1000000000000'));
const parseShare = (share) => (new BN(share)).mul(new BN('1000000000000000000'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little confusing that this hardcodes 12 and 18 decimals, given that there's also a test where the token has 18 decimals. I think these helpers should be defined inside a describe block where we use these decimals.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they are used everywhere expect for the multiple mint, deposit, redeem & withdrawal test.

@frangio
Copy link
Contributor

frangio commented May 31, 2022

I wanted to analyze the conversion functions to make sure I understood exactly the way we're pricing shares in the degenerate cases.

Our conversion functions are not symmetrical, they differ depending on whether the input is a number of assets or shares (e.g. mint vs deposit). This is our currently implemented behavior:

AS Z NZ
Z 1 !
NZ 1 %
AS Z NZ
Z 1 0
NZ 1 %

(Sorry for the symbols but I liked the succinct presentation. A: totalAssets, S: totalSupply, A→S: convert to shares, A←S: convert to assets, Z: zero, NZ: non-zero, 1: decimals-adjusted 1-to-1 price, %: price based on vault ratio, !: revert)

Note that they differ in the case that there are zero assets and non-zero shares. I don't think this is right, I think we should aim for symmetry. I would suggest reverting independently of the conversion direction.

Amxx and others added 2 commits May 31, 2022 17:03
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
@Amxx
Copy link
Collaborator Author

Amxx commented May 31, 2022

I don't think there is any inconsistency here.

If we have 0 assets, and >0 shares, then:

  • shares are worth 0 assets
  • assets are worth an infinite amount of shares.

So "!" and "0" are in fact the value that corresponds to "%" in that particular case.

@frangio
Copy link
Contributor

frangio commented May 31, 2022

Mathematically % would be ∞ but that is not equivalent to a revert. Still think the asymmetry is a problem. Will try to implement the symmetrical behavior to revert in both cases without too much added overhead.

@Amxx
Copy link
Collaborator Author

Amxx commented May 31, 2022

This last commit adds quite a lot of sloads:

  • totalSupply() can be expected to be dirty, so its only 100 gas each time
  • totalAssets() can also be expected to be dirty, but includes a staticcall to another contract, which is not free.

It also checks totalAssets() in cases where totalSupply() is 0, which used to not be necessary (in that case the sload is cold).
All that to "fix" the behavior in a case where the vault is broken.

This case should never happen unless the vault is overridden in a way to enables slashing or loss of assets. I'm not sure we should include that by default. IMO this is something that should be added only if the vault mechanics are changed in a way that can cause it to be broken. We should not include it by default. Other devs should include it if their instance requires these additional checks

Copy link
Contributor

@frangio frangio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, we're good.

@Amxx Amxx merged commit 5e00787 into OpenZeppelin:master Jun 2, 2022
@Amxx Amxx deleted the feature/ERC4626 branch June 2, 2022 08:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

EIP-4626: Tokenized Vault Standard
6 participants