-
Notifications
You must be signed in to change notification settings - Fork 16
Refactor PufferProtocol to keep <24KB #119
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
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did we doument anywhere that scenario, where Source consolidates into a bad target and NoOp loses money?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use IPufferProtocolLogic.depositValidationTime.selector directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
*/ | ||
uint256 internal constant _32_ETH_GWEI = 32 * 10 ** 9; | ||
|
||
bytes32 internal constant _FUNCTION_SELECTOR_REGISTER_VALIDATOR_KEY = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can get rid of all these internal constants and just inline IPufferProtocolLogic.whatever.selector
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to create the constants because of the stack too deep. Maybe it isn't necessary anymore. I'll try
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
receive() external payable { } | ||
|
||
fallback() external payable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add a simple explainer.
If a function selector is not found in this contract ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
revert DeadlineExceeded(); | ||
} | ||
|
||
_GUARDIAN_MODULE.validateBatchWithdrawals(validatorInfos, guardianEOASignatures, deadline); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
return; | ||
} | ||
|
||
_GUARDIAN_MODULE.validateTotalEpochsValidated({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can do it here as well validateGuardiansEOASignatures
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
import { EpochsValidatedSignature } from "./struct/Signatures.sol"; | ||
import { InvalidAddress } from "./Errors.sol"; | ||
|
||
contract PufferProtocolLogic is PufferProtocolBase, IPufferProtocolLogic { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
natspec for this contract plz
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (yesterday)
* @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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add amount>0 and recipient!=o validation check to avoid informational issue from auditors :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (yesterday)
InvalidETHAmount() | ||
); | ||
|
||
epochsValidatedSignature.functionSelector = IPufferProtocolLogic.depositValidationTime.selector; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why are we modifying the memory param here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
few places we are using require
and other places revert
- inconsistent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
payable | ||
override | ||
{ | ||
if (srcIndices.length == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could replace some of these if revert with require for less LOC and readability, wyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I just wrote that in the other comment :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
if (block.timestamp > deadline) { | ||
revert DeadlineExceeded(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
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