Refactor PufferProtocol to keep <24KB#119
Conversation
| // will continue. If the `msg.sender` did a `VALIDATOR_TICKET.approve(spender, amount)` before calling this | ||
| // And the spender is `msg.sender` the Permit call will revert, but the overall transaction will succeed | ||
| _callPermit(address(VALIDATOR_TICKET), permit); | ||
| _callPermit(address(_VALIDATOR_TICKET), permit); |
There was a problem hiding this comment.
can remove _callPermit and just inline the code here, should save some gas. Or remove it completely and just do the .approve flow since VT is deprecated.
There was a problem hiding this comment.
Removed the Permit flow completely
| require(validatorSrc.node == msg.sender && validatorSrc.status == Status.ACTIVE, InvalidValidator()); | ||
| srcPubkeys[i] = validatorSrc.pubKey; | ||
| validatorTarget = $.validators[moduleName][targetIndices[i]]; | ||
| require(validatorTarget.node == msg.sender && validatorTarget.status == Status.ACTIVE, InvalidValidator()); |
There was a problem hiding this comment.
Did we doument anywhere that scenario, where Source consolidates into a bad target and NoOp loses money?
There was a problem hiding this comment.
No further work was done in relation to consolidation. We mentioned removing numBatches from Validator info, I still need to check if that's reasonable or breaks some other flow. We'll probably need to sync again on consolidation
| * @inheritdoc IGuardianModule | ||
| */ | ||
| function validateWithdrawalRequest(bytes[] calldata eoaSignatures, bytes32 messageHash) external view { | ||
| function validateWithdrawalRequest( |
There was a problem hiding this comment.
This would require us to deploy a new GuardianModule, we could have this message logic in the protocol, and use
GUARDIAN_MODULE.validateGuardiansEOASignatures
That way, we wouldn't need to re-audit/redeploy
GuardianModule
There was a problem hiding this comment.
We need to re-deploy the GuardianModule anyways since the struct StoppedValidatorsInfo has changes. It was commented in the Pectra PR => #115 (comment)
There was a problem hiding this comment.
If we move that signature logic to the protocol, we might not need to, please check my big comment on the PR
|
You can use the fallback of PufferProtocol to delegateCall to another logic as long as you have the same interface in the logic contract; everything will work. E.G.
```
fallback() external payable { ```
And then in the Logic contract, instead of Maybe it wouldn’t be bad to compose PufferProtocol out of multiple interfaces. Also, since this PufferProtocolLogic is being called by delegateCall, it doesn’t need to have the constants in it as well. You can have the constants from (PufferProtocolBase) only in the PufferProtocol. |
Codecov Report❌ Patch coverage is
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
| InvalidETHAmount() | ||
| ); | ||
|
|
||
| epochsValidatedSignature.functionSelector = _FUNCTION_SELECTOR_DEPOSIT_VALIDATION_TIME; |
There was a problem hiding this comment.
You can use IPufferProtocolLogic.depositValidationTime.selector directly.
| */ | ||
| uint256 internal constant _32_ETH_GWEI = 32 * 10 ** 9; | ||
|
|
||
| bytes32 internal constant _FUNCTION_SELECTOR_REGISTER_VALIDATOR_KEY = |
There was a problem hiding this comment.
You can get rid of all these internal constants and just inline IPufferProtocolLogic.whatever.selector
There was a problem hiding this comment.
I had to create the constants because of the stack too deep. Maybe it isn't necessary anymore. I'll try
|
|
||
| receive() external payable { } | ||
|
|
||
| fallback() external payable { |
There was a problem hiding this comment.
Maybe add a simple explainer.
If a function selector is not found in this contract ...
| revert DeadlineExceeded(); | ||
| } | ||
|
|
||
| _GUARDIAN_MODULE.validateBatchWithdrawals(validatorInfos, guardianEOASignatures, deadline); |
There was a problem hiding this comment.
I'd use _GUARDIAN_MODULE.validateGuardiansEOASignatures. Prepare the message here, in this contract. That way, we won't need to redeploy GuardianModule contract at all
| return; | ||
| } | ||
|
|
||
| _GUARDIAN_MODULE.validateTotalEpochsValidated({ |
There was a problem hiding this comment.
You can do it here as well validateGuardiansEOASignatures
| import { EpochsValidatedSignature } from "./struct/Signatures.sol"; | ||
| import { InvalidAddress } from "./Errors.sol"; | ||
|
|
||
| contract PufferProtocolLogic is PufferProtocolBase, IPufferProtocolLogic { |
There was a problem hiding this comment.
natspec for this contract plz
| * @notice Check IPufferProtocol.withdrawValidationTime | ||
| * @dev This function should only be called by the PufferProtocol contract through a delegatecall | ||
| */ | ||
| function withdrawValidationTime(uint96 amount, address recipient) external override { |
There was a problem hiding this comment.
the natspec says that fn can be called by delegatecall, but since this is a separate contract, can't be this fn directly called by someone? we are not checking anywhere that the caller should be PufferProtocol, or msg.sender == address(this) since this is delegate call. Am I missing something here?
There was a problem hiding this comment.
Think of a Proxy pattern. If you interact with the implementation contract you're not accessing the proxy storage, right? This is the same. If you call the PufferLogicContract you'll be reading/writing the PufferLogicContract storage, not the PufferProtocol one, which is the important one. So no checks are needed.
However I just noticed due to your comment that since we're not checking the restricted in the original PufferProtocol anymore due to the last refactor (using just the fallback instead of the old functions), we need to add the check somehow. Probably will add it in the fallback function directly
| /** | ||
| * @notice Check IPufferProtocol.withdrawValidationTime | ||
| * @dev This function should only be called by the PufferProtocol contract through a delegatecall | ||
| */ |
There was a problem hiding this comment.
add amount>0 and recipient!=o validation check to avoid informational issue from auditors :D
| InvalidETHAmount() | ||
| ); | ||
|
|
||
| epochsValidatedSignature.functionSelector = IPufferProtocolLogic.depositValidationTime.selector; |
There was a problem hiding this comment.
why are we modifying the memory param here?
There was a problem hiding this comment.
We have that struct (EpochsValidatedSignature) that contains all info regarding the epochsvalidated. One of that items is the function selector, which we need to get the proper nonce (see ProtocolSignatureNonces contract), that are now separated by function selector, in order to avoid DOS by blocking a nonce used by another function. This is, we previously used just nonce by address, but that way a user could send a tx that would block a nonce so it couldn't be used for another flow.
Well this function receives that struct, but the function selector doesn't need to be set by the user (they could tamper it), so it's set in the function body. It's also set in other places where the struct is created (like registerValidatorKey or batchHandleWithdrawals)
| if (block.timestamp > epochsValidatedSignature.deadline) { | ||
| revert DeadlineExceeded(); | ||
| } |
There was a problem hiding this comment.
few places we are using require and other places revert - inconsistent
There was a problem hiding this comment.
True, but that was also the case before this PR. IIRC we're not enforcing consistency with require/revert. I think this came up in a different PR
There was a problem hiding this comment.
Yeah, we got no consensus on this. I think that this uses slightly less gas. Just use whatever you feel is more readable to human.
On another note, this is 3 LOC when auditing the contract, while require would likely be 1 :D
I don't like inline `if(something) revert error() without the {}. I need to re-wire my brain to read it properly.
There was a problem hiding this comment.
When I was optimizing I looked it up and it's equivalent. I usually prefer the require approach. And the LOC thing is valid, I'll see where it makes sense to change it
| payable | ||
| override | ||
| { | ||
| if (srcIndices.length == 0) { |
There was a problem hiding this comment.
Maybe we could replace some of these if revert with require for less LOC and readability, wyt?
There was a problem hiding this comment.
Agreed. I just wrote that in the other comment :D
| if (block.timestamp > deadline) { | ||
| revert DeadlineExceeded(); | ||
| } |
There was a problem hiding this comment.
This can probably be a modifier, it shouldn't add too much bytecode, it will be a little more readable. I've seen it 3 times in this contract
Co-authored-by: Benjamin <[email protected]>
Co-authored-by: Benjamin <[email protected]>
Co-authored-by: Benjamin <[email protected]>
Co-authored-by: Benjamin <[email protected]>
Co-authored-by: Benjamin <[email protected]>
Co-authored-by: Benjamin <[email protected]>
Before this PR, due to the changes for Pectra and the Validator Tokens => Validation Time rework, the size of the PufferProtocol contract had reached 28.5 KB, way over the limit.
In this PR I've implemented a contract that offloads a big part of the logic of the PufferProtocol contract. The way to call this contract is via delegatecall so the storage is the PufferProtocol's. A new base contract has been created so both PufferProtocol and PufferProtocolLogic share constants, immutables, storage, errors, and other common stuff.
The contract size achieved is 18.2 KB, so there's more than enough space to continue with the Pectra implementation and add some more features.
The PR includes some changes to the tests but they're not related to the PufferProtocolLogic contract. They were adapted so the previous tests could work with the changes made regarding the signatures. The changes to the protocol and protocol logic contract have not altered the tests and they still pass