ERC-4824 Decentralized Autonomous Organizations

Also I’m not sure what fields are considered required and which optional?

For some use cases, the Members JSON-LD file is not possible as there is maybe no array list of members held by DAO contract, only total supply of governance tokens. This to save gas and storage, and maybe anonymity/security reasons too.
When proposals are voted for, at that stage can get list of accounts that voted with their governance tokens. So only then can these subset of accounts be recorded in proposals/activity files.

Hi all,

Forgive me for not contributing to the technical discussion around this proposal.

Re: mutli-DAO proposals and master-minion contracts, can someone link me to those discussion pages or (if they exist) proposals?

1 Like

Hi,
This works for my use cases for e-commerce and social media:

I was concerned on two things; lack of any timestamps and the JSON-LD files lack of security as they can be stored on centralised storages then changed at will.

I believe this helps bolster security by “grounding” the JSON-LD to the blockchain a lot more, by ensuring that any updates have emitted together the msg.sender and uri (hashed) link.

    event DAOUpdate(
        address indexed sender,
        address indexed dao,
        string name,
        string description,
        string governanceURI
    );
    event MemberUpdate(
        address indexed sender,
        address indexed dao,
        address indexed member,
        string memberURI
    );
    event ProposalUpdate(
        address indexed sender,
        address indexed dao,
        string indexed proposalId,
        string proposalURI
    );
    event ActivityLogUpdate(
        address indexed sender,
        address indexed dao,
        string indexed activityId,
        string activityLogURI
    );

This means breaking up the updates to their own JSON-LD files. So there is an individual Member JSON-LD Schema, Proposal JSON-LD Schema file, individual Activity Log JSON-LD Schema, rather than having them collected in bigger JSON-LD files.

Every update is posted out by calling their corresponding singleton contract function; dao, memberUpdate, proposalUpdate, or activityLogUpdate with the msg.sender address and uri link.

    function dao(
        address calldata dao,
        string calldata name,
        string calldata description,
        string calldata governanceURI
    ) public {
        emit DAOUpdate(msg.sender, daos, name, description, governanceURI);
    }
    function memberUpdate(
        address calldata dao,
        address member,
        string calldata memberURI
    ) public {
        emit MemberUpdate(msg.sender, daos, member, memberURI);
    }
    function proposalUpdate(
        address[] calldata daos,
        string calldata proposalId,
        string calldata proposalURI
    ) public {
        emit ProposalUpdate(msg.sender, daos, proposalId, proposalURI);
    }
    function activityLogUpdate(
        address calldata dao,
        string calldata activityId,
        string calldata activityLogURI
    ) public {
        emit ActivityLogUpdate(msg.sender, daos, activityId, activityLogURI);
    }

This solves my timestamp issue as every emitted event has a block number it came from. This can be retrieved and block numbers converted to timestamps using a web3 library.

Can validate off-line too that the posted uri hash matches the content that it points to (depending on recognised link formats like ipfs). As mentioned, each posted uri link is emitted with the msg.sender address, thus we know what account made the change. Off-line processing can validate if that account is a DAO member etc.

With the use of event filters from a web3 library, all proposals’ updates for a DAO can be retrieved and sorted by block number, hence just the latest changes can be returned etc. Similarly, all or individual proposals, members and activity logs can be retrieved using any or combination of their event’s indexed parameters.

So now I have a fourth version:

contract ERC4824v4 {
    event SchemaUpdate(
        address indexed sender,
        string indexed indexedSchemaURI,
        string name,
        string description,
        string schemaURI
    );

    event DAOUpdate(
        address indexed sender,
        address indexed dao,
        string name,
        string description,
        string governanceURI
    );

    event MemberUpdate(
        address indexed sender,
        address indexed dao,
        address indexed member,
        string memberURI
    );

    event ProposalUpdate(
        address indexed sender,
        address indexed dao,
        string indexed proposalId,
        string proposalURI
    );

    event ActivityLogUpdate(
        address indexed sender,
        address indexed dao,
        string indexed activityId,
        string activityLogURI
    );

    function schemaUpdate(
        string calldata name,
        string calldata description,
        string calldata schemaURI
    ) public {
        emit SchemaUpdate(msg.sender, schemaURI, name, description, schemaURI);
    }

    function daoUpdate(
        address dao,
        string calldata name,
        string calldata description,
        string calldata governanceURI
    ) public {
        emit DAOUpdate(msg.sender, dao, name, description, governanceURI);
    }

    function memberUpdate(
        address dao,
        address member,
        string calldata memberURI
    ) public {
        emit MemberUpdate(msg.sender, dao, member, memberURI);
    }

    function proposalUpdate(
        address dao,
        string calldata proposalId,
        string calldata proposalURI
    ) public {
        emit ProposalUpdate(msg.sender, dao, proposalId, proposalURI);
    }

    function activityLogUpdate(
        address dao,
        string calldata activityId,
        string calldata activityLogURI
    ) public {
        emit ActivityLogUpdate(msg.sender, dao, activityId, activityLogURI);
    }
}

Edit: Addtional event to handle schema updates

    event SchemaUpdate(
        address indexed sender,
        string indexed indexedSchemaURI,
        string name,
        string description,
        string schemaURI
    );

Another point I wish to make about v4 is that the amount of JSON-LD files to write out will be a lot less, as won’t have to update a whole set of files ('cause they are linked) for one change. Only the JSON-LD file that has changed needs to be uploaded now.

This makes memberships that change a lot more possible to keep up to date as just need to call the memberUpdate method of the singleton contract when a member joins, to emit the MemberUpdate event. If a member leaves then can indicate this by simply calling memberUpdate with an empty memberURI string. All this means that DAOs that don’t explicitly keep a record of all members to save gas on storage or other reasons, don’t have to store an array of members in their contract.

I have a little time over the weekend to implement this.

That way can examine any potential pitfalls. E.g. do endpoints to blockchain do throttling if there is a lot of event requests?

Hi,

I tested out the concept and it met my requirements.

I set up some dummy DAO details called DAO Charity.

2022-04-11 20_09_59-localhost_3100_dao1

I then called the DAO Caster (provisional name I gave to the smart contract that emits the DAO updates).

Then I set up a dummy DAO discovery service that was able to retrieve the DAOUpdate event and its JSON-LD file. Noticed how the sender account address (msg.sender) is retrieved as it was sent out by the DAOUpdate. Additional information can be obtained from the event, such as the block number the event was emitted:

Next, I set up a new proposal.

With DAO discovery service, was able to retrieve the proposal details from the proposal event emitted by DAO Caster.

Noticed how the JSON-LD file got flagged with “Warning Sender was not a member”. This was achieved by using the sender account address sent out by the DAO Caster ProposalUpdate event to look for MemberUpdate events that had that sender account address. None were found for the block number the proposal was submitted in.

I also added extra details, as seen in the ActivityLog JSON-LD file:

One extra property is the DAO address that is required for the security checks. Now that the JSON-LD files have sender details and the URI hashed links sent from the emitted events, the JSON-LD files can be checked and verified. I believe this makes the JSON-LD files in this web3 world much more secure and practical to use. Thus increases the security of other JSON-LD files that link to them.

Code for DAO Caster and test apps here: https://github.com/julesl23/DAO-Caster

3 Likes

A note, JSON-LD schemas in the web3 world, should be secured on immutable storage and hashed, just like the DAO updates. Hence, DAO Caster has an additional function called schemaUpdate that can be called for when a new or amended schema is uploaded for use.

1 Like

Hey @Asgeir and @julesl23 , sorry for the delay in responding!

Some of the other co-authors (Zargham and Isaac) and I recently had a chance to work on this in-person during a conference last week. TL;DR: instead of asking DAOs to interact with the existing Poster contract in order to emit an event describing their daoURI, we will encourage DAOs to deploy a new, thin contract that contains functions to display and manage daoURI. This is similar to one of @Asgeir’s recommendations to deploy a new Poster-like contract. The spec for that contract is written below.

We will also deploy a factory contract to make it easier to deploy these contracts (so in practice the DAO just needs to emit a contract interaction, just as with Poster) and to take care of other “convenience” things like detecting the DAO contract form & deploying an fully-functioning endpoint, but the factory contract will not be part of the specification.

ALSO, in response to comments from one of the EIP editors, we are planning to rename the standard to “EIP-4824 DAO API” (this might help explain the repeated references to “API” in the rationale below).

Rationale

Migrating or upgrading contracts is a nontrivial task, and asking DAOs to migrate contracts just in order to implement the daoURI interface presents a significant barrier to adoption. On the other hand, it is relatively straightforward to ask DAOs to (1) deploy a new “API manager” contract and then (2) verify ownership via a contract interaction with the manager contract. In cases where the new contract is deployed via a contract interaction with a factory contract, the two steps above can be condensed into a single contract interaction.

We also considered allowing DAOs to “post” their daoURI by interacting with an existing contract, e.g. one based on EIP-3772 Poster, in order to emit an event containing the daoURI address. The issue with this option is that daoURI is a key part of a DAO and we believe it should live in DAO’s own contract space and be “self-hosted” rather than be tied to an external contract. Not having control over the daoURI contract also makes it harder to transfer ownership of the DAO’s API due to contract migrations or other events. [side note: Zargham referred to this manager contract as a kind “mutable NFT”]

DAO API Manager Contract Specification

Here’s a complete specification of that contract; we will refine this spec into an actual contract specification that will go into the standard.

Goals

  • provide DAOs a means of “owning” the URI pointing to their metadata
  • increase discoverability of the URI from a DAO’s existing contract(s)
  • allow DAOs the ability to update their metadata (in this case, just daoURI)
  • provide on-chain transparency for updates to their data

Data Structure

  • Owner:
  • Offer: {ToBeOwner: , Deadline: }
  • daoURI:

Restricted Methods (msg sender restriction)

Offer Ownership (OwnerOnly)

  • assert msg sender is Owner
  • input message has 1 argument
  • optionally we generate block height a “Deadline” after which the offer is timed out
  • posterior state: ToBeOwner is changed to the argument
  • emit a proposed change of ownership event

Claim Ownership (unique state depended address)

  • assert msg sender is ToBeOwner
    ++ assert the blockheight is before the deadline (probably using Get Offer Status method below)
  • input message has no argument
  • posterior state: Owner is changed to ToBeOwner
  • emit a change of Owner event

Update URI (OwnerOnly)

  • assert msg sender is Owner
  • input message has 1 argument
  • posterior state: daoURI is changed to the argument
  • emit a change of URI event

Public methods

Get Owner

  • no restrictions on who can call this method
  • no arguments required
  • returns Owner field
  • intended to run off chain (since it doesn’t mutate the contract state)

Get Offer Status

  • no restrictions on who can call this method
  • no arguments required
  • returns True if the deadline is not past, and false if it has
  • intended to run off chain (since it doesn’t mutate the contract state)

Get Candidate Owner

  • no restrictions on who can call this method
  • no arguments required
  • returns the 0x000 address (or the current Owner) if get offer status is False
  • returns the ToBeOwner address if offer status is True
  • intended to run off chain (since it doesn’t mutate the contract state)

Get daoURI

  • no restrictions on who can call this method
  • no arguments required
  • returns daoURI field
  • intended to run off chain (since it doesn’t mutate the contract state)
1 Like

Hi there,
This is my first contribution, apologies if I is is slightly out of scope.

I was recently onboarded to the DAOStar initiative in the scope of the Aragon project (@nivida and Michael Heuer) and I found a couple of topics that I would like to gather your feedback from, as they may affect other DAO tooling as well.

Standardised Multi endpoint URL’s

Talking about a DAO API, it seems unavoidable to deal with centralized services. But at the same time, DAO’s are also likely to pin static metadata through IPFS or similar. Centralized + decentralized.

If the value behind daoURI() or any JSON URI field is interpreted at the will of the implementer, there’s a risk that some DAO details can be browsed on one platform but not on another one. What’s the common intuition about endpoints?

  • https://service.net/file.json (no integrity check)
  • ipfs://ipns/<ipns-name>/meta.json
  • ipfs://ipfs/<cid>/meta.json

I Vocdoni we went a bit further back in 2018 and drafted a simple solution that could inspire some standardisation on the DAO API side. Example of an on-chain URL:

ipfs://ipns/<ipns-name>/meta.json,ipfs://ipfs/<cid>,https://server-fallback.net/meta.json!<expected-hash>

  • Split the URL by commas ,
  • Try to resolve from left to right
  • If the https endpoint is fetched, hash the data and compare it to the value after the ! bang symbol (optional)

I would be glad to get your feedback.

Multiple types of membership

Another topic where I’d like your feedback is about the fact that some DAO’s may have more than one membership model in parallel.

Example:

  • A group of wallets on a multisig can approve Action 1 at any time
  • A massive group of ERC20 (weighted) holders can approve Action 2 after a two-week vote
  • People with a valid ZK proof based on off-chain data can vote to approve Action 3

There may be cases in which a flat list of members doesn’t accurately represent de definition of the membership. In some cases it may not even be possible to generate a deterministic list of wallets.

Does this scenario echo with someone else?
Thank you!

1 Like

I Vocdoni we went a bit further back in 2018 and drafted a simple solution that could inspire some standardisation on the DAO API side. Example of an on-chain URL:

ipfs://ipns/<ipns-name>/meta.json,ipfs://ipfs/<cid>,https://server-fallback.net/meta.json!<expected-hash>

This seems very sensible to me, and could be used to designate fallbacks even if we assume DAOs don’t use IPFS. That said, I think this could be a separate standard from EIP-4824. We can, however, start playing around with something like this in our own internal implementations.

There may be cases in which a flat list of members doesn’t accurately represent de definition of the membership. In some cases it may not even be possible to generate a deterministic list of wallets.

Agreed; however, EIP-4824 doesn’t define how DAOs define membership internally, just a mechanism for publishing a list of records. They are free to define multiple membership categories, and then record them via some sort of “category” field in each member record.

Thank you for the response Josh,

  • What would we need for an interim recommendation to eventually be part of the current draft?
  • What’s the procedure to start a separate EIP? A new post on the Ethereum Magicians forum?
  • Then, may it be useful to propose a JSON schema for it and some recommended values? There is already a recommendation about proposal status enum’s, where each DAO framework has its own values. Maybe there can be a similar thing with categories?
  • Regarding endpoints tied to a backend (members, activity), would it make sense to suggest a pagination mechanism? The list of all USDC token holders may be quite large.

I’d also like to ask about another topic regarding metadata contents, like a DAO’s description. Is there consensus about how description texts should be interpreted?

  • Plain text, wrapped.
  • Plain text, transforming line breaks
  • Plain text, converting line breaks into paragraphs
  • Markdown (restricted subset)
  • Markdown
  • HTML

Thank you!