It has been a while since 1hive forked Aragon’s Token Manager to include the hooks feature, and I think it is worth reviewing if they are still useful or if we can replace them for better solutions.
First, let’s explain what token manager hooks are and what they have been used for.
As you may already know, Aragon / Garden DAOs are multi-contract decentralized applications living on the blockchain using one or many MiniMe tokens to govern themselves.
The Token Manager app is the contract that usually controls the MiniMe token. Most DAOs have one of those, although sometimes they are not needed when the DAO is using an external token.
The other contracts of the DAO usually make use of tokens, for example the Voting and Conviction Voting apps to have a census of the addresses that can vote on a particular proposal. The coordination between the tokens with those applications is key for the security of the DAO.
This coordination is not always easy. For example, in order to implement correctly a voting application that creates votes at any discrete time such as in Voting, we have to take a snapshot of the token at the block in which the vote was created to be able to know later how much tokens each addresses had in a certain point. In order to accomplish that we need to take checkpoints each time a token is transferred, with the computational cost (aka gas fees) that it takes.
What’s worse, checkpointing is not enough when we are voting on proposals continuously. Conviction Voting does not have end dates for proposals, so it has to account for tokens that were not there in the moment of each proposal creation, as well as the changes of support from one proposal to another at any moment.
This is what makes it problematic to transfer tokens when we are supporting a Conviction Voting proposal: in normal conditions Conviction Voting wouldn’t know the tokens with which we are supporting a proposal are not in our account anymore. This is why we need to use Conviction Voting along with a Hooked Token Manager in order to control the case in which supporting tokens are burnt or transferred.
The Hooked Token Manager is the fork of the Token Manager in which we included the hooks feature to deal with these cases. In Garden DAOs, the Conviction Voting app is registered as a Token Manager Hook so it can execute a function every time a token transfer is made. In this particular case, this function checks if the token balance of the sender is going to be lower to the amount of tokens supporting proposals on conviction voting, and acts in consequence withdrawing as much support as needed in order to not exceed the new token balance.
Another use case for Token Manager hooks is token semi-transferability. Autark Labs was the first to implement this in Aragon by forking the Token Manager as well to make tokens that can only be transferred to allowed addresses.
Honey also used to be a semi-transferable token when we were using Dandelion Voting in 1hive. Instead of using the Autark solution, we implemented this feature as a Token Manager hook because they offer a more general and composable approach. The purpose of the hook was to mimic Moloch DAOs in order to make Dandelion DAOs do not allow to redeem one’s tokens (to ragequit) if one has recently voted yes on a proposal.
This was in the pre-Celeste version, but with the advent of new disputable apps, the only hook that is used right now in Gardens is for Conviction Voting. This is how a Garden v1 looks like:
This hook is only needed on transfers of tokens that are supporting a proposal. The fact that the hooks are executed in each token transfer makes them very gas ineffective. It was not a big issue in xDAI, but since we plan to migrate Honey and Gardens to Arbitrum, we know that the old gas problem will raise again.
Thankfully, we are in a good position to deprecate Token Manager hooks completely using some of the contracts Aragon One developed for the Court.
When Aragon One implemented the Court, they faced a similar problem than us when they designed it. They needed a contract that had control over some people’s tokens and act when those tokens where withdrawn (such as we need in Conviction Voting). They also needed the people with staked tokens to be able to vote as if their tokens were not staked but in their wallets (like us).
We solved this dilemma implementing the hooks: we reflect each address token balance during all the time so voting works perfectly, but we need to perform checks in each transfer.
Aragon One went the other way around: they stake the tokens (transfer the tokens to a contract implementing the ERC900) so they have absolute control over them (they can act when they are unstaked), and then solve the voting problem aggregating voting power from both the token and the stake contract.
I think Aragon One’s idea is more complex but it is a better solution for this problem, since it unlocks the following possibilities:
- Conviction Voting would not require a Hooked Token Manager anymore, and we would be able to use the same token for multiple gardens without having to register the Conviction Voting as a hook (ex. use HNY in a 1hive subgarden).
- We could create multi-token Gardens as well aggregating stakes from multiple tokens and passing them to Conviction Voting (ex. use 80% AGVE and 20% HNY as governance tokens of a garden). We would be able to do the same using a Hooked Token Manager that aggregates multiple tokens, but at the cost of not being able to vote later on other Garden votings.
This is how it would look like a Gardens without the Hooked Token Manager and with the Staking and Voting Aggregator apps. Note that we use only one Staking app per token, so it could be shared among various Gardens:
Supporting a proposal in Conviction Voting would require to lock tokens within the Staking app, defining Conviction Voting as the Lock Manager. The tokens would only be unlocked by withdrawing the support in the proposals.
Staking and vote aggregation is useful in many use-cases in which we used hooks for, but there are cases in which we want to interfere with tokens transferability even if they are not staked. Examples of these would be:
- Allow list: you can only transfer to addresses within a list.
- Deny list: you cannot transfer to addresses within a list.
- Vesting: you cannot transfer until a certain amount of time has passed (this could also be implemented using the stake app, actually).
- Whales not allowed: you cannot transfer to an address that already has too many tokens (in absolute or relative terms).
For all these cases we could implement something similar to Autark’s solution: an oracle that tells the token if it can be transferred or not. Unlike their solution, I would suggest to reuse as much as we can the ACL oracle feature that AragonOS provides.
A good example of this would be the Dandelion Voting. It implements an ACL oracle that returns
false if the sender address has voted yes on a Dandelion proposal recently. This could be reused directly as a transferability oracle checked by the Token Manager! We could reuse ACL oracles if the Token Manager offered compatibility!
Token Manager hooks have been very useful for the 1hive architecture until now but they are expensive in terms of gas and constrain the capabilities of our Gardens. This document has exposed a solution that uses a different approach to design the next version of the Gardens’ apps.
These are the proposed changes to apply to our apps:
- Hooked Token Manager would be deprecated, and we could use a normal Token Manager or an enhanced one with the semi-transferability permissions.
- Conviction Voting would lock tokens from the staking contract to anyone who wants to vote on a proposal, with the similar logic used by Agreements, but without the possibility of losing funds.
- The voting aggregator would count the staking app as another token capable of voting in Voting.
These are some things to take into account if we choose going this way:
- It is still uncertain how to build a voting aggregator for Conviction Voting. We could implement it within Conviction Voting app itself, choosing which pre-allowed token to lock when supporting a proposal, or implementing a more general Stake Aggregator app.
- If you stake tokens on a proposal of a Conviction Voting app, you won’t be able to stake it in another Conviction Voting app from the same or another Garden until you withdraw them from the first one. This is not how conviction voting works right now, but we can say that it would not be an unexpected behavior when you stake tokens on a proposal.
- There may still be edgy use-cases in which it is required to execute code in the exact moment a transfer is made and tokens can not be staked. For those cases I would recommend to use more specific hooks: it is preferable to hook the mint or burn functions of a particular Token Manager or the deposit an withdraw functions of a particular Vault than hook all the transfers. We could leave the decision of what to hook to the discretion of each Garden developer, instead of enforcing a hook for all Garden tokens.
- We also have to take into account that we could use off-chain solutions to account conviction but we would still have the vote on-chain to apply the changes. We do not have a good solution for this yet, but it will be interesting to take a look on what Aragon is doing for this use case. Definitely something to study for Celeste.