Project: A design for smart contract withdrawals

Background

Lido was originally deployed such that it specifies a BLS eth2 pubkey as the withdrawal credential for all deposits to the deposit contract. Because these keys can’t specify smart contracts that are written today, this required the current threshold signature based withdrawal mechanism. While the group involved in this ceremony is relatively diverse and respected, this is still a scenario where $10B+ of ETH withdrawals hinge on a k-of-11 entity threshold committee.

A couple months after Lido’s release, a change was made to the eth2 spec supporting a way to specify eth1 addresses at deposit time. This allows specifying a smart contract as the withdrawal adddress, allowing trustless withdrawal logic.

It was always in the plans to move to this new scheme, but below I make the argument that it’s worth thinking about now and present a sketch for how this could happen.

The result of this would be that all subsequent deposits to Lido would have a trustless, smart contract withdrawal scheme rather than relying on the 11 member threshold signature group.

Why now?

So, why not wait until the last minute? Why bother doing anything before merge?

The biggest reason is that the longer we wait, the more ETH that relies on the threshold sig for withdrawal vs. a smart contract. Lido has had an impressive growth curve so far, and if it’s true that staking pools may be a winner-takes-most category, a nontrivial fraction of ETH supply may end up reliant on an 11 person threshold signature committee. No matter who is in that committee, this situation is non-ideal for the overall safety of Ethereum and by extension, Lido.

Secondarily, moving to smart contract withdrawals will likely improve the decentralization optics of Lido itself, which may (funnily enough) improve user adoption. The ETH community is still composed of many people who care about the permanence/decentralization of the systems we’re building and a push to smart contract withdrawal could win a lot of potential depositors over.

Design

To recap, there are two steps involved:

  1. writing/deploying a new withdrawal contract
  2. through governance, setting the withdrawal credentials to a value corresponding to this new contract

Here’s a sketch of what the withdrawal contract may look like:

contract LidoWithdrawals {
    using SafeMath for uint256;

    Lido private lido;

    constructor(Lido _lido) {
        // Store Lido DAO contract
        lido = _lido;
    }

    /**
     * @dev user withdrawal fn
     * @param _value amount of stETH to withdraw
     */
    function withdraw(uint256 _value) {
        // must be authorized xfer
        require(lido.allowance(msg.sender, address(this)) >= _value)

        (uint256 _, uint256 _, uint256 beaconBalance) = lido.getBeaconStat();

        // someone should check that this is the right calculation :)
        uint256 ethToWithdraw = _value.div(lido.totalSupply()
                                      .mul(beaconBalance);

        // must have enough ETH (i.e. enough BLS sig -> eth1 addr withdrawals have taken place)
        // If contract doesn't, user may need to rely on threshold sig mechanism
        require(address(this).balance >= ethToWithdraw);

        // handle withdrawal
        lido.transferFrom(msg.sender, address(this), _value);
        msg.sender.transfer(ethToWithdraw);
    }
}

This design is optimized for minimal governance actions/changes to the existing contracts. It doesn’t keep track of how much ETH is staked to be withdrawn by the old vs. new withdrawal credentials and instead just fails if there isn’t sufficient ETH here.

There is a small UX downside to this: stETH holders who are withdrawing their ETH ‘last’ may have to withdraw tranches separately between the threshold sig withdrawal and the smart contract withdrawal because there won’t be enough in either left to cover their entire withdraw. I think this price is worth paying for the contract simplicity.

Once this contract is written/deployed, the withdrawal credentials should be set as specifiedhere with the eth1 address set to the withdrawal contract’s address.

Let’s discuss!

This proposal is largely meant to be a starting point for discussion. One thing I hope to convey is how simple this upgrade could be. That being said, I likely am missing edge cases and other considerations; would love to hear what the community thinks and work towards making this happen.

11 Likes

It’s def time to start the discussion and we are working on our own proposal to that effect: HackMD - Collaborative Markdown Knowledge Base

The solution you proposed can be problematic for a number of reasons from plain to subtle, with the root cause the withdrawals being a lengthy and asynchronous process. The most obvious problem is that for ETH to appear on a withdrawal contract, it needs to be signaled at least 27.5h in advance, potentially up to months (so if someone wants to withdraw 100eth, they need to have 100 stETH and signal they want to withdraw).

That signal can’t be “free” - otherwise, stETH is suspectible to griefeing attacks. The trivial (and IMO best) way to attach a cost to that signaling is locking stETH (so to withdraw 100 ETH you need to lock 100 stETH). But if someone paid the opportunity cost to lock 100 stETH to signal the withdrawal, it’s them who should get the opportunity to swap stETH to ETH and not some random freeloading stETH holder.

The other thing to consider is that ETH2 witdrawals are in queue and the large ones can be quite long. We would probably want to maintain a queue for Lido withdrawals as well.

After that, a number of edge cases dealing with potential slashings need to be addressed (see Handling withdrawals in Lido's ETH liquid staking protocol - Eth1-to-Eth2 Transition - Ethereum Research).

And one final thing, I don’t think it’s secure to have an immutable withdrawal contract while eth2 spec is quite mutable. I think we will have an alternative proposal out soon, because it’s v. important topic; but not very soon because it’s a pretty complicated topic.

4 Likes

Appreciate the thoughtful response! Left a few comments in the hackmd.

Why is it so crucial that withdrawals need to be signal-able by stETH users? There’s not very much consensus on how withdrawal-credential triggered withdrawals might work yet and I’m not sure it makes sense to design with that feature in mind today. Of course, I understand the UX challenge here and the fact that vals could theoretically hold staked ETH ransom, but I just don’t know that we can patch the hole with features that don’t exist yet.

100% agree that putting an upgrade proxy in front of the withdrawal contract is right. Your point about the mutability of the eth2 spec is apt.

So given that, and the fact that every day that passes where new users deposit into Lido, more ETH is dependent on the threshold sig committee to be withdrawn, I see a spectrum of responses where 1 is the most certainty/least work and 4 is the least certainty/most work:

  1. do nothing now
  2. change withdrawal creds to an upgrade proxy in eth1
  3. change withdrawal creds to an upgrade proxy in eth1 that by default points to simple, non-optimal solution like the one I presented
  4. change withdrawal creds to the full, queued, approach

Because of the exit queue and other edge cases/race conditions due to information traveling b/w eth1/eth2 and slashing, 4 is definitely tricky to get right (as you point out) and will just inevitably take time.

2 seems strictly better than 1 because at least the DAO is responsible for the withdrawal mechanism instead of the threshold sig committee.

Which leads me to believe that 2 or 3 is the right response today. Maybe 2 is the least contentious, but 3 gives users some extra certainty around ‘default’ behavior. My beliefs between the 2 are pretty weakly held.

Wdyt? Worth prioritizing 2 (incl. auditing, etc.) now?

1 Like

On signals: to initiate withdrawals, validators or (in future) withdrawal credential holders need to send message that they want to exit. What’s the condition for them to do that? How do they know they should exit N validators to the total of M ether? I propose that the condition is “user that has M stETH signals that they want to exit”. There can be alternative automated designs (e.g. withdraw when stETH:ETH pair has lost peg) but I don’t think we should go there.

I don’t see much value in 3 - it’ll be most likely upgraded anyway, and having an explicit stub in place of withdrawal contract makes it very clear that governance risk of Lido can be very impactful, and I’d prefer clarity on that.

We are working toward 2: highest priority in the backlog now. Will get to work on that when we deal with current almost-finished tasks.

4 Likes

Excellent, agreed on 2!

:+1: on putting it behind a “dumb” proxy w/ no logic owned by the DAO, with a strong commitment that we’d burn the upgrade keys assuming we’ve navigated to the right design by the time withdrawals are live on mainnet.

We must make it extra clear that even though this upgradable proxy would control the DAO AUM (and be vulnerable to Maker-type governance attacks) it’d be harmless before the activation of withdrawals.

Also :+1: on having users lock their stETH in a contract to signal that a validator should withdraw.

2 Likes

Are you sure trustless withdrawals (and that too only for partial stake) represents a meaningful improvement to status quo for Lido? Validators on Lido are primarily trusted based on off-chain reputation rather than cryptoeconomic guarantees. This continues to hold if trustless withdrawals are enabled, since the withdrawals still need to be triggered by the validators themselves.

Malicious validators can demand a ransom (in ETH) failing which they get the entire ETH slashed. This is a war of attrition, which has been analysed. Assuming identical utility curves for both the stakers and the validators, you’ll end up with 50% of the ETH being taken as ransom. In practice, due to unequal utility curves, reputation, etc, the game could be biased in favour of either side. Malicious validators being wealthy favours them (utility curve) whereas having reputations to lose disfavours them.

Demanding a ransom can be trustless using a smart contract on eth1 (if 32 ETH withdrawn here, release 20 ETH ransom to this person).

Hence with trustless withdrawals, the amount a malicious validator can steal drops from the full 32 ETH to roughly 16 ETH (± delta).

This is an improvement, no doubt, but is it worth the effort? Wouldn’t it be better to instead design from scratch in a manner where validators also have cryptoeconomic incentives besides offchain reputation. Rocketpool demands validators put up 16 ETH of their own to ensure skin in the game, I’m very keen on seeing Lido adopt a similar approach. Especially since Lido has grown this big and will likely play a major role in ethereum 2.0 going forward

1 Like

Any credible purely cryptoeconomic mechanism to ensure validator’s honesty I’ve seen or had imagined is so capital inefficient it’s not even funny, in my opinion, and can’t compete with custodial staking. That’s the reason that, e.g., rocketpool had introduced trusted nodes that can forego the self-staking requirements.

If there’s a cryptoeconomic mechanism out there that doesn’t have this flaw, I’ll be all for it.

So you’re saying that on average, there will be a lot more ETH that non-stakers own and want someone else to stake, as compared to the ETH that stakers own and are staking?

That is possible but what is the ratio of the two? If it is 3:1 for instance, we could have stakers stake 1 ETH of their own for every 3 ETH they accept from others.

Yes, I think it’s about 50:1 (off the top of my head), and intersection of node operators who own a lot of eth vs. good node operators is basically @JK_stakefish :slight_smile:

2 Likes

If thats the case, wouldn’t it be better if ethereum reduced inflation such that the ratio is reasonable?

50:1 is essentially delegated proof of stake which ethereum has worked very hard to avoid.

Imo I think that’s the decision making ethereum will have to do and protocols like lido may have to adapt.

I don’t think avoiding delegated proof of stake is a realistic goal. The only credible alternative is custodial staking, which isn’t better by any count.

Why is reducing staking rewards - and by extension the total fraction of ETH circulating supply being staked, not a credible solution?

Because half or more of income will tips + MEV, for one. And it reduces network security, for other.

1 Like

MEV income will be an interesting dynamic to see. Even so, isn’t it likely the market will then move ETH price up until the income is again only a few APY?

And minimum necessary issuance isn’t something we’re very good at measuring. Roughly depends on how much ETH is available for shorting and how coordinated the stakers are, if I’ve understood it correctly.

I guess this is a longer discussion that can’t be completed here. My point is I don’t think it’s trivial to assume the ratio of delegators to self-stakers will inevitably be 50:1, or that this reflects the current opinion of either the Lido community or the ethereum community at large. Open problem I guess.

1 Like

It sounds great to have validators put up their own bond. However, in practice, I’ve seen this to play out in the favor of whales at the end of the day. It’s impossible for small/middle validators to scale their validator size regardless of how competent they are due to the bond constraint. Eventually, these validators end up going to foundations or whales to borrow tokens. Then, you end up with off-chain relationships and leverage.

2 Likes

Will be interesting to see either way :slight_smile:

If Lido thinks non-bonded validators will dominate market share, then yeah they should win if so. Guess we may need a separate thread to discuss and gauge Lido community sentiment in general.

1 Like