Proposal to update the Hub contract (Circles v2)

Hey everyone,

we recently found ourselves working around some issues we already brought up in Earth Circle IP 1 - Circles 2.0 Architecture - #12 by ouie .
Instead of keep working around these issues with dirty tricks in the client software (which hurts compatibility) we decided to take the step and created a v2 Hub- and Token-contract.

We outline the goals and how we intend to achieve them below and kindly ask for feedback.

Goals

  1. Keep all existing trust relations working
  2. Remove the necessity to hold on to own tokens to be able to receive other tokens
  3. Allow accounts to use a trust list of a trusted entity to automatically accept tokens listed there (follow trust)
  4. Remove the possibility to keep tokens alive from “outside”
  5. Have a shorter inflation period
  6. Have a well specified upgrade path for future versions

Organizations:
Besides not being a hard goal it turned out that no changes are required for organization wallets.

Upgrade process

Exchanging v1- for v2-Tokens

Everyone who holds v1 Tokens can exchange them for v2 Tokens as soon as the token owner migrated to the new version.
To do that, each v1 token holder sends their holdings to the hub v2 contract and then mints the same amount of v2 tokens.
This process should be automated by the client software so that whenever a user upgraded to v2 all holders of tokens of this user will exchange their tokens on the next usage of the client.

Persons

New signup

All new users have to signup at the v1 hub just as they would do now. Additionally, they have to call the v2 hub’s “migrate” function.

Migration from v1

If a user already got a circles safe and receives UBI then this person needs to deactivate their v1 token before the upgrade.
The client software should make sure that the outstanding UBI is minted a last time before the token is de-activated.
When the token was deactivated the user can call the “migrate” method on the v2 hub and then exchange all of their own tokens.

Organizations

Since the orga accounts didn’t suffer from the receive-limitations in the first place, nothing changed.
However, an organization still can decide to register a Verifier which then has the same effect as on user accounts.

Implementation

We copied the Hub- and Token.sol contracts and modified them as described below. The new files have the “v2” appendix in their name.
Additionally, we added a new “Verifier” interface which can be used to implement the “follow trust” functionality.

HubV2.sol

This is a copy of Hub.sol where all parts regarding trust have been removed.
It references the v1 Hub which is used to check trust limits and if a user/orga is already signed up.

We made some changes to existing functions and added some new:

checkSendLimit()

This method now treats trust as binary (either you’re trusted or you’re not).
We also removed the restriction which required users to hold own tokens in order to be able to receive other tokens.
The effect is that as long as you trust someone that person will be able to send (all) their tokens to you.

Also, we added a way to delegate the send-limit calculation to a different contract.
We use this to implement the “follow trust” which is explained below.

useVerifier()

Users can set a custom verifier if they like to do so. Verifiers are external contracts with the same interface as the “checkSendLimit” function (see VerifierI.sol).

The verifier is a fallback that’s used when no trust relation exists between the sender and receiver (or the trust was set to zero).
The main use case we intended for it is the “follow trust” functionality where a user can decide to automatically accept all tokens that are accepted by another trusted entity.

migrate()

Since all users need to signup at the v1 Hub first they always “migrate” to the new version.
The method checks if the users’ v1 Token is stopped (doesn’t mint new UBI) and only then deploys and registers the new token.

mint()

Users who hold v1 tokens must have a way to exchange them for v2 tokens. This is what this method is for.
It uses the same principle as the group tokens but always burns the received v1 tokens.

It’s used as following:

  1. [if token owner] Mint all outstanding v1 UBI
  2. Transfer v1 tokens to the v2 Hub
  3. Call mint() to get the same amount of v2 tokens back

! Steps two and three have to be completed in the same transaction otherwise someone else could mint instead of the original owner.

TokenV2.sol

The v1 token and v2 token are identical except for one additional method on the v2 Token.
Also, only the token owner can call the update-method now to prevent other people from keeping tokens “alive”.

update()

Same as in the v1 token except that it can now be called only by the token owner.

migrateMint()

This method can only be called from the v2 hub.
It is used to mint new v2 tokens for v1 token holders via the HubV2’s mint() function.

Source code

You can find the new contracts here: https://github.com/jaensen/circles-contracts/tree/fork-1
They’re forked from Alex’s fork which already includes the GroupCurrency contracts.

New files are:

as well as the corresponding interfaces:

Future updates

We think that future updates can be handled in the same way as this proposed update.

1 Like

Nice, thanks for sharing! Looks good, but I only really scanned over it.

I especially like the followTrust implementation, looks neat.

As discussed in the chat, I was wondering if the Factory pattern could help here for future changes. I was thinking of something like this

But I am really not sure if this is probably overengineered with not much help.

The idea was that the Hub has an exchangeable TokenFactory and only knows Tokens by their interface. The Token implementation is only known by the TokenFactory, the interface of which is not allowed to change (but it’s only one method anyways).

Hi!

Hmm… looks interesting. I could image such a scenario for organizations (basically they could have a group token that’s known to the circles hub). But for private tokens I think it might create some issues:

  • If f.e. the token implementation can be changed by the user then it would be possible to mint different amounts of UBI or even override the transfer functionality to do do something nasty there.
  • If the token implementation should be changeable by the hub-owner than we’d need to establish such an owner first. I don’t think this is desirable (even if the owner would be a DAO) since it would give the “hub owner” a lot of power.

The first argument I don’t get, can you rephrase? So the user can’t change anything, it would always be the “Hub-Owner”(-DAO) who would also review (or probably provide) the factories.
For the 2nd argument: yes, that’s true for sure. So “flexibility” in terms of updates comes with the high price of establishing governance for exchanging token factories.
So as I see it now it is not worth it, it really depends on the interval of creation of new Hubs - if this happens more often or flexibility is needed more over the time, then it might be worth reevaluating.

O.k. then the first concern doesn’t apply. I was not sure what was meant by “owner”. Could have been the token owner or the hub owner.

Thanks for this initiative @jaensen

I would suggest - if we go this route of a contract update we should reflect the new balances (demurrage instead of inflation) in the contract as well.

However - I see value in both representations:

a) the demurrage representation as this is closer to what users see/ expect
b) the inflation representation as it can be problematic for other contract if a token suddenly (constantly) changes it balances (“re-base-token”)

A solution could be that each user deploys 2 token contracts when they sign up for Circles. While I first thought they might share the same state even, that seems dangerous. The simplest compromise would be to at least add a non-standard “get_adjusted_balance” function. Potentially even an “adjusted_trasfer” function that call a regular transfer after converting the balance.

Wonder if we can get rid of requering a signup in v1 as it unnecessarily creates a v1 token.
The alternative would be to signup directly in v2. The issue would be that this then would require the trust connections of this person to be stored in the v2 hub leading to a situation where a pathfinder would need to aggregate state from 2 different contracts.

A possibility that comes to mind is that the pathfinder (and all other software) always asks the v2 hub for trust connections and the v2 hub falls back to the v1 hub if no connection for the pair can be found there.

It wouldn’t be easily possible to create trust relations to users who are still on v1. All new trust relations would need to be established via the v2 hub and every relation that exists there overrides the v1 trust connections. But if I e.g. untrust someone in the v2 hub then I still need to do this in the v1 hub else the existing tokens won’t be affected. I’m not yet sure if this has some other unwanted consequences as well.

I myself don’t see a huge problem when there is also a v1 token as it will be “killed” right away for new users. The only drawback I see is a bit more “clutter” and of course the gas fees. The upside is that the process is simple and easy to reason about.

In an ideal world we would have a separate contract for the trust network which is independent of the hub. Besides the added legacy-clutter the v1 hub would serve this role just well once everybody moved to v2+.

The more I think about it, I believe having a clean v2 trust hub + seperated token hub and removing old v1 legacy clutter might be the cleanest way to go forward. With porting v1 to v2 a user would just re-run their trusts in the new v2 trust hub.

Hi,
Today I am back from my summer vaccation. Last I heard (from Martin) was that we were postponing the launching of smart contract update until next year, so you have to excuse that I have not been in the loop over the summer.

That’s a lot of work! Thanks for sharing this suggestion. Would have been great to know you were already working on it though as we had officially put this on ice for a while.

I suggest we meet and have a discussion around these changes and also plan a shared timeline for the transition so that we are all on the same page and can collaborate on the updates. I’ll send an invite in our shared channels.

I am also wondering if you have updated any tests or if you are planning on doing those once we have agreed to changes?

Would be cool as well if we could agree on making PRs to the CirclesUBI repo for the contracts as there are currently several forks. What do you think?
I think we fixed the access on github and so on before the summer, let me know if not and if you can’t create PRs!

Regarding the contents we agree with not having a Hub owner and that we prefer the solution that does not use the trust network of hub1.

Hi! I had a meeting (about 1 month ago) with Martin where we discussed on how to proceed with the version 2 of Circles. The following is a summary of what we talked about:

Token

Creation timestamp

The token should have a field that stores it’s creation timestamp.
The need for this emerged while discussing the integration with proof of humanity (see appendix).

Predecessor token:

In the next version of the Token it should be possible to specify a ‘predecessor’ token on creation.
The predecessor token must exist and be stopped to be accepted as predecessor.
The need for this emerged while discussing the integration with proof of humanity (see appendix).

The predecessor is specified in the token’s constructor. This means that everyone can claim to have the successor of a stopped token. This also means that there can be more than one token claiming to be the successor of one token.

Allowances for the hub

Currently the token is tightly coupled to the hub. It contains a reference to the hub that created the token initially.
This reference is used in the “onlyHub” check which in turn is used only by the “hubTransfer” function.
To allow easier upgrades in the future (namely replacing the hub without the necessity to also create a new Token) we’d like to loosen this coupling and replace it with allowances for the hub.

Since regular allowances must be set per token holder, this mechanism must be replaced with one that allows to set the allowance for all token holders simultaneusly.
Therefore we decided to override the “allowance” function of the ERC20 and add a fallback handler for the case that no token-local allowance was found.
The fallback handler checks the “globalAllowances” on the V2 hub. This way, whenever a new hub might be required, it’s possible to update the global allowance to point to the new hub without the necessity to deploy a new token.

Caution: This can be implemented in different ways. Some of them allow to remove the hub-allowance completely which would prevent hub transfers completely.

TODO: Discuss the side effects of each implementation and decide if they’re wanted or tolerable.

Hub

Keeping the v1 trust connections vs. starting from scratch

We also discussed the possibility to create a completely new v2 hub that doesn’t rely on the v1 hub for the trust connections. In the end the only argument we found for a clean sheet v2 trust network was the reduced “clutter” by not requiring new users to create and immediately stop a v1 token. But once this is abstracted away by a library, barely anyone will notice it. We think that the current trust network (despite the many stopped tokens) is a real, organically grown network that is worth to be preserved.

Allow to trust group currencies/organizations

In the v1 hub it’s not possible to trust organizations. Since the group currencies are registered as organizations they cannot be used as token in a transitive transfer.
The idea is to add a second trust-map to the v2 hub which only handles trust to organizations. All other trust queries and mutations are forwarded to the v1 hub.
Systems that are not aware of the v2 hub’s trust connections can continue to function normally while aware systems can utilize the new trust relations.

Add a “data” parameter to the transferTrough function

It should be possible to link additional data to a hub transfer. The simplest use case is to link a transfer message which is stored in IPFS to the transfer.
We should use 2 byte prefixes to specify the link type. An example would be 0x0001 for a IPFS hash, 0x0002 for a URL etc.

Appendix

Proof of Humanity case

We want to create a group currency that contains the safes/tokens of all people who completed the Proof of Humanity procedure. As it turned out in a discussion with Andrei on Telegram it’s not enough to map the PoH soul-id to a safe address and call it a day. The inherent problem with the simple solution is, that users can change the wallet address that’s assigned to their soul-id as many times as they want. In the case of the group currency, this would allow a user to create multiple UBI safes/tokens and then rotate the mapped safe address one by one, each time minting new group tokens.
To prevent the above attack, it’s necessary to add some new fields to the CRC token which then can be used in a validation function to make sure that users can only rotate to a new token once their old token is already stopped. Also the new token must be created after the old one was stopped.

As always with the kind request for comments :slight_smile:

2 Likes

Thanks, here come the requested comments :slight_smile:

Token

Creation timestamp

easy to do and might also benefit other use cases, eg. for Group Currencies to evaluate “shares”.

Predecessor token:

I don’t get this, I understand the need for this, but is there some algorithm to identify “predecessor tokens”? Otherwise I could just implement some stoppable interface and that’s it - so the question is how should the successor token know that the predecessor is valid?

Allowances for the hub

Does this mean that the allowance is for the Hub (1…n users) instead of one EOA?

Hub

Keeping the v1 trust connections vs. starting from scratch

Certainly reusing the existing trust network does make sense. I don’t get the argument against it? I tought the idea was to have the v1 Hub as a “Trust entity” which will be reused by successor versions?

Allow to trust group currencies/organizations

Sounds great to have a “delegate” version for the non-orga-trusts and have v2 implement orga-trusts.

Add a “data” parameter to the transferTrough function

That would be great, however it would not include the “legacy” transfer and transferForm functions?

Appendix

Proof of Humanity case

This is related to the first topic - I don’t understand how a valid predecessor can be identified? I can always add a reference to something that implements the interface isStopped, so how can the successor identify a valid predecessor?

1 Like

Predecessor token

I don’t get this, I understand the need for this, but is there some algorithm to identify “predecessor tokens”?

We can do a lookup at the hub to see if it is in fact a CRC token.

There are two possible ways to add a Safe/Token to the PoH group currency:
a) A user adds their Token for the first time
b) A user adds a successor token

In the first scenario, the group currency just creates a new (Soul-ID → Token) mapping and the token is then added as member token.

In the second scenario, the group currency must check if there is already a token associated with the user’s soul-id. If that’s the case then the new token must have the already registered one as predecessor and must have been created after the predecessor was stopped.

Allowances for the hub

Does this mean that the allowance is for the Hub (1…n users) instead of one EOA?

Yes it means that the Hub has allowances for all user balances of all tokens. It basically replaces the direct link between the hub and the token and is meant to support the hub transfer case so that the hub can do transitive transfers.

Keeping the v1 trust connections vs. starting from scratch

I don’t get the argument against it?

New users will always have to signup at the V1 hub first even when the V2 hub is online. This means that they also always deploy a V1 token and get the V1 signup bonus. They then have to stop the V1 token and must “migrate” to V2. This adds “clutter”… But I think overall benefits outweigh the clutter.

Add a “data” parameter to the transferTrough function

That would be great, however it would not include the “legacy” transfer and transferForm functions?

True. But since these functions are basically fixed by ERC20 we cannot do anything about it. Also the way circles are meant to be transferred is via hubTransfers.

We can do a lookup at the hub to see if it is in fact a CRC token.

There are two possible ways to add a Safe/Token to the PoH group currency:
a) A user adds their Token for the first time
b) A user adds a successor token

In the first scenario, the group currency just creates a new (Soul-ID → Token) mapping and the token is then added as member token.

In the second scenario, the group currency must check if there is already a token associated with the user’s soul-id. If that’s the case then the new token must have the already registered one as predecessor and must have been created after the predecessor was stopped.

Ok, I would have to understand more about the Soul-ID thing and Token mapping. So looking up the Hub makes sense, but what to look for exactly and how to make sure that the current user is actually “allowed” to do this, so how to trustlessly assign the Soul-ID to the old and new Token - there are some pieces missing for me.

Keeping the v1 trust connections vs. starting from scratch

This adds “clutter”… But I think overall benefits outweigh the clutter.

yes, but it’s true: this is not nice but could be included in some workflow and be transparant to the user, ie. “it could just work” without the user having to do something.

Lets discuss this in more detail in:
https://aboutcircles.com/t/proof-of-humanity-group-token/489/5.