-
Notifications
You must be signed in to change notification settings - Fork 329
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
Stake Root Simplifications [WIP] #741
Conversation
_addStrategiesAndMultipliers(IAVSDirectory.OperatorSet({avs: msg.sender, operatorSetId: operatorSetId}), strategiesAndMultipliers); | ||
} | ||
|
||
// natspec |
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.
delete
|
||
// natspec | ||
/// @notice Initializes an operator set with a given set of strategies and multipliers | ||
/// @dev This a convinience (gas saving) function for avs' |
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 multiline comments stylistically
/**
*
...
*/
/// @dev This a convinience (gas saving) function for avs' | ||
/// @param operatorSetId The ID of the operator set to initialize | ||
/// @param strategiesAndMultipliers The strategies and multipliers to initialize the operator set with | ||
function initOperatorSetInStakeTree( |
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.
hm.
Can we just make addStrategiesAndMultipliers
payable and deposit if msg.value > 0
?
@@ -26,170 +25,139 @@ contract StakeRootCompendium is StakeRootCompendiumStorage { | |||
|
|||
/// @inheritdoc IStakeRootCompendium | |||
function depositForOperatorSet(IAVSDirectory.OperatorSet calldata operatorSet) 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.
just deposit
to be consistent with new internal funcs
uint256 multipliedDelegatedShares = delegatedShares[i] * multipliers[i] / 1 ether; | ||
delegatedStake += multipliedDelegatedShares * totalMagnitudes[i]; | ||
slashableStake += multipliedDelegatedShares * allocatedMagnitudes[i]; | ||
delegatedStake += delegatedShares[i] * totalMagnitudes[i] / 1 ether * multipliers[i]; |
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 the previous saves more gas in the zkVM
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.
not sure though, we should benchmark
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.
ahh okay this was inadvertent
} | ||
IAVSDirectory.OperatorSet memory substituteOperatorSet = operatorSets[operatorSets.length - 1]; | ||
// note: operatorSetIndex is 1 indexed so operatorSetOneBasedIndex - 1 is strictly non-negative | ||
operatorSets[operatorSetOneBasedIndex - 1] = substituteOperatorSet; |
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 definitely prefer 0 based indexing haha
return latestSnapshottedCumulativeCharge + uint224(currentCharge * (latestCalculationTimestamp - latestSnapshotTimestamp) / proofInterval); | ||
// update the index of the operator sets | ||
operatorSetToOneBasedIndex[msg.sender][operatorSet.operatorSetId] = 0; | ||
operatorSetToOneBasedIndex[msg.sender][substituteOperatorSet.operatorSetId] = operatorSetOneBasedIndex; |
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 should not be msg.sender, it should be the operatorSet.avs
and substituteOperatorSet.avs
respectively
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, definitely
); | ||
// updates the deposit balance for the operator set and returns the penalty if the operator set has fallen below the minimum deposit balance | ||
function _updateDepositInfo(IAVSDirectory.OperatorSet memory operatorSet) internal returns (uint256) { | ||
uint256 depositBalance = getDepositBalance(operatorSet); |
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 should remove them from the operatorSet if they fall below the min balance 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 can, i reserved that logic where it is explicitly relevant eg processing withdrawals. . .
let me ponder, can put it back in, just thinking if there was a good reason i took it out
uint224 operatorSetIndex = operatorSetToIndex[operatorSet.avs][operatorSet.operatorSetId].latest(); | ||
operatorSets[operatorSetIndex] = substituteOperatorSet; | ||
operatorSets.pop(); | ||
function _updateTotalStrategies(uint256 numStrategiesBefore, IAVSDirectory.OperatorSet memory operatorSet) internal { |
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.
rename back to updateTotalCharge because we need to call this when charges change
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
cumulativeContantChargeSnapshot.upperLookup(timestamp), | ||
cumulativeLinearChargeSnapshot.upperLookup(timestamp) | ||
function _removeOperatorSetFromStakeTree(IAVSDirectory.OperatorSet memory operatorSet) internal { | ||
uint256 operatorSetOneBasedIndex = operatorSetToOneBasedIndex[operatorSet.avs][operatorSet.operatorSetId]; |
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 update the totalCharge in this function in case this is a force removal that isn't a removal of strategies
|
||
// remove the strategies and multipliers for the operator set | ||
_removeStrategiesAndMultipliers(operatorSet, strategies); | ||
function removeOperatorSetFromStakeTree(uint32 operatorSetId) external { |
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.
hm why do we have 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.
not necessary I guess, could remove it if it doesn't matter for AVS'
} | ||
|
||
/// @inheritdoc IStakeRootCompendium | ||
function setOperatorSetExtraData(uint32 operatorSetId, bytes32 extraData) external { | ||
(bool exists,, uint224 index) = operatorSetToIndex[msg.sender][operatorSetId].latestCheckpoint(); |
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.
seems correct to revert here imo
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 i'll put that back, it's a setter so used very rarely
operatorExtraDatas[msg.sender][operatorSetId][operator] = extraData; | ||
} | ||
|
||
/// POSTING ROOTS AND BLACKLISTING | ||
|
||
/// @inheritdoc IStakeRootCompendium | ||
function verifyStakeRoot( | ||
uint32 calculationTimestamp, | ||
uint64 calculationBlockNumber, |
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 should be a timestamp since everything is in timestamps in the slashing release
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.
hmmmm this might be awkward since the zk proof is against a blockhash and blocks are sequential but calculationTimestamps can jump
BN: [ 0 ] [ 1 ] [ 2 ]
TS: 12 36 48
so there is a missing timestamp corresponding to timestamp 24. . . should a proof be skipped in this case?
); | ||
require( | ||
block.timestamp < stakeRootSubmissions[submissionIndex].blacklistableBefore, | ||
"StakeRootCompendium.blacklistStakeRoot: stakeRoot cannot be blacklisted" | ||
latestBlockNumberProven < calculationBlockNumber, |
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.
calculationBlockNumber = latestBlockNumberProven + proofInterval
And this can only be if this is not the first submission
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.
what do you mean?
from the first require statement, we have calculationBlockNumber % proofInterval == 0
and from latestBlockNumberProven < calculationBlockNumber
we have basically that
latestBlockNumberProven = latestBlockNumberProven + n * proofInterval
do you mean proofs should be calculated strictly sequentially?
"StakeRootCompendium._postStakeRoot: calculationTimestamp must be greater than the last posted calculationTimestamp" | ||
); | ||
if (submission.stakeRoot == bytes32(0)) { | ||
submission.stakeRoot = stakeRoot; |
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 this is not a proof post, the submission must be proven. confirmation must happen after proofs
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 was kind of overloading this functionality for the desired feature that if the provers are offline, the same data structures can be used for AVS' which may choose to use a simpler confirmation rule (trust eigen labs)
or what is desired as a fallback if zkprovers are all offline?
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.
but yeah, certainly can adjust this so confirmerss strictly confirm
operatorSetToIndex[operatorSet.avs][operatorSet.operatorSetId].push(uint32(block.timestamp), operatorSetIndex); | ||
function _setChargePerProof(uint64 _chargePerProofLinear, uint64 _chargePerProofConstant) internal { | ||
_updateCumulativeCharges(); | ||
chargePerProofLinear = _chargePerProofLinear; |
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 update the total Charge 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.
yeah, i overlooked some total charging related logic, will put it in there
function _updateDepositInfo(IAVSDirectory.OperatorSet memory operatorSet) internal returns (uint256) { | ||
uint256 depositBalance = getDepositBalance(operatorSet); | ||
depositInfo[operatorSet.avs][operatorSet.operatorSetId].balance = uint96(depositBalance); | ||
depositInfo[operatorSet.avs][operatorSet.operatorSetId].snapshotTimestamp = uint64(block.timestamp); |
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 update the cumulatives here
stakeRootSubmissions[submissionIndex].blacklisted = true; | ||
// TODO: verify proof | ||
_postStakeRoot(true, calculationBlockNumber, stakeRoot); | ||
uint256 charge = totalChargeSnapshot.upperLookup(uint32(calculationBlockNumber)); |
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 just allow them to submit an index here to avoid lookup gas?
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 need to put a bit more thought into the total charge, thats a component I overlooked a bit. . .
can't we avoid snapshots by having a view function for the totalCharge and doing a storage proof for that block? that's basically what the zk proof is doing anyways right? could add that to the proof? and to avoid zk tech stack failure enforce a MAX_TOTAL_CHARGE = 3 eth
alternatively to KISS we can do a little bit more accounting to have an O(1) lookup, I think there's a simple way to do it
@@ -23,8 +23,6 @@ abstract contract StakeRootCompendiumStorage is IStakeRootCompendium, OwnableUpg | |||
uint32 public constant MAX_NUM_OPERATOR_SETS = 2048; | |||
/// @notice the maximum number of strategies that each operator set in the StakeTree can use to weight their operator stakes | |||
uint32 public constant MAX_NUM_STRATEGIES = 20; | |||
/// @notice the placeholder index used for operator sets that are removed from the StakeTree | |||
uint32 public constant REMOVED_INDEX = type(uint32).max; | |||
|
|||
/// @notice the minimum balance that must be maintained for an operatorSet | |||
uint256 public constant MIN_DEPOSIT_BALANCE = 0.001 ether; |
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 make this an immutable?
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.
MIN_DEPOSIT_BALANCE
? yeah
stakeRootSubmissions[submissionIndex].blacklisted = true; | ||
// TODO: verify proof | ||
_postStakeRoot(true, calculationBlockNumber, stakeRoot); | ||
uint256 charge = totalChargeSnapshot.upperLookup(uint32(calculationBlockNumber)); |
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.
set latestBlockNumberProven
function _isInStakeTree(IAVSDirectory.OperatorSet memory operatorSet) internal view returns (bool) { | ||
(bool exists,,uint224 index) = operatorSetToIndex[operatorSet.avs][operatorSet.operatorSetId].latestCheckpoint(); | ||
return exists && index != REMOVED_INDEX; | ||
} |
Check warning
Code scanning / Slither
Unused return
function _updateTotalStrategies(uint256 _countStrategiesBefore, uint256 _countStrategiesAfter) internal { | ||
totalStrategies = totalStrategies - _countStrategiesBefore + _countStrategiesAfter; | ||
_updateChargePerProof(); | ||
} |
Check warning
Code scanning / Slither
Costly operations inside a loop
bytes32 _imageId | ||
) StakeRootCompendiumStorage(_delegationManager, _avsDirectory, _maxTotalCharge, _minBalanceThreshold, _minProofsDuration, _verifier, _imageId) {} | ||
|
||
function initialize(address _owner, address _rootConfirmer, uint32 _proofIntervalSeconds, uint96 _chargePerStrategy, uint96 _chargePerOperatorSet) public initializer { |
Check notice
Code scanning / Slither
Missing zero address validation
if (endIndex > stakeRootSubmissions.length) { | ||
endIndex = stakeRootSubmissions.length; | ||
} | ||
function setProofIntervalSeconds(uint32 proofIntervalSeconds) public onlyOwner { |
Check notice
Code scanning / Slither
Local variable shadowing
// total charge is the charge per strategy per proof times the number of strategies at the time of proof | ||
totalCharge += totalChargeSnapshot.upperLookup(stakeRootSubmissions[i].calculationTimestamp); | ||
} | ||
function setRootConfirmer(address _rootConfirmer) public onlyOwner { |
Check notice
Code scanning / Slither
Missing zero address validation
function _updateChargePerProof() internal { | ||
// note if totalStrategies is 0, the charge per proof will be 0, and provers should not post a proof | ||
uint256 chargePerProof = operatorSets.length * chargePerOperatorSet + totalStrategies * chargePerStrategy; | ||
require(chargePerProof <= MAX_TOTAL_CHARGE, "StakeRootCompendium._updateChargePerProof: charge per proof exceeds max total charge"); | ||
chargePerProofHistory.push( | ||
uint32(block.timestamp), | ||
uint224(chargePerProof) | ||
); | ||
} |
Check warning
Code scanning / Slither
Unused return
bytes32 public immutable imageId; | ||
|
||
/// @notice the interval in seconds at which proofs can be posted | ||
uint32 public proofIntervalSeconds; |
Check failure
Code scanning / Slither
Uninitialized state variables
bytes32 public immutable imageId; | ||
|
||
/// @notice the interval in seconds at which proofs can be posted | ||
uint32 public proofIntervalSeconds; |
Check warning
Code scanning / Slither
State variables that could be declared constant
42b86cd
to
d0a6339
Compare
This PR represents WIP simplification and extension of the stake root contracts. It is not in a finalized state as items such as test coverage, safe type casting, natspec comments, incentive compatibility for removals, modifications needed to charging, and agreement of the simplification approach are needed.
Simplifications
AVS' can withdraw the balances at any time and remove operator sets from the tree.