-
Notifications
You must be signed in to change notification settings - Fork 75
feat: hubpool third iteration. Add dispute logic and refine bitmap structure. #14
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
Co-authored-by: Matt Rice <matthewcrice32@gmail.com>
Signed-off-by: chrismaree <christopher.maree@gmail.com>
Signed-off-by: chrismaree <christopher.maree@gmail.com>
Signed-off-by: chrismaree <christopher.maree@gmail.com>
Signed-off-by: chrismaree <christopher.maree@gmail.com>
Signed-off-by: chrismaree <christopher.maree@gmail.com>
| @@ -1,13 +0,0 @@ | |||
| // SPDX-License-Identifier: GPL-3.0-only | |||
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 removed this for now...we dont have a definition of what this requires just yet and I thought it cleaner to directly add the finder to the hub.
| uint64 unclaimedPoolRebalanceLeafCount; | ||
| bytes32 poolRebalanceRoot; | ||
| bytes32 destinationDistributionRoot; | ||
| mapping(uint256 => uint256) claimedBitMap; |
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 is an important change: we use a 1D bitMap after this PR, thereby reducing storage requirements and enabling us to re-use state between each proposal. tradeoff is we can only have up to 256 different chains supported, but this is likely fine :)
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 this seems reasonable until v3 :D
| uint64 indexed relayerRefundId, | ||
| uint64 requestExpirationTimestamp, | ||
| uint64 poolRebalanceLeafCount, | ||
| uint64 unclaimedPoolRebalanceLeafCount, |
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.
variable name changed for @nicholaspai
| require(getCurrentTime() >= refundRequest.requestExpirationTimestamp, "Not passed liveness"); | ||
|
|
||
| // Verify the leafId in the poolRebalance has not yet been claimed. | ||
| require(!MerkleLib.isClaimed(relayerRefund.claimedBitMap, poolRebalance.leafId), "Already claimed"); |
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 use a separate MerkleLib method that I added to deal with 1D bit maps.
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 will add unit tests for this once @mrice32 's MerkleLib PR has been merged.
| function getNumberOfRelayRefunds() public view returns (uint256) { | ||
| if (relayerRefundRequests.length == 0) return 0; | ||
| return relayerRefundRequests.length - 1; | ||
| function _getRefundProposalAncillaryData() public view returns (bytes memory ancillaryData) { |
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 was my initial pass at constructing the ancillary data. curious what you guys think & if we need more/less in this.
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.
these seem reasonable. I'm thinking about ways we can reduce the amount of calls to appendKeyValue... because these functions are expensive I believe
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.
Do we call this in the happy path? Or only on disputes? If only on disputes, we might not need to worry about gas quite as much (although it's still worth thinking about!!).
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.
it only happens in the dispute case so the gas does not matter too much I don't think.
| * @param index the index to check in the bitmap. | ||
| \* @return bool indicating if the index within the claimedBitMap has been marked as claimed. | ||
| */ | ||
| function isClaimed1D(uint256 claimedBitMap, uint256 index) public pure returns (bool) { |
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.
these are the new merkleLib methods I added. v simple and adapted from the original ones to take in a uint256 claimedBitMap rather than mapping(uint256 => uint256) storage claimedBitMap
| @@ -0,0 +1,33 @@ | |||
| import { getContractFactory, utf8ToHex } from "./utils"; | |||
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.
~30 lines to deploy the whole UMA ecosystem <3. I love clean repos.
| refundRequest.unclaimedPoolRebalanceLeafCount = poolRebalanceLeafCount; | ||
| refundRequest.poolRebalanceRoot = poolRebalanceRoot; | ||
| refundRequest.destinationDistributionRoot = destinationDistributionRoot; | ||
| refundRequest.proposer = msg.sender; |
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.
All things being equal I prefer using key-value syntax to set refundRequest here because it means that the developer is less likely to forget to set a parameter. Are there gas savings to setting each param individually and leaving default values as-is for keys like proposerBondRepaid?
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.
so that would replace the delete call plus this assigmeneet with this:
// Remove the existing information relating to the previous relayer refund request and set to the new info.
refundRequest = RefundRequest({
requestExpirationTimestamp: requestExpirationTimestamp,
unclaimedPoolRebalanceLeafCount: poolRebalanceLeafCount,
poolRebalanceRoot: poolRebalanceRoot,
destinationDistributionRoot: destinationDistributionRoot,
claimedBitMap: 0,
proposer: msg.sender,
proposerBondRepaid: false
});I tried out the gas costs:
using the old assignment + delete that I had cost: 155863
using the syntax above cost: 156041
I think the reason the original syntax is cheaper is you are deleting storage and then not re-asigining some things, such as the claimedBitMap and the proposerBondRepaid. for these variables the default (post delete values) are the correct in this context.
As a result, I'm going leave it with the original implementation, unless you have anything else I can try that is syntactically better but still gas efficient.
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 also think there are some annoying solidity inefficiencies with key value syntax. I think they create the entire struct in mem and then assign it rather than just assigning each value directly to storage. This increased mem usage might be why the gas costs are higher.
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.
sounds good to me
| delete refundRequest; // Remove the existing information relating to the previous relayer refund request. | ||
|
|
||
| refundRequest.requestExpirationTimestamp = requestExpirationTimestamp; | ||
| refundRequest.unclaimedPoolRebalanceLeafCount = poolRebalanceLeafCount; |
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.
Do we need to check that unclaimedPoolRebalanceLeafCount > 0? I only ask because the condition unclaimedPoolRebalanceLeafCount == 0 is an important one in this contract but I also don't think its exploitable to set it to 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.
ye, this is a good point and is actually something we should check. if the relayer provided a set of proofs with poolRebalanceLeafCount set to 0 then there is actually no L1->L2 actions that this bundle can do. they could be providing valid roots that do nothing. I agree that we should only process a bundle if it has at least one valid L2 action. I'll add this require.
nicholaspai
left a 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.
I haven't checked all the tests out carefully yet but first pass the contract logic makes sense!
mrice32
left a 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.
This looks awesome!! Only minor comments and responses to threads.
contracts/HubPool.sol
Outdated
|
|
||
| event RelayerRefundDisputed(uint256 relayerRefundId, address disputer); | ||
| event RelayerRefundDisputed( | ||
| address disputer, |
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.
indexed?
| SkinnyOptimisticOracleInterface.Request ooPriceRequest, | ||
| RefundRequest refundRequest |
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.
One minor comment on these: we might want to select out parts of these structs we want to emit to 1) reduce the data and 2) make etherscan better able to encode/decode the events rather than just showing bytes.
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 is a good point and i'll change the same for SpokePool
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'm going to leave this for now and will try and see how much of a gas difference this makes.
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 don't think it should change anything
| } | ||
|
|
||
| function setBondTokenFinalFeeMultiplier(uint64 newBondAmount) public onlyOwner { | ||
| function setBond(IERC20 newBondToken, uint256 newBondAmount) public onlyOwner onlyIfNoActiveRequest { |
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.
+1 for this modifier
| "Last bundle has unclaimed leafs" | ||
| ); | ||
| ) public onlyIfNoActiveRequest { | ||
| require(poolRebalanceLeafCount > 0, "Bundle must have at least 1 leaf"); |
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.
+1 good check. This will make us more spam resistant.
| refundRequest.unclaimedPoolRebalanceLeafCount = poolRebalanceLeafCount; | ||
| refundRequest.poolRebalanceRoot = poolRebalanceRoot; | ||
| refundRequest.destinationDistributionRoot = destinationDistributionRoot; | ||
| refundRequest.proposer = msg.sender; |
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 also think there are some annoying solidity inefficiencies with key value syntax. I think they create the entire struct in mem and then assign it rather than just assigning each value directly to storage. This increased mem usage might be why the gas costs are higher.
| function getNumberOfRelayRefunds() public view returns (uint256) { | ||
| if (relayerRefundRequests.length == 0) return 0; | ||
| return relayerRefundRequests.length - 1; | ||
| function _getRefundProposalAncillaryData() public view returns (bytes memory ancillaryData) { |
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.
Do we call this in the happy path? Or only on disputes? If only on disputes, we might not need to worry about gas quite as much (although it's still worth thinking about!!).
| refundRequest.requestExpirationTimestamp | ||
| ); | ||
|
|
||
| ancillaryData = AncillaryData.appendKeyValueUint( |
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 do end up caring about hyper-optimizing gas usage here, we probably need to take a second look at the Uint method, since I don't think we optimized that last time around.
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.
it only is called in the dispute case so I don't think it matters too much
nicholaspai
left a 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.
Thanks for responding to my comments, lgtm
contracts/MerkleLib.sol
Outdated
| \* @return bool indicating if the index within the claimedBitMap has been marked as claimed. | ||
| */ | ||
| function isClaimed1D(uint256 claimedBitMap, uint256 index) public pure returns (bool) { | ||
| return claimedBitMap & (1 << index) > 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.
nit: can you explain the differences in logic from the uniswap one?
I think their version of this code is:
uint256 mask = (1 << index);
return claimedBitMap & mask == mask;
Not trying to be nit-picky, this logic is just very dense, and I want to make sure we're super confident in the correctness.
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.
going to add unit tests now on this and will verify the behaviour. can test out this other code example as well (note I created my implementation from modifying the uniswap code to be 1 dimensional :) )
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.
both work equivalently. I reverted to your recommendation as it's easier to read.
contracts/HubPool.sol
Outdated
| bytes32 poolRebalanceRoot; | ||
| bytes32 destinationDistributionRoot; | ||
| mapping(uint256 => uint256) claimedBitMap; | ||
| uint256 claimedBitMap; |
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 think this is a good change, but we've started making some assumptions like "there will never be more than X tokens or Y chains". I want to write these down somewhere. Should we go back to the interface doc and write these down? Or start a known issues and constraints doc to outline this sort of stuff? Thoughts?
I will go ahead and start such a doc, but let me know if you disagree.
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.
ok great. ye, you right, we need to write this down! the interface doc is now somewhat out of date though in some regards. our options are to update the interface doc or start a new one.
in either case, I'm going to add some more comments on this in the code.
contracts/HubPool.sol
Outdated
| event BondAmountSet(uint64 newBondMultiplier); | ||
|
|
||
| event BondTokenSet(address newBondMultiplier); | ||
| event BondSet(address newBondToken, uint256 newBondAmount); |
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.
Should we index newBondToken?
mrice32
left a 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.
LGTM! Minor comments on events!
| bytes32 destinationDistributionRoot, | ||
| address indexed proposer | ||
| ); | ||
| event RelayerRefundExecuted(uint256 relayerRefundId, MerkleLib.PoolRebalance poolRebalance, address caller); |
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 we enumerate the fields in poolRebalance? Also, do you think there are fields we can remove? Do we think these emissions are important for bot verification or anything?
| ); | ||
|
|
||
| relayerRefundRequests.pop(); | ||
| emit RelayerRefundDisputed(msg.sender, ooPriceRequest, refundRequest); |
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 we enumerate these structs for better parseability? Also might be nice to do so so we can drop fields that are unnecessary.
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.
will do this on a follow up.
Co-authored-by: Matt Rice <matthewcrice32@gmail.com>
No description provided.