Smart Contract Documentation

Table of Contents

Create A Data DAO

Prompt: “Write me a set of solidity contracts for a DAO that uses a non-transferable ERC-20 token for governance and token-weighted voting, enable someone with an ethereum wallet address to join the DAO and become a member by uploading a dataset to IPFS, mint governance tokens to members proportionally relevant to the size of the dataset they upload, store a ledger of IPFS CIDs associated with the wallet address of the publisher (owner), require staking governance tokens to submit a proposal, allow members to propose require that staked governance tokens be slashed when a proposal does not pass, use a non-transferable ERC-1155 token as the membership NFT, mint a transferable ERC-721 NFT to the account that publishes a dataset, update the ledger of IPFS CIDs to reflect the new owners address instead of the publisher if an ERC-721 is transferred between wallets.”

Response:

“Here is a set of Solidity contracts that should meet your requirements:

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract GovernanceToken is ERC20 {
    constructor(string memory name, string memory symbol) ERC20(name, symbol) {}

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }
}

contract MembershipNFT is ERC1155 {
    constructor(string memory uri) ERC1155(uri) {}

    function mint(address to, uint256 id, uint256 amount, bytes memory data) public onlyOwner {
        _mint(to, id, amount, data);
    }
}

contract DatasetNFT is ERC721 {
    constructor(string memory name, string memory symbol) ERC721(name, symbol) {}

    function mint(address to, uint256 tokenId) public onlyOwner {
        _mint(to, tokenId);
    }
}

contract DAO is Ownable {
    GovernanceToken public governanceToken;
    MembershipNFT public membershipNFT;
    DatasetNFT public datasetNFT;

    uint256 public proposalStake;
    uint256 public proposalSlash;

    struct Proposal {
        address proposer;
        string description;
        uint256 stakedAmount;
        bool passed;
        bool executed;
    }

    Proposal[] public proposals;

    mapping(address => string[]) public datasets;

    constructor(
        string memory governanceTokenName,
        string memory governanceTokenSymbol,
        string memory membershipNFTUri,
        string memory datasetNFTName,
        string memory datasetNFTSymbol,
        uint256 _proposalStake,
        uint256 _proposalSlash
    ) {
        governanceToken = new GovernanceToken(governanceTokenName, governanceTokenSymbol);
        membershipNFT = new MembershipNFT(membershipNFTUri);
        datasetNFT = new DatasetNFT(datasetNFTName, datasetNFTSymbol);
        proposalStake = _proposalStake;
        proposalSlash = _proposalSlash;
    }

    function join(string memory datasetCID, uint256 datasetSize) public {
        datasets[msg.sender].push(datasetCID);
        governanceToken.mint(msg.sender, datasetSize);
        membershipNFT.mint(msg.sender, 1, 1, "");
        datasetNFT.mint(msg.sender, uint256(keccak256(abi.encodePacked(datasetCID))));
    }

    function propose(string memory description) public {
        require(governanceToken.balanceOf(msg.sender) >= proposalStake, "Insufficient governance tokens to stake");
        governanceToken.transferFrom(msg.sender, address(this), proposalStake);
        proposals.push(Proposal(msg.sender, description, proposalStake, false, false));
    }

    function vote(uint256 proposalId, bool support) public {
        require(membershipNFT.balanceOf(msg.sender, 1) > 0, "Only members can vote");
        Proposal storage proposal = proposals[proposalId];
        require(!proposal.executed, "Proposal already executed");

        if (support) {
            proposal.stakedAmount += governanceToken.balanceOf(msg.sender);
            if (proposal.stakedAmount >= governanceToken.totalSupply() / 2) {
                proposal.passed = true;
                proposal.executed = true;
                governanceToken.transfer(proposal.proposer, proposal.stakedAmount);
            }
        } else {
            if (proposal.stakedAmount >= proposalSlash) {
                proposal.stakedAmount -= proposalSlash;
                governanceToken.burn(proposalSlash);
            } else {
                governanceToken.burn(proposal.stakedAmount);
                proposal.stakedAmount = 0;
            }
            if (proposal.stakedAmount == 0) {
                proposal.executed = true;
            }
        }
    }

    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4) {
        require(msg.sender == address(datasetNFT), "Only accept Dataset NFTs");

        for (uint i = 0; i < datasets[from].length; i++) {
            if (uint256(keccak256(abi.encodePacked(datasets[from][i]))) == tokenId) {
                datasets[operator].push(datasets[from][i]);
                delete datasets[from][i];
                break;
            }
        }

        return this.onERC721Received.selector;
    }
}

This set of contracts includes a GovernanceToken contract that implements a non-transferable ERC-20 token, a MembershipNFT contract that implements a non-transferable ERC-1155 token, and a DatasetNFT contract that implements a transferable ERC-721 token. The DAO contract uses these tokens to implement the desired functionality.

A user can join the DAO by calling the join function and providing the CID of the dataset they want to upload to IPFS and the size of the dataset. This will mint governance tokens to the user proportionally to the size of the dataset, mint a membership NFT to the user, and mint a dataset NFT to the user. The CID of the dataset is also stored in a ledger associated with the user's wallet address.

A user can submit a proposal by calling the propose function and providing a description of the proposal. This requires staking governance tokens, which will be slashed if the proposal does not pass.

Members of the DAO can vote on proposals by calling the vote function and providing the ID of the proposal they want to vote on and whether they support or oppose it. Votes are weighted by the number of governance tokens held by the voter. If a proposal receives support from more than half of all governance tokens, it passes and the staked governance tokens are returned to the proposer. Otherwise, if a proposal does not pass, staked governance tokens are slashed.

The DAO contract also implements the onERC721Received function to handle transfers of dataset NFTs between wallets. When a dataset NFT is transferred, the ledger of IPFS CIDs is updated to reflect the new owner's address instead of the publisher's address.

I hope this helps! Let me know if you have any questions or need further assistance.”


Enable Withdrawals

Untitled

Untitled

Prompt: “Add a way to distribute profits”

Response: