Skip to content
This repository has been archived by the owner on May 11, 2023. It is now read-only.

NFT Reward Contribution Mechanism #8

Open
sebastinez opened this issue Sep 23, 2021 · 8 comments
Open

NFT Reward Contribution Mechanism #8

sebastinez opened this issue Sep 23, 2021 · 8 comments
Assignees

Comments

@sebastinez
Copy link
Member

sebastinez commented Sep 23, 2021

Abstract

This issue details the implementation of a proof of contribution mechanism, that would allow a user to claim a NFT showing his contribution through a commit to a specific organization.

Glossary

  • Message = The message describing the contribution.
  • Proof = The hashed and by the Orgs member(s) signed message.
  • NFT factory = The smart contract responsible to mint and transfer the contribution rewards.

Discovery

  • A org MAY, using the CLI search their repos for signed commits, in the commit header and in the trailers of the commit message.
    $ rad-reward discover --path <path-to-repo>
    Commits                                    Contributions
    f673cf386bc01688c53b51b4c2eedcf2945d70ea > committed-by  0CE7AA44C56AE6A8C43978663E3C1B6185AC4475
    ef27096c91f95791dccaf9992ef772b28a76558e > committed-by  0x8152237402E0f194176154c3a6eA1eB99b611482
                                               authored-by   0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0
    f179aab522d746735d73a8342a12a276be0e6cb8 > committed-by  ABAF11C65A2970B130ABE3C479BE3E4300411886
    
  • The CLI MUST show a listing of all the eligible commits, and all the signed contributions that were made.

Creation

  • With a commit found through the discovery mechanism, an org MAY create an offchain proof.

    $ rad-reward create \
      --receiver <receiver-address> \
      --org <org-address> 
      --project-urn <project-urn> \
      --commit <commit-hash> // provided or selected through CLI
    
  • The message to be signed MUST follow this pattern:

    The org {org_address} wants to reward {receiver_address} for the contribution of commit {commit_hash} in the project {project_id}
    

    Example:

    The org 0x8152237402E0f194176154c3a6eA1eB99b611482 wants to reward 0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0 for the contribution of commit b90bf64 in the project rad:git:hnrkybhzyqzg37cmaoyh7qs8uayjzf4qn7w1y

  • The org MUST hash the message with SHA256, sign the resulting hash by all its members and provide the resulting signatures:

    • In the case of a single owner, the owner MUST sign the hashed data.
    • In the case of a gnosis safe each member of the safe MUST sign the hashed data.

    Example of a proof for a single owner org

    {
      "address": "0x5e813e48a81977c6fdd565ed5097eb600c73c4f0",
      "msg": "0x546865206F7267203078383135323233373430324530663139343137363135346333613665413165423939623631313438322077616E747320746F207265776172642030783545383133653438613831393737633646646435363565643530393765623630304337334334663020666F722074686520636F6E747269627574696F6E206F6620636F6D6D6974206239306266363462616662363535643565386233306663643135366230313463333932323932316220696E207468652070726F6A656374207261643A6769743A686E726B7962687A79717A673337636D616F7968377173387561796A7A6634716E37773179",
      "sig": "0x10fcfb6c28bbe91550647bf1bb1db4accc95f03d18bfb7786390844b0e135687416ffea2a3012ff279cae44383028d89ca6fb093016aab591f50d620bfbb9ad51c"
    }

    Example of a proof for a multi-sig org

    {
      "address": [
        "0x8f49efe8ce86420221536c3cc97f00de593e0ad3",
        "0x0eB0778dA82972b6bAbef3147201096255ca05BA"
      ],
      "msg": "0x546865206F7267203078383135323233373430324530663139343137363135346333613665413165423939623631313438322077616E747320746F207265776172642030783545383133653438613831393737633646646435363565643530393765623630304337334334663020666F722074686520636F6E747269627574696F6E206F6620636F6D6D6974206239306266363462616662363535643565386233306663643135366230313463333932323932316220696E207468652070726F6A656374207261643A6769743A686E726B7962687A79717A673337636D616F7968377173387561796A7A6634716E37773179",
      "sig": [
        "0xa74820fc4df36e0e317d78c0b6636f8010f8ce81fee43fa1e30ffc689f36c10b3cf1e326868088a9e6e497650389c3cba54d6bf03942508103006cb3cb760ec21b",
        "0x822a9bf4c17e6143d00b0120bcb13b2ce112f8f09c06f0fd996acff4b58a1bf762b83cf013951875bd8e13967e5b754b80fbab4da2123096348aa8e5fafe3c9c00"
      ]
    }
  • Once signed by all the members, the proof MAY be stored as git-notes in the corresponding repository.

    • These proofs are being stored encoded in base58 in /refs/notes/contributions/, and can be seen in the git commit for example:
    $ git show --show-signature
    commit 1388152c68c835dd27b5ca962e018f0e557080a0 (HEAD -> master)
    gpg: Signature made jue 23 sep 2021 09:45:54 CEST
    gpg:                using EDDSA key BEAAF7F416675528064B5DEF379319A763C7C0F5
    gpg:                issuer "[email protected]"
    gpg: Good signature from "Sebastian Martinez <[email protected]>" [ultimate]
    gpg:                 aka "Sebastian Martinez <[email protected]>" [ultimate]
    Author: Sebastian Martinez <[email protected]>
    Date:   Thu Sep 23 09:45:54 2021 +0200
    
      initial commit
      
      Signed-off-by: Sebastian Martinez <[email protected]>
    
    Notes (contributions):
      JsHiVteQVKXj3mV43TkVLtDqbiPF3ZhWPLKVCHF8HgcFkyj66FUqRQ6FDhBYEundYsRobYD1T9zZ453CGwMG2qViiiJ2z2LGcpGFxfqbLxGT7tYY5AmZddg6iYHQW85jdwLPMcFTegvUsp98GrQQP5NgWehiJyRwu28JXUNJLFUZZZCDJV73iAvGVGYiRejr1xFhC2HpQXhX5SSoCEtjotv81w24d93Gi8hKp1P8QS6hj5vjVFNFLNMiXTkgaMHT5wyqn4GWZp2AAA1EHZ3cKjk1WUZSnZqdK9ZTSD8KQ2DNLc1DzLyW1YsqAjfnWDYxGojXKSffDkqEao6XjC2q2osmBKtGmnnx6rmrGjq7hYN9DkvZAsCctDvy86apCPnGdfKDZe6J92qyJpsx4wh11WSFRRv6paJcyTwhfr4MT8N9mSmGG8TPEKfLqa9yDdWpazByioVkqxTEqCY9zLz7DXGCXNbToHugSwitWTv4q4GFsDMYxVyHeAvFmxHXJwZ4ECg5uw4WYA9Q5KP18AUhDwFztS225A4C2qJi3DsefJMqEHVAm3mwV7m2SMNPcG6tTrg7iiLoG9FcayjNo4CWe8XmLr9MWoJBCaJtpVPLtSP6F1Sb8zHBJBTbrhx3KRTRGuRWfzsgSq3VcFRXng5DjCykbTh7KaSgCP6QLzaiBHLvfFnTtZCfcswrLLw4aNfwnWeRSYtLxPLugFdU5php4WGaLjeSqi85mhcnqXGocQudhi12B7RHzqWFvXDKYiegahWXF7aucmaJpf1viAxN2Lh3GQj8HiSC6ZSVum2CsGrr2eEXU3cKHJ1yUW2tWFt1cJEg8bUWe9nXtKcRRU23WTFAmjCaG5qk1qV16moHY5JcTrbn7xdRbrJoBYk24B27Rwr2Ls3CGpWAquHqvxET2BQzdVy5SfdVN3h6TYHbm4p85W4s1uZ764Wh2J2rYDhEPH1GnZ6zhEJB9FGj1dunvy64vuyg3bRj2XjhRQigdjUUg9LD5o1VdmUyzK2fqJRjb7tqMfiW8QUR2X5FBvXaYzFQ4arhD7X5n2pmgHFeyKXP1PtP61fLnFcecQtJBpK5j7i6qcmKeBXYbLR5ZZ28wRT68uWcDy3343mQW31hhEcXyVN7teKY5STDcxhzBjbFTp19oEizoXqqVSPY1dKM4zU9CReiRTdoyovZvyh3n6VbWv1bVZgScK273xKTTDie11Xk6yr7C7pa6a2R1vVoMGP2rFeJ4sZtq4xL5xi7NBCARkTT55QhwdTrDJ8gc
    

Claim

  • The contributor can obtain the proofs created by a org, when searching with the CLI through the git repository, looking for proofs where the org assigned him as receiver, he should receive a listing of all the eligible commits and select the commit, or provide the base58 encoded proof as argument
$ rad-reward claim \
  --repo ~/repo \
  --rpc-url <rpc-json-provider-url>

Eligible Commits
  2821409 Show Seed ID on project page
> fd5b617 Move 'Clone' button so the pop-up doesn't spill
  85042ab Use websocket provider for additional performance
  af3ace3 Use existing resolver in getRegistration
  • Once identified the commit the contributor wants to mint a NFT for, he creates and sends a ETH transaction through the CLI where the CLI uses the proof to encode the information into a ETH transaction, the contributor signs the transaction and sends it to the NFT factory.
$ rad-reward claim \
  --proof <base58-encoded-proof> \
  --rpc-url <rpc-json-provider-url>
 
Claiming reward for commit fd5b617...

Mint

  • The NFT factory receives the transaction and checks the following things:
    • msg.sender == proof.receiver
    • For checking the provided signature we have to take the following precautions:
      • In case of a single owner org, it should be enough with org.owner == ecreover(proof.msg, proof.sig)
      • If the org is a safe, we MUST check with the Gnosis Safe checkSignatures function, if all signatures provided with the proof are valid and correspond to the safe members.
  • Once the contract verifies the signatures, and the origin of the proof he creates the payload of the token with the following structure.
    • Example:
      struct Reward
        {
          address org;
          address receiver;
          bytes32 commit;
          Project project;
        }
      struct Project {
          // A tag that can be used to discriminate between project types.
          uint32 tag;
          // The project id in multihash format.
          bytes multihash;
      }
  • A new tokenId gets created on-chain and the payload gets assigned as tokenURI of the token.
  • After the creation of the token the Transfer event gets emitted and the CLI will display the tokenId for the contributor.
@sebastinez sebastinez self-assigned this Sep 23, 2021
@Ryanmtate
Copy link
Contributor

👋 Hey @cloudhead @sebastinez, would love to help in any capacity on this issue. I'm in the discord channel if you want to connect.

@cloudhead
Copy link
Contributor

Great start. I think it would be useful to lay out what the method call might look like when the contributor claims their reward.
Additionally, as mentioned in RTC, I think the Org would ideally sign an EIP 712 payload instead of a bytestring.

Besides that, it all makes sense to me.

@Ryanmtate
Copy link
Contributor

Ryanmtate commented Sep 29, 2021

I agree that EIP-712 would be the best way to go about displaying user signed information and efficiently verifying the payload.

I'm happy to work on a PR for ethers-rs to implement sign_typed_data. The EIP and JS example provides good implementation details to get started. Also read the MetaMask eip712 article, which provides a good example for signing, and verifying within the contract.

I can work on adding eip712.rs to the list of transaction types and updating the signing trait with sign_typed_data, something like:

async fn sign_typed_data(&self, data: &TypedData, address: &Address) -> Result<Signature, Self::Error>;

I imagine TypedData would implement Serialize/Deserialize between JSON and the Struct and have additional helper / verification methods.

If this sounds good to you both, happy to get started on it.

@sebastinez
Copy link
Member Author

Hey @Ryanmtate yeah I think that would be great!
I would add as reference material the issue gakonst/ethers-rs#16 where Georgios shows the idea of a trait based implementation, creating a custom derive macro that could be defined on top of the struct which one wants to use as typed struct.
I'll push a first alpha version of the CLI with the ability to create the contribution puzzle which we need to send with EIP-712 so you can eventually use it to test the ethers-rs implementation.

@Ryanmtate
Copy link
Contributor

Thanks @sebastinez! I think a custom derive would be great for this. I'll work toward an implementation and continue to review the other libraries mentioned in the thread.

@Ryanmtate
Copy link
Contributor

Ryanmtate commented Oct 1, 2021

@sebastinez @cloudhead getting closer to having Eip712 derive merged into ethers-rs-- opened a draft PR gakonst/ethers-rs#481 for early feedback.

Still have to work on test coverage and verify accuracy against solidity contract and EIP specification. Some features, such as nested struct encoding is not there yet, but is on my radar for implementation.

Would love your feedback!

@Ryanmtate
Copy link
Contributor

Quick update on the Eip712 derive macro. Getting closer to landing it.

@sebastinez
Copy link
Member Author

This issue has been deprioritized since we are working on ways to award contributors with rewards offchain by looking at the git repositories instead of creating onchain transactions for each reward/contributor.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants