Staking locks spec, v2

What and why

ERC900 defines a standard interface for staking and unstaking tokens in a smart contract, as well as getting information about the state of a holder’s stake and the total amount of tokens staked to the contract.

However, ERC900 doesn’t define what can happen once someone has staked their tokens. We started thinking about stake locking to standardize how other interesting usecases could be built on top of a user depositing their tokens to the contract. Stake locking allows a staker to put a limit on their ability to transfer tokens. This unlocks these two important properties:

  • Anti-sybil protection: the fact that a token is fungibile means that a token unit is indistiguisible from any other token units. Token units don’t have a serial number, so it becomes impossible to check if a particular token has been used more than once for a particular task. This is particulary important for token-weighted voting, one token could potentially be used to vote more than once (if the owner of the token transfers it to another account) if no extra protections are in place. By locking one’s tokens for some amount of time, those tokens cannot have been transferred, therefore it ensures integrity to applications that need to make sure that one token cannot be used more than once.

  • Slashing potential: a lot of interesting applications for staking tokens are built on the fact that a part or the totality of one’s tokens could potentially be lost if they were to act in a bad way.

Solving anti-sybil

On the previous iteration of the Staking app, locks were used to prevent sybil attacks (by preventing tokens from moving). Having to use locks for this started being a challenge when apps would need to set many locks to prevent tokens from being transferred during different timespans, we implemented something called overlocking and we started realizing that the previous approach was falling short when used for more complex applications, such as implementing a TCR (which needed multiple Staking apps to circumvent some limitations).

A solution that doesn’t require locking is using ERC900 checkpointing, which allows to get the staked balance of a staker at the end of particular block number. Because staked tokens cannot be owned by two different accounts at a given block height, applications that need to ensure that the same tokens aren’t used twice, can check staked balances at a block number in the past. An example of applications for this would be stake voting (preventing double voting) or distributing inflation rewards to stakers.

An alternative approach would be to not add checkpointing to the Staking contract, but offload it to a non-transferable IOU token with checkpointing functionality (MiniMe). When someone stakes, tokens can be minted for the holder, which represent that they have a given stake in the Staking contract. If the staker is slashed or unstakes, an equal amount of the IOU token gets burned from the staker’s balance. This IOU token could directly be used with any MiniMe compatible apps.

While the above works for some applications that just need to check the staked balance, it is not enough for usecases where the staked balance needs to be ‘cached’ by the contract for some reason (for example our liquid democracy implementation). A potential solution would be to add stake change hooks that a staker can set up in their account, so whenever their staked balance changes, functions in other smart contracts would be called. This wouldn’t be a problem for staker initiated changes (staking or unstaking), but for external changes (as a result of slashing for example) it could be problematic as whoever triggers slashing would be paying for the computation executed in the hook.

Slashing

If anti-sybil can be solved with the mechanisms described on top, then locks are only needed when there is potential for slashing and for that reason tokens must not be transferred while they could be slashed.

Lock spec

A work in progress spec for how stake locking could look like:

The core idea is that every lock has a manager (looking for a better word here), after the lock is created, the manager has control over the lock, including modifying the lock amount (by decreasing it), transferring locked funds to another staker or lock, changing the data associated with the lock and even changing the manager. Lock managers have complete control over the funds in a lock, but the ownership of the funds in the staking contract is maintained by the staker.

Managers will be smart contracts in most cases, which have well-defined rules for when a lock can be removed, funds transferred or the manager changed to another contract that will take over managing the lock.

By setting managers at the lock level, rather than global authorized entities with the MOVE_TOKENS role as in the previous implementation, a Staking app instance is much more trustless than before, and users can choose what manager has control over any lock, allowing to have many ways to resolve agreements and different types of agreements running at the same time over a unique staking pool.

Time-locks as supported in the previous iteration can also be built using managed locks. By taking advantage of the data payload that locks can have associated with them, a very simple TimeLock manager can be built:

Aragon Network v1: staking, governance and agreements

With this new spec, the Aragon Network v1 core component would be a Staking app, where stakers can create locks for different types of agreements, which can be resolved with different mechanisms. Staked funds, locked or unlocked, can be used to participate in governance decisions of the Aragon Network.

Resolution agnostic agreements

Agreements could be created using staked funds by locking funds and setting a manager that will unlock funds based on how the agreement is resolved. This has the advantage that we don’t need to commit to a version of the Aragon Court early on, nor wait until the fully fledged Aragon Court is ready (see simulations with the latest research) for users and DAOs to start creating agreements collateralized by ANT.

It also means that other types of agreements, that don’t necessarily need to use a decentralized court for resolution, can be created, while the locked tokens can still be used for participating in governance decisions.

An example of this would be a simple escrow contract between two parties, which a designated arbiter can resolve:

For the specific Aragon Court implementation as defined in the whitepaper, a lock would be created per agreement with the minimum liability of the agreement. Maximum liability which can be shared between agreements, would be another unique lock managed by the Aragon Court. When creating a new agreement, it is possible that funds from the maximum liability lock need to be locked in the minimum liability of the new agreement. Under the new spec, an undercollateralized agreement could be created, and then the court could transfer funds from the maximum liability lock to collateralize the new agreement, by transferring tokens between locks.

Stake-based liquid democracy

With the current naïve implementation of liquid democracy (https://github.com/aragonlabs/liquid-democracy/pull/1) the stake change hooks would be problematic, as whenever a delegator’s stake changes the increment or decrement on balance would need to be propagated up the delegation chain (as in the current implementation each step in the delegation chain has the sum of all delegated tokens for cheap tallying). In the worst case (delegation chain depth of 20), the cost for this update would be around 500k gas (~20k + 5k gas per step) which would need to be paid by the slasher. If the staker has different delegation chains for different topics, the gas cost would increase linearly with the number of topics.

The combination of LD with this new Staking implementation, creates even more potential griefing attacks that need to be evaluated, as creating artificially long delegation chains can impact slashing. I’m not sure on-chain tallying of LD as we are doing at the moment will be feasible, but needs further thinking and research.

Work in progress

Our thinking on staking and locking, and its implementation on the Aragon Network is quickly evolving. Some areas that need more work:

  • Figuring out whether stake change hooks make sense at all, or how to implement LD using the new Staking app.
  • Making sure the experience for DAOs Staking and creating locks and agreements is great, taking it into account when working on the Actor app
3 Likes

I’ve been thinking about this for a while (are more to come), specially on the context of TCR and PLCR, but so far looks beautiful to me! I think it can simplify things a lot, although I’ll try to get more into the details and even try to apply to that use case during the next few days. I have some doubts/comments though,mostly related to that application, and in particular to time based locks:

  • First of all I wonder if time is going to be such an essential characteristic of most lock use case that it should be always present (in struct Lock I mean, like it used to be in our last Staking app with deferred lock), instead of relying on bytes data for it. In case of not needing time constrains there are always workarounds like setting endDate to MAX_UINT.
  • I see there are some time fields in that struct, like createdAt. What would it be for? I understand that the effective start date for the lock in such a time based one would be inside data, right?
  • Not so sure about Manager being able to change lock data. Again in this case of time based lock, if a user commits to lock some tokens for a period T, it would be weird that the manager has the power to extend that period without user consent.

So you mean implementing time locks as a native feature inside the staking app? Don’t you think most applications that use them (and are the lock manager) will also have a notion of the lock timing and can manage that? I think when we were checking locks before, we were checking that the time in the lock matched what the application expected, we can make the app directly manage the lock time.

The idea was that this way managers can have a way to check on-chain when a lock was created (as contracts can’t check event logs). In case of a timelock, it could be inside the lock’s data, but then the manager would need to verify after the lock was created that the initial date is correct. Otherwise one can create a lock with a ‘past start date’.

I think it is important specially for managers that will ‘escalate’ to another manager, and that may require different data.

Then they shouldn’t use a time lock manager that has the ability to change the time of the lock :wink:

Ha, good point. But still, I feel it’s opening a door. Because then they shouldn’t use a lock manager that is upgradeable, right? As it could not be able to change data in the initial version, but there may be an upgrade that allows for it once they have their tokens locked.

https://github.com/Recoblix/aragon-delay-app This is an implementation I made a while ago for delaying/time locking actions. It only lets you manage the time lock duration by creating a new proxy with the new time lock and giving that the necessary permissions. It is made to be compatible with any actor app.

1 Like

Interesting @jvluso, I’ll have a look!
Replying to myself, actually a Lock Manager could upgrade and just slash all the tokens, so I guess data would be a minor problem :wink:

1 Like

And what’s the problem with creating locks with past start date? As long as you have the tokens locked when the manager requires and checks it, and until (end date) it requires, it should be ok. Actually you convinced me of it:

:wink:

1 Like

Fair point. You just convinced me back!

I’m not sure I’m totally following the implications of this.

From the user perspective there would be individual locks for each agreement based on Minimum_Liability, and then a single lock controlling the Maximum_Liability portion of all agreements.

The maximum liability lock must always be large enough to cover the largest maximum liability agreement. So the “Max_Liability” lock manager must keep track of what that value is.

When agreements are updated (either by mutual consent, or via a dispute in the Aragon Court) both the individual minimum liability lock and the maximum liability lock need to be updated. It seems important for the maximum liability to keep mapping of all the users agreements and their max liability values? Because if the agreement which has the highest maximum liability gets updated/released the lock manager would need to know what the new “max liability value” is.

Its not clear to me when it would ever make sense to move funds from the max liability lock to create a new minimum liability lock – Instead it seems like if the highest maximum liability agreement gets updated it should automatically unlock funds based on the difference between the two highest maximum liability agreements.

For example:
I have 2 agreements, one with max liability = 100, one with max liability 50. The maximum liability lock manager creates a lock of 100, then the agreement with max liability is resolved. I want to unlock some funds, so the maximum liability lock manager must be aware that I can not unlock more than 50.