Repository navigation
Add Operator.rebalanceStake - #1094
Conversation
8484a7e to
299587f
Compare
299587f to
3c82c89
Compare
| } | ||
|
|
||
| // fail fast, before spending gas on the reductions: staking into a full Sponsorship would only revert at the very end | ||
| _checkCanJoin(stakeOnSponsorships); |
There was a problem hiding this comment.
this check seems to cause quite a lot of added code into Sponsorship (not to mention integrating max operators policy into main contract, meaning maybe the rest of the module (= the max operator count limitation feature) should live in the main contract instead of a separate module...). Considering a reverted transaction would mean no gas is spent anyway, "failing fast" doesn't really mean gas savings (unless the revert is caught somewhere? On a quick glance it looks like line 111 would pass on the revert.
There was a problem hiding this comment.
Considering a reverted transaction would mean no gas is spent anyway, "failing fast" doesn't really mean gas savings (unless the revert is caught somewhere?
@jtakalai I don't understand this part. On a failed transaction, the sender does pay for all gas spent up until the failure. In a race to join a sponsorship, the sponsorship may well become full in between submitting the join transaction and getting it included in a block, so this pre-check would reduce gas wasted in a fairly typical case (if max operators is set on the sponsorship).
Example calculation: if rebalanceStake runs two reduceStakeTos (each ~100k+ gas: Sponsorship allocation-policy updates, token transfers, events), pays out the queue, and then hits error_tooManyOperators in the join policy on the stake, the operator's wallet is charged for all of that work and gets nothing for it. With the pre-check, the same failure costs only the isFull() staticcalls (~3–10k per sponsorship) plus the base fee.
There was a problem hiding this comment.
maybe [...] the max operator count limitation feature) should live in the main contract instead of a separate module
I agree, it probably should. However, folding the functionality into Sponsorship proper would necessitate a breaking change in the SponsorshipFactory's API (maxOperators becomes a proper argument). Better avoid it now if possible.
3c82c89 to
d50e8b1
Compare
d50e8b1 to
a316dbb
Compare
Summary
Adds
Operator.rebalanceStake, which moves stake between Sponsorships in one transaction: it reduces stake in a list of Sponsorships (a target of zero unstakes completely), pays out the undelegation queue from the freed DATA, and then stakes into another list of Sponsorships. It behaves like callingreduceStakeToand thenstakefor each entry, with two differences:ArrayLengthMismatchotherwise).SponsorshipFull. Joining is racy — another Operator can take the last slot while the transaction is pending — so failing here saves the gas that the reductions would have spent. Sponsorships the Operator is already staked in are skipped, since increasing an existing stake does not count against the limit.Sponsorship.isFull()andSponsorship.maxOperatorCount()back that check. The limit lives only inMaxOperatorsJoinPolicy's namespaced storage, somaxOperatorCount()reads that slot directly (zero means no limit). Sponsorships deployed from the current template have noisFull(); the check catches that and falls through to staking. Whether an address is a factory-deployed Sponsorship at all is still checked later, insideStakeModule._stake.The loop lives in
NodeModule._rebalanceStakeand delegatecallsStakeModulefor the actual staking.StakeModuleis at the contract size limit, and every module carriesOperator's bytecode.version()stays at 4: this ships in the same deployment as the Operator drain hotfix.For a move across four Sponsorships (two reductions, two stakes),
rebalanceStakeused 968k gas against 1,524k for the equivalent separate transactions.