// SPDX-License-Identifier: Unlicense pragma solidity ^0.8.24; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "./ReputationHolder.sol"; struct Stake { bool inFavor; uint256 amount; address sender; } struct ValidationPool { mapping(uint => Stake) stakes; uint stakeCount; uint duration; uint endTime; bool resolved; bool outcome; } struct StakeData { uint poolIndex; bool inFavor; } /// This contract must manage validation pools and reputation, /// because otherwise there's no way to enforce appropriate permissions on /// transfer of value between reputation NFTs. contract DAO is ERC721("Reputation", "REP"), ReputationHolder { mapping(uint256 tokenId => uint256) tokenValues; uint256 nextTokenId; uint256 totalValue; mapping(uint => ValidationPool) validationPools; uint validationPoolCount; // ufixed8x1 constant mintingRatio = 1; // ufixed8x1 constant quorum = 0; // ufixed8x1 constant stakeForAuthor = 0.5; // ufixed8x1 constant winningRatio = 0.5; // TODO: Make parameters adjustable // TODO: Add forum parameters event ValidationPoolInitiated(uint poolIndex); /// Inspect the value of a given reputation NFT function valueOf(uint256 tokenId) public view returns (uint256 value) { value = tokenValues[tokenId]; } /// Confirm ownership of a token and return its value. /// This should be used when receiving an NFT transfer, because otherwise /// someone could send any NFT with a tokenId matching one of ours. function verifiedValueOf( address owner, uint256 tokenId ) public view returns (uint256 value) { require(ownerOf(tokenId) == owner); value = valueOf(tokenId); } /// Internal function to mint a new reputation NFT function mint(uint256 value) internal returns (uint256 tokenId) { // Generate a new (sequential) ID for the token tokenId = nextTokenId++; // Mint the token, initially to be owned by the current contract. _mint(address(this), tokenId); tokenValues[tokenId] = value; // Keep track of total value minted // TODO: More sophisticated logic can compute total _available_, _active_ reputation totalValue += value; } /// Internal function to transfer value from one reputation token to another function transferValueFrom( uint256 fromTokenId, uint256 toTokenId, uint256 amount ) internal { require(amount >= 0); require(valueOf(fromTokenId) >= amount); tokenValues[fromTokenId] -= amount; tokenValues[toTokenId] += amount; } /// Accept fee to initiate a validation pool function initiateValidationPool(uint duration) public payable { uint poolIndex = validationPoolCount++; ValidationPool storage pool = validationPools[poolIndex]; pool.duration = duration; pool.endTime = block.timestamp + duration; // Because we need to stake part of the mited value for the pool an part against, // we mint two new tokens. // Here we assume a minting ratio of 1, and a stakeForAuthor ratio of 0.5 // Implementing this with adjustable parameters will require more advanced fixed point math. // TODO: Make minting ratio an adjustable parameter // TODO: Make stakeForAuthor an adjustable parameter uint256 tokenIdFor = mint(msg.value / 2); uint256 tokenIdAgainst = mint(msg.value / 2); stake(pool, address(this), true, tokenIdFor); stake(pool, address(this), false, tokenIdAgainst); emit ValidationPoolInitiated(poolIndex); } /// Internal function to register a stake for/against a validation pool function stake( ValidationPool storage pool, address sender, bool inFavor, uint256 tokenId ) internal { require(block.timestamp < pool.endTime); Stake storage _stake = pool.stakes[pool.stakeCount++]; _stake.sender = sender; _stake.inFavor = inFavor; _stake.amount = verifiedValueOf(sender, tokenId); } /// Accept reputation stakes toward a validation pool function onERC721Received( address, address from, uint256 tokenId, bytes calldata data ) public override returns (bytes4) { // `data` needs to encode the target validation pool, and the for/again boolean StakeData memory stakeParameters = abi.decode(data, (StakeData)); ValidationPool storage pool = validationPools[ stakeParameters.poolIndex ]; stake(pool, from, stakeParameters.inFavor, tokenId); return super.onERC721Received.selector; } /// Evaluate outcome of a validation pool function evaluateOutcome(uint poolIndex) public returns (bool outcome) { ValidationPool storage pool = validationPools[poolIndex]; require(block.timestamp >= pool.endTime); require(pool.resolved == false); uint256 amountFor; uint256 amountAgainst; Stake memory _stake; for (uint i = 0; i < pool.stakeCount; i++) { _stake = pool.stakes[i]; if (_stake.inFavor) { amountFor += _stake.amount; } else { amountAgainst += _stake.amount; } } // Here we assume a quorum of 0 // TODO: Make quorum an adjustable parameter // A tie is resolved in favor of the validation pool. // This is especially important so that the DAO's first pool can pass, // when no reputation has yet been minted. outcome = amountFor >= amountAgainst; pool.resolved = true; // Distribute reputation // Distribute fee } }