Replies: 7 comments 9 replies
-
While I take the time to consider the other points, I will say I find points 2 and 4 nice. I don't think you remember, but our initial conversation around the periphery contracts was to have them become "helper contracts". These would get us additional info we need from the main contracts. Anything from simple getters ( Therefore, I think we should keep the "actions" (re. point 1) in the main contracts, while moving any non vital methods in a separate contract. For context, keeping important actions in the same place has multiple advantages: events/errors are emitted by the same contract, integrators have a more limited number of endpoints they have to consider etc. |
Beta Was this translation helpful? Give feedback.
-
I just added a new idea to the list, which is to delete the Update: I implemented this on the prb/recipient-owner-of branch to see what kind of bytecode saving we would get, and unfortunately we wouldn't get much.
For ~0.05kB, I think that keeping the |
Beta Was this translation helpful? Give feedback.
-
Didn't think about this but you are right, we don't need this function in the core.
To address @razgraf's concern, we can call the
As I said before, this is a MUST.
I've always been pro to this idea, if you remember I made a PR in your repo. This will also solve other issues, like the
I would suggest leaving this idea as a last resort, given the gas differences.
As I said in the other discussions, I don't think this is a good idea.
I don't think this is possible. The More potential solutions
@PaulRBerg I recommend you to take a look to aave's repo to see how they implemented the libraries system. They barely have actual logic in the contracts. |
Beta Was this translation helpful? Give feedback.
-
Looks like we have a winner with moving I removed these functions locally and re-run |
Beta Was this translation helpful? Give feedback.
-
I just added a new idea to the list, which is to tune down the number of optimizer runs. |
Beta Was this translation helpful? Give feedback.
-
I have spent a few hours today benchmarking the gas optimizations we various numbers of optimizer runs (1k, 5k, 10k, 50k, and 4,294,967,295, the maximum allowed), and, based on the findings reported here, I can confidently say that tuning down the optimizer runs is the way to go to reduce the size of the pro contract. Between 5k runs (a number of optimizer runs that would get the size of the contract down to 24.438kB) and 4,294,967,295, there is an average gas saving of only ~1,751 (that is, for the fuzzed calls made to the I think that this is a fair price to pay for our ability to get to mainnet sooner than later and peace of mind regarding the pro contract (fewer refactors means fewer changing assumptions). Also, this gas cost is negligible on non-Ethereum chains. Do you agree, @andreivladbrg? |
Beta Was this translation helpful? Give feedback.
-
Locking, ended up reducing the number of optimizer runs to 5,000 in this commit: |
Beta Was this translation helpful? Give feedback.
-
Problem
The current size of the
SablierV2LockupPro
is ~25.49kB, which is ~0.914kB higher than the contract code size limit of ~24.576kB implemented in EIP-170.To be able to deploy this contract to mainnet, we have to get it under the limit.
Potential Solutions
There is no silver-bullet solution - we will have to be creative and employ a mixed approach. Below I have suggested a few potential solutions, ordered by desirability and implementation difficulty.
1. Move
createWithDurations
in the peripheryIt is not necessary to keep the
createWithDurations
function in the core contracts - all this function does is to compute the parameters for thecreateWithRange
function dynamically using theblock.timestamp
at the time of including the transaction in the block.Similar to the
createMultiple
function(s) that we plan on implementing in the periphery, we could move this function over there.2. Move the
getStreamed
helpers in an external libraryAs suggested by @andreivladbrg in this comment, we could move the
getStreamed
helpers in a separate library:getStreamedAmountForMultipleSegments
getStreamedAmountForOneSegment
As I said in that other conversation, one other reason I like this idea is that it makes it possible to perform the pro calculations from outside of the pro contracts - which might be helpful for integrators, or in our periphery contracts.
3. ERC-721 Implementation with Custom Errors
This would reduce the bytecode significantly, but unfortunately there is no good, off-the-shelf implementation of ERC-721 that uses custom errors. I have asked about this on Twitter, and I got no answer.
OpenZeppelin has opened EIP-6093, which purports to retroactively add custom errors to the popular token standards ERC-20, ERC-721, and ERC-1155. However, the EIP is still in draft, and they are still working on implementing it in their own contracts library.
Therefore, the only workaround in the short term would be to implement this ourselves in prb-contracts.
4. Move the getters to the periphery
The idea is to move all the static
get
functions (that is, all except the calculation functions) to the periphery.The counter-argument to this is that it would deteriorate the developer experience for integrators, and really anyone else who'd like to query the data from the core contracts directly, e.g. Etherscan users.
5. Remove flash loan support
See the discussion I opened here:
#278
6. Merge thegetRecipient
function withownerOf
We have a functiongetRecipient
that does the same thing as the ERC-721'sownerOf
function.We provide the former function to provide context to our users (so we have an on-chain reference to who is the "recipient of the stream"). But we might have to jettison this function to reduce the size of the pro contract .. after all, the vast majority of Sablier users will use a UI to access the protocol. The UI can make the translation for the user, and refer to the NFT owner as the recipient.Update: the bytecode saving for doing this would be a paltry ~0.05kB. See my comment below.
7. Delete the
renounceAdmin
functionThis wouldn't save much gas, but the
renounceAdmin
of theAdminable
contract is technically redundant. We could refactor theAdminable
contract that has norenounceAdmin
but which contains only atransferAdmin
function that accepts a zero address as an argument.Update: actually, we should definitely do, see #289.
8. Tune down the optimizer
It just dawned on me that the simplest way to lower the contract size is to lower the number of optimizer runs. This is explained in detail in the Downsizing contracts guide on the Ethereum website, and also in the Solidity docs on the optimizer.
Just by changing the number of optimizer runs to 5,000, I have managed to get the size of the pro contract down to ~24.472kB.
The only caveat to this solution is that it goes against what we want - we would ideally set the optimizer runs to 2^32-1, just like Seaport does, since we expect the Sablier V2 contracts to be called lots and lots of time during their lifetimes (they are singleton contracts, after all - storage and functionality and bundled together).
See related discussion in #290.
Beta Was this translation helpful? Give feedback.
All reactions