├── .gitignore ├── README.md ├── contracts ├── DaoToken.sol ├── FundDao.sol ├── Governor.sol ├── IDaoToken.sol ├── Ownable.sol ├── TransferHelper.sol └── mock │ └── USDC.sol ├── demo ├── .gitignore ├── README.md ├── babel.config.js ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ └── Demo.vue │ └── main.js └── yarn.lock ├── deployments └── abi │ ├── DaoToken.json │ ├── ERC20.json │ ├── FundDao.json │ ├── Governor.json │ ├── IDaoToken.json │ ├── IERC20.json │ ├── IERC20Metadata.json │ ├── IFundDao.json │ ├── Ownable.json │ └── USDC.json ├── hardhat.config.js ├── mock └── transfer.js ├── package.json ├── scripts ├── 00_deploy_mock_usdc.js ├── 01_deploy_dao.js └── artifact_log.js ├── start_hardhat.sh └── 分享.md /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | artifacts/ 3 | cache/ 4 | node_modules/ 5 | deployments/31337/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tutorial-DAO 2 | 3 | 4 | FundDao: Hold & Manage Fund, The Miner of DaoToken. 5 | DaoToken: A ERC20 Token for Governor. 6 | Governor: The Owner of FundDao, Control the FundDao. 7 | 8 | ## Deploy Contract 9 | 1. create .env file with content: 10 | 11 | ``` 12 | MNEMONIC="your mnemonic" 13 | ``` 14 | 15 | 2. deploy mock usdc (option): 16 | 17 | ``` 18 | npx hardhat run scripts/00_deploy_mock_usdc.js --network mumbai 19 | ``` 20 | 21 | if deploy on local hardhat node , run `start_hardhat.sh` first, then use `--network dev` . 22 | 23 | 3. deploy dao contracts: 24 | 25 | ``` 26 | npx hardhat run scripts/01_deploy_dao.js --network mumbai 27 | ``` 28 | 29 | ## Run Demo 30 | 1. cd demo & npm install 31 | 2. npm run serve 32 | 33 | 34 | ## Read More 35 | 36 | 1. Solidity Docs: https://learnblockchain.cn/docs/solidity/ 37 | 2. Ethers.js Docs: https://learnblockchain.cn/docs/ethers.js/ 38 | 3. Hardhat: 39 | * https://learnblockchain.cn/docs/hardhat/tutorial/ 40 | * https://learnblockchain.cn/docs/hardhat/getting-started/ 41 | 42 | 43 | -------------------------------------------------------------------------------- /contracts/DaoToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0; 3 | 4 | interface IFundDao { 5 | function initDaoToken() external; 6 | } 7 | 8 | contract DaoToken { 9 | string public constant name = "DaoToken For GOV"; 10 | 11 | string public constant symbol = "DaoToken"; 12 | 13 | uint8 public constant decimals = 18; 14 | 15 | uint public totalSupply; 16 | 17 | address public minter; 18 | 19 | // Allowance amounts on behalf of others 20 | mapping (address => mapping (address => uint96)) internal allowances; 21 | 22 | // Official record of token balances for each account 23 | mapping (address => uint96) internal balances; 24 | 25 | /// @notice A record of each accounts delegate 26 | mapping (address => address) public delegates; 27 | 28 | /// @notice A checkpoint for marking number of votes from a given block 29 | struct Checkpoint { 30 | uint32 fromBlock; 31 | uint96 votes; 32 | } 33 | 34 | /// @notice A record of votes checkpoints for each account, by index 35 | mapping (address => mapping (uint32 => Checkpoint)) public checkpoints; 36 | 37 | /// @notice The number of checkpoints for each account 38 | mapping (address => uint32) public numCheckpoints; 39 | 40 | bytes32 public immutable domainSeparator; 41 | 42 | /// @notice The EIP-712 typehash for the contract's domain 43 | bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); 44 | 45 | /// @notice The EIP-712 typehash for the delegation struct used by the contract 46 | bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); 47 | 48 | /// @notice A record of states for signing / validating signatures 49 | mapping (address => uint) public nonces; 50 | 51 | /// @notice An event thats emitted when an account changes its delegate 52 | event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); 53 | 54 | /// @notice An event thats emitted when a delegate account's vote balance changes 55 | event DelegateVotesChanged(address indexed delegate, uint previousBalance, uint newBalance); 56 | 57 | /// @notice The standard EIP-20 transfer event 58 | event Transfer(address indexed from, address indexed to, uint256 amount); 59 | 60 | /// @notice The standard EIP-20 approval event 61 | event Approval(address indexed owner, address indexed spender, uint256 amount); 62 | 63 | constructor() { 64 | minter = msg.sender; 65 | domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), block.chainid, address(this))); 66 | } 67 | 68 | function transferMinter(address newMinter) public { 69 | require(msg.sender == minter, "no permission"); 70 | minter = newMinter; 71 | } 72 | 73 | function mint(address account, uint96 amount) public { 74 | require(msg.sender == minter, "no permission"); 75 | _mint(account, amount); 76 | } 77 | 78 | function burn(address account, uint96 amount) public { 79 | require(msg.sender == minter, "no permission"); 80 | _burn(account, amount); 81 | } 82 | 83 | /** 84 | * @notice Get the number of tokens `spender` is approved to spend on behalf of `account` 85 | * @param account The address of the account holding the funds 86 | * @param spender The address of the account spending the funds 87 | * @return The number of tokens approved 88 | */ 89 | function allowance(address account, address spender) external view returns (uint) { 90 | return allowances[account][spender]; 91 | } 92 | 93 | /** 94 | * @notice Approve `spender` to transfer up to `amount` from `src` 95 | * @dev This will overwrite the approval amount for `spender` 96 | * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) 97 | * @param spender The address of the account which may transfer tokens 98 | * @param rawAmount The number of tokens that are approved (2^256-1 means infinite) 99 | * @return Whether or not the approval succeeded 100 | */ 101 | function approve(address spender, uint rawAmount) external returns (bool) { 102 | uint96 amount; 103 | if (rawAmount == type(uint).max) { 104 | amount = type(uint96).max; 105 | } else { 106 | amount = safe96(rawAmount, "approve: amount exceeds 96 bits"); 107 | } 108 | 109 | allowances[msg.sender][spender] = amount; 110 | 111 | emit Approval(msg.sender, spender, amount); 112 | return true; 113 | } 114 | 115 | /** 116 | * @notice Get the number of tokens held by the `account` 117 | * @param account The address of the account to get the balance of 118 | * @return The number of tokens held 119 | */ 120 | function balanceOf(address account) external view returns (uint) { 121 | return balances[account]; 122 | } 123 | 124 | /** 125 | * @notice Transfer `amount` tokens from `msg.sender` to `dst` 126 | * @param dst The address of the destination account 127 | * @param rawAmount The number of tokens to transfer 128 | * @return Whether or not the transfer succeeded 129 | */ 130 | function transfer(address dst, uint rawAmount) external returns (bool) { 131 | uint96 amount = safe96(rawAmount, "transfer: amount exceeds 96 bits"); 132 | _transferTokens(msg.sender, dst, amount); 133 | return true; 134 | } 135 | 136 | /** 137 | * @notice Transfer `amount` tokens from `src` to `dst` 138 | * @param src The address of the source account 139 | * @param dst The address of the destination account 140 | * @param rawAmount The number of tokens to transfer 141 | * @return Whether or not the transfer succeeded 142 | */ 143 | function transferFrom(address src, address dst, uint rawAmount) external returns (bool) { 144 | address spender = msg.sender; 145 | uint96 spenderAllowance = allowances[src][spender]; 146 | uint96 amount = safe96(rawAmount, "approve: amount exceeds 96 bits"); 147 | 148 | if (spender != src && spenderAllowance != type(uint96).max) { 149 | uint96 newAllowance = spenderAllowance - amount; 150 | allowances[src][spender] = newAllowance; 151 | 152 | emit Approval(src, spender, newAllowance); 153 | } 154 | 155 | _transferTokens(src, dst, amount); 156 | return true; 157 | } 158 | 159 | /** 160 | * @notice Delegate votes from `msg.sender` to `delegatee` 161 | * @param delegatee The address to delegate votes to 162 | */ 163 | function delegate(address delegatee) public { 164 | return _delegate(msg.sender, delegatee); 165 | } 166 | 167 | /** 168 | * @notice Delegates votes from signatory to `delegatee` 169 | * @param delegatee The address to delegate votes to 170 | * @param nonce The contract state required to match the signature 171 | * @param expiry The time at which to expire the signature 172 | * @param v The recovery byte of the signature 173 | * @param r Half of the ECDSA signature pair 174 | * @param s Half of the ECDSA signature pair 175 | */ 176 | function delegateBySig(address delegatee, uint nonce, uint expiry, uint8 v, bytes32 r, bytes32 s) public { 177 | bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry)); 178 | bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); 179 | address signatory = ecrecover(digest, v, r, s); 180 | require(signatory != address(0), "delegateBySig: invalid signature"); 181 | require(nonce == nonces[signatory]++, "delegateBySig: invalid nonce"); 182 | require(block.timestamp <= expiry, "delegateBySig: signature expired"); 183 | return _delegate(signatory, delegatee); 184 | } 185 | 186 | /** 187 | * @notice Gets the current votes balance for `account` 188 | * @param account The address to get votes balance 189 | * @return The number of current votes for `account` 190 | */ 191 | function getCurrentVotes(address account) external view returns (uint96) { 192 | uint32 nCheckpoints = numCheckpoints[account]; 193 | return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0; 194 | } 195 | 196 | /** 197 | * @notice Determine the prior number of votes for an account as of a block number 198 | * @dev Block number must be a finalized block or else this function will revert to prevent misinformation. 199 | * @param account The address of the account to check 200 | * @param blockNumber The block number to get the vote balance at 201 | * @return The number of votes the account had as of the given block 202 | */ 203 | function getPriorVotes(address account, uint blockNumber) public view returns (uint96) { 204 | require(blockNumber < block.number, "getPriorVotes: not yet determined"); 205 | 206 | uint32 nCheckpoints = numCheckpoints[account]; 207 | if (nCheckpoints == 0) { 208 | return 0; 209 | } 210 | 211 | // First check most recent balance 212 | if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) { 213 | return checkpoints[account][nCheckpoints - 1].votes; 214 | } 215 | 216 | // Next check implicit zero balance 217 | if (checkpoints[account][0].fromBlock > blockNumber) { 218 | return 0; 219 | } 220 | 221 | uint32 lower = 0; 222 | uint32 upper = nCheckpoints - 1; 223 | while (upper > lower) { 224 | uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow 225 | Checkpoint memory cp = checkpoints[account][center]; 226 | if (cp.fromBlock == blockNumber) { 227 | return cp.votes; 228 | } else if (cp.fromBlock < blockNumber) { 229 | lower = center; 230 | } else { 231 | upper = center - 1; 232 | } 233 | } 234 | return checkpoints[account][lower].votes; 235 | } 236 | 237 | function _delegate(address delegator, address delegatee) internal { 238 | address currentDelegate = delegates[delegator]; 239 | uint96 delegatorBalance = balances[delegator]; 240 | delegates[delegator] = delegatee; 241 | 242 | emit DelegateChanged(delegator, currentDelegate, delegatee); 243 | 244 | _moveDelegates(currentDelegate, delegatee, delegatorBalance); 245 | } 246 | 247 | function _transferTokens(address src, address dst, uint96 amount) internal { 248 | require(src != address(0), "_transferTokens: cannot transfer from the zero address"); 249 | require(dst != address(0), "_transferTokens: cannot transfer to the zero address"); 250 | require(balances[src] == amount, " must transfer all"); 251 | balances[src] = balances[src] - amount; 252 | balances[dst] = balances[dst] + amount; 253 | emit Transfer(src, dst, amount); 254 | 255 | if(delegates[dst] == address(0)) { 256 | delegates[dst] = dst; 257 | } 258 | 259 | _moveDelegates(delegates[src], delegates[dst], amount); 260 | } 261 | 262 | function _mint(address account, uint96 amount) internal { 263 | require(account != address(0), "ERC20: mint to the zero address"); 264 | if (amount == 0) { 265 | return ; 266 | } 267 | 268 | totalSupply = totalSupply + amount; 269 | 270 | balances[account] = balances[account] + amount; 271 | emit Transfer(address(0), account, amount); 272 | 273 | if(delegates[account] == address(0)) { 274 | delegates[account] = account; 275 | } 276 | _moveDelegates(address(0), delegates[account], amount); 277 | 278 | } 279 | 280 | 281 | function _burn(address account, uint96 amount) internal { 282 | require(account != address(0), "ERC20: to the zero address"); 283 | if (amount == 0) { 284 | return ; 285 | } 286 | 287 | require(totalSupply >= amount, "Sub Error"); 288 | totalSupply = totalSupply - amount; 289 | 290 | balances[account] = balances[account] - amount; 291 | emit Transfer(account, address(0), amount); 292 | 293 | _moveDelegates(delegates[account], address(0), amount); 294 | } 295 | 296 | function _moveDelegates(address srcRep, address dstRep, uint96 amount) internal { 297 | if (srcRep != dstRep && amount > 0) { 298 | if (srcRep != address(0)) { 299 | uint32 srcRepNum = numCheckpoints[srcRep]; 300 | uint96 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0; 301 | uint96 srcRepNew = srcRepOld - amount; 302 | _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew); 303 | } 304 | 305 | if (dstRep != address(0)) { 306 | uint32 dstRepNum = numCheckpoints[dstRep]; 307 | uint96 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0; 308 | uint96 dstRepNew = dstRepOld + amount; 309 | _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew); 310 | } 311 | } 312 | } 313 | 314 | function _writeCheckpoint(address delegatee, uint32 nCheckpoints, uint96 oldVotes, uint96 newVotes) internal { 315 | uint32 blockNumber = safe32(block.number, "_writeCheckpoint: block number exceeds 32 bits"); 316 | 317 | if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) { 318 | checkpoints[delegatee][nCheckpoints - 1].votes = newVotes; 319 | } else { 320 | checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes); 321 | numCheckpoints[delegatee] = nCheckpoints + 1; 322 | } 323 | 324 | emit DelegateVotesChanged(delegatee, oldVotes, newVotes); 325 | } 326 | 327 | function safe32(uint n, string memory errorMessage) internal pure returns (uint32) { 328 | require(n < 2**32, errorMessage); 329 | return uint32(n); 330 | } 331 | 332 | function safe96(uint n, string memory errorMessage) internal pure returns (uint96) { 333 | require(n < 2**96, errorMessage); 334 | return uint96(n); 335 | } 336 | 337 | } 338 | -------------------------------------------------------------------------------- /contracts/FundDao.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0; 3 | 4 | import "./Ownable.sol"; 5 | import "./IDaoToken.sol"; 6 | import "./TransferHelper.sol"; 7 | 8 | interface IERC20 { 9 | function decimals() external view returns (uint8); 10 | function balanceOf(address owner) external view returns (uint); 11 | } 12 | 13 | // transfer Owner to Gover 14 | contract FundDao is Ownable { 15 | 16 | address public immutable usdt; 17 | 18 | IDaoToken public immutable daotoken; 19 | 20 | event Withdrawal(address indexed user, uint256 amount); 21 | event Join(address indexed user, uint256 amount); 22 | event Quit(address indexed user, uint256 amount); 23 | 24 | constructor(address _usdt, IDaoToken _daotoken) { 25 | usdt = _usdt; 26 | daotoken = _daotoken; 27 | } 28 | 29 | function join(uint amount) public { 30 | TransferHelper.safeTransferFrom(usdt, msg.sender, address(this), amount); 31 | daotoken.mint(msg.sender, uint96(amount)); 32 | } 33 | 34 | function appayFund(address receiver, uint256 amount) external onlyOwner { 35 | emit Withdrawal(receiver, amount); 36 | TransferHelper.safeTransfer(usdt, receiver, amount); 37 | } 38 | 39 | function quit() external { 40 | address user = msg.sender; 41 | uint share = daotoken.balanceOf(user); 42 | require(share >= 0, "Not Member"); 43 | 44 | uint balance = IERC20(usdt).balanceOf(address(this)); 45 | 46 | uint amount = share * balance / daotoken.totalSupply() ; 47 | emit Quit(user, amount); 48 | daotoken.burn(user, uint96(share)); 49 | 50 | TransferHelper.safeTransfer(usdt, user, amount); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /contracts/Governor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0; 3 | 4 | import "./IDaoToken.sol"; 5 | 6 | contract Governor { 7 | /// @notice The name of this contract 8 | string public constant name = "FundDao Governor"; 9 | 10 | bytes32 public immutable domainSeparator; 11 | 12 | /// @dev The maximum number of actions that can be included in a proposal 13 | uint constant MAX_ACTIONS = 10; 14 | 15 | uint constant VOTE_PERIOD = 10; 16 | 17 | /// @dev The delay before voting on a proposal may take place, once proposed 18 | uint constant VOTING_DELAY = 0; 19 | 20 | /// @dev The number of votes required in order for a voter to become a proposer 21 | uint constant PROPOSAL_THRESHOLD = 1; 22 | 23 | /// @notice The address of the FundDao governance token 24 | IDaoToken public token; 25 | 26 | /// @notice The total number of proposals 27 | uint public proposalCount; 28 | 29 | struct Proposal { 30 | uint id; 31 | 32 | address proposer; 33 | 34 | // The timestamp that the proposal execution 35 | uint eta; 36 | 37 | // the ordered list of target addresses for calls to be made 38 | address[] targets; 39 | 40 | // The ordered list of values (i.e. msg.value) to be passed to the calls to be made 41 | uint[] values; 42 | 43 | // The ordered list of function signatures to be called 44 | string[] signatures; 45 | 46 | // The ordered list of calldata to be passed to each call 47 | bytes[] calldatas; 48 | 49 | // The block at which voting begins: holders must delegate their votes prior to this block 50 | uint startBlock; 51 | 52 | // The block at which voting ends: votes must be cast prior to this timestap 53 | uint endBlock; 54 | 55 | // Current number of votes in favor of this proposal 56 | uint forVotes; 57 | 58 | // Current number of votes in opposition to this proposal 59 | uint againstVotes; 60 | 61 | // Flag marking whether the proposal has been executed 62 | bool executed; 63 | 64 | string desc; 65 | 66 | // Receipts of ballots for the entire set of voters 67 | mapping (address => Receipt) receipts; 68 | } 69 | 70 | // Ballot receipt record for a voter 71 | struct Receipt { 72 | // Whether or not a vote has been cast 73 | bool hasVoted; 74 | 75 | bool support; 76 | 77 | uint96 votes; 78 | } 79 | 80 | // Possible states that a proposal may be in 81 | enum ProposalState { 82 | Pending, 83 | Active, 84 | Defeated, 85 | Succeeded, 86 | Expired, 87 | Executed 88 | } 89 | 90 | /// @notice The official record of all proposals ever proposed 91 | mapping (uint => Proposal) public proposals; 92 | 93 | /// @notice The latest proposal for each proposer 94 | mapping (address => uint) public latestProposalIds; 95 | 96 | /// @notice The EIP-712 typehash for the contract's domain 97 | bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); 98 | 99 | /// @notice The EIP-712 typehash for the ballot struct used by the contract 100 | bytes32 public constant BALLOT_TYPEHASH = keccak256("Ballot(uint256 proposalId,bool support)"); 101 | 102 | /// @notice An event emitted when a new proposal is created 103 | event ProposalCreated(uint id, address proposer, address[] targets, uint[] values, string[] signatures, bytes[] calldatas, uint startBlock, uint endBlock, string description); 104 | 105 | /// @notice An event emitted when a vote has been cast on a proposal 106 | event VoteCast(address voter, uint proposalId, bool support, uint votes); 107 | 108 | /// @notice An event emitted when a proposal has been executed 109 | event ProposalExecuted(uint id, uint eta); 110 | 111 | constructor(address _token) { 112 | token = IDaoToken(_token); 113 | domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), block.chainid, address(this))); 114 | } 115 | 116 | function propose(address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description) public returns (uint) { 117 | require(token.getPriorVotes(msg.sender, block.number - 1) >= PROPOSAL_THRESHOLD, "Governor::propose: proposer votes below proposal threshold"); 118 | require(targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, "Governor::propose: proposal function information arity mismatch"); 119 | require(targets.length != 0, "Governor::propose: must provide actions"); 120 | require(targets.length <= MAX_ACTIONS, "Governor::propose: too many actions"); 121 | 122 | uint latestProposalId = latestProposalIds[msg.sender]; 123 | if (latestProposalId != 0) { 124 | ProposalState proposersLatestProposalState = state(latestProposalId); 125 | require(proposersLatestProposalState != ProposalState.Active, "Governor::propose: one live proposal per proposer, found an already active proposal"); 126 | require(proposersLatestProposalState != ProposalState.Pending, "Governor::propose: one live proposal per proposer, found an already pending proposal"); 127 | } 128 | 129 | uint startBlock = block.number + VOTING_DELAY ; 130 | uint endBlock = block.number + VOTE_PERIOD; 131 | 132 | proposalCount++; 133 | Proposal storage newProposal = proposals[proposalCount]; 134 | newProposal.id = proposalCount; 135 | newProposal.proposer = msg.sender; 136 | newProposal.eta = 0; 137 | newProposal.targets = targets; 138 | newProposal.values = values; 139 | newProposal.signatures = signatures; 140 | newProposal.calldatas = calldatas; 141 | newProposal.startBlock = startBlock; 142 | newProposal.endBlock = endBlock; 143 | newProposal.forVotes = 0; 144 | newProposal.againstVotes = 0; 145 | newProposal.executed = false; 146 | newProposal.desc = description; 147 | 148 | latestProposalIds[newProposal.proposer] = newProposal.id; 149 | 150 | emit ProposalCreated(newProposal.id, msg.sender, targets, values, signatures, calldatas, startBlock, endBlock, description); 151 | return newProposal.id; 152 | } 153 | 154 | function execute(uint proposalId) public payable { 155 | require(state(proposalId) == ProposalState.Succeeded, "Governor::execute: only Succeeded proposal can be executed "); 156 | Proposal storage proposal = proposals[proposalId]; 157 | proposal.eta = block.timestamp; 158 | proposal.executed = true; 159 | for (uint i = 0; i < proposal.targets.length; i++) { 160 | _executeTransaction(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i]); 161 | } 162 | emit ProposalExecuted(proposalId, block.timestamp); 163 | } 164 | 165 | function _executeTransaction(address target, uint value, string memory signature, bytes memory data) internal returns (bytes memory) { 166 | bytes memory callData; 167 | if (bytes(signature).length == 0) { 168 | callData = data; 169 | } else { 170 | callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data); 171 | } 172 | 173 | // solium-disable-next-line security/no-call-value 174 | (bool success, bytes memory returnData) = target.call{value: value}(callData); 175 | require(success, "_executeTransaction: Transaction execution reverted."); 176 | return returnData; 177 | } 178 | 179 | function getActions(uint proposalId) public view returns (address[] memory targets, uint[] memory values, string[] memory signatures, bytes[] memory calldatas) { 180 | Proposal storage p = proposals[proposalId]; 181 | return (p.targets, p.values, p.signatures, p.calldatas); 182 | } 183 | 184 | function getReceipt(uint proposalId, address voter) public view returns (Receipt memory) { 185 | return proposals[proposalId].receipts[voter]; 186 | } 187 | 188 | function state(uint proposalId) public view returns (ProposalState) { 189 | require(proposalCount >= proposalId && proposalId > 0, "Governor::state: invalid proposal id"); 190 | Proposal storage proposal = proposals[proposalId]; 191 | if (block.number <= proposal.startBlock) { 192 | return ProposalState.Pending; 193 | } else if (block.number <= proposal.endBlock) { 194 | return ProposalState.Active; 195 | } else if (proposal.forVotes <= proposal.againstVotes) { 196 | return ProposalState.Defeated; 197 | } else if (proposal.eta == 0) { 198 | return ProposalState.Succeeded; 199 | } else if (proposal.executed) { 200 | return ProposalState.Executed; 201 | } else { // if (block.number > proposal.endBlock) 202 | return ProposalState.Expired; 203 | } 204 | } 205 | 206 | function castVote(uint proposalId, bool support) public { 207 | return _castVote(msg.sender, proposalId, support); 208 | } 209 | 210 | function castVoteBySig(uint proposalId, bool support, uint8 v, bytes32 r, bytes32 s) public { 211 | 212 | bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support)); 213 | bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); 214 | address signatory = ecrecover(digest, v, r, s); 215 | require(signatory != address(0), "Governor::castVoteBySig: invalid signature"); 216 | return _castVote(signatory, proposalId, support); 217 | } 218 | 219 | function _castVote(address voter, uint proposalId, bool support) internal { 220 | require(state(proposalId) == ProposalState.Active, "Governor::_castVote: voting is closed"); 221 | Proposal storage proposal = proposals[proposalId]; 222 | Receipt storage receipt = proposal.receipts[voter]; 223 | require(receipt.hasVoted == false, "Governor::_castVote: voter already voted"); 224 | uint96 votes = token.getPriorVotes(voter, proposal.startBlock); 225 | 226 | if (support) { 227 | proposal.forVotes = proposal.forVotes + votes; 228 | } else { 229 | proposal.againstVotes = proposal.againstVotes + votes; 230 | } 231 | 232 | receipt.hasVoted = true; 233 | receipt.support = support; 234 | receipt.votes = votes; 235 | 236 | emit VoteCast(voter, proposalId, support, votes); 237 | } 238 | 239 | } 240 | 241 | 242 | 243 | -------------------------------------------------------------------------------- /contracts/IDaoToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0; 3 | 4 | interface IDaoToken { 5 | 6 | function totalSupply() external view returns (uint); 7 | function balanceOf(address owner) external view returns (uint); 8 | function allowance(address owner, address spender) external view returns (uint); 9 | 10 | function approve(address spender, uint value) external returns (bool); 11 | function transfer(address to, uint value) external returns (bool); 12 | function transferFrom(address from, address to, uint value) external returns (bool); 13 | 14 | function mint(address account, uint96 amount) external ; 15 | function burn(address account, uint96 amount) external ; 16 | 17 | function getPriorVotes(address account, uint blockNumber) external view returns (uint96); 18 | } 19 | -------------------------------------------------------------------------------- /contracts/Ownable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0; 3 | 4 | /** 5 | * @dev Contract module which provides a basic access control mechanism, where 6 | * there is an account (an owner) that can be granted exclusive access to 7 | * specific functions. 8 | * 9 | * This module is used through inheritance. It will make available the modifier 10 | * `onlyOwner`, which can be applied to your functions to restrict their use to 11 | * the owner. 12 | */ 13 | abstract contract Ownable { 14 | address private _owner; 15 | 16 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 17 | 18 | /** 19 | * @dev Initializes the contract setting the deployer as the initial owner. 20 | */ 21 | constructor () { 22 | _owner = msg.sender; 23 | emit OwnershipTransferred(address(0), _owner); 24 | } 25 | 26 | /** 27 | * @dev Returns the address of the current owner. 28 | */ 29 | function owner() public view returns (address) { 30 | return _owner; 31 | } 32 | 33 | /** 34 | * @dev Throws if called by any account other than the owner. 35 | */ 36 | modifier onlyOwner() { 37 | require(isOwner(), "Ownable: caller is not the owner"); 38 | _; 39 | } 40 | 41 | /** 42 | * @dev Returns true if the caller is the current owner. 43 | */ 44 | function isOwner() public view returns (bool) { 45 | return msg.sender == _owner; 46 | } 47 | 48 | /** 49 | * @dev Leaves the contract without owner. It will not be possible to call 50 | * `onlyOwner` functions anymore. Can only be called by the current owner. 51 | * 52 | * NOTE: Renouncing ownership will leave the contract without an owner, 53 | * thereby removing any functionality that is only available to the owner. 54 | */ 55 | function renounceOwnership() public onlyOwner { 56 | emit OwnershipTransferred(_owner, address(0)); 57 | _owner = address(0); 58 | } 59 | 60 | /** 61 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 62 | * Can only be called by the current owner. 63 | */ 64 | function transferOwnership(address newOwner) public onlyOwner { 65 | _transferOwnership(newOwner); 66 | } 67 | 68 | /** 69 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 70 | */ 71 | function _transferOwnership(address newOwner) internal { 72 | require(newOwner != address(0), "Ownable: new owner is the zero address"); 73 | emit OwnershipTransferred(_owner, newOwner); 74 | _owner = newOwner; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /contracts/TransferHelper.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity >=0.6.0; 4 | 5 | // helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false 6 | library TransferHelper { 7 | function safeApprove(address token, address to, uint value) internal { 8 | // bytes4(keccak256(bytes('approve(address,uint256)'))); 9 | (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value)); 10 | require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: APPROVE_FAILED'); 11 | } 12 | 13 | function safeTransfer(address token, address to, uint value) internal { 14 | // bytes4(keccak256(bytes('transfer(address,uint256)'))); 15 | (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value)); 16 | require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED'); 17 | } 18 | 19 | function safeTransferFrom(address token, address from, address to, uint value) internal { 20 | // bytes4(keccak256(bytes('transferFrom(address,address,uint256)'))); 21 | (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value)); 22 | require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED'); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /contracts/mock/USDC.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.8.0; 2 | 3 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 4 | 5 | contract USDC is ERC20 { 6 | constructor() ERC20("Mock-USDC", "USDC") { 7 | _mint(msg.sender, 10000000000 * 10 ** 18); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # demo 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | yarn lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /demo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FundDaoDemo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^3.6.5", 12 | "element-ui": "^2.15.6", 13 | "vue": "^2.6.11" 14 | }, 15 | "devDependencies": { 16 | "@vue/cli-plugin-babel": "~4.5.0", 17 | "@vue/cli-plugin-eslint": "~4.5.0", 18 | "@vue/cli-service": "~4.5.0", 19 | "babel-eslint": "^10.1.0", 20 | "eslint": "^6.7.2", 21 | "eslint-plugin-vue": "^6.2.2", 22 | "vue-template-compiler": "^2.6.11" 23 | }, 24 | "eslintConfig": { 25 | "root": true, 26 | "env": { 27 | "node": true 28 | }, 29 | "extends": [ 30 | "plugin:vue/essential", 31 | "eslint:recommended" 32 | ], 33 | "parserOptions": { 34 | "parser": "babel-eslint" 35 | }, 36 | "rules": {} 37 | }, 38 | "browserslist": [ 39 | "> 1%", 40 | "last 2 versions", 41 | "not dead" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Polygon-Academy/Tutorial-DAO/a9d61dd2056bbf9390e0ff16294821995282b517/demo/public/favicon.ico -------------------------------------------------------------------------------- /demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /demo/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | 30 | -------------------------------------------------------------------------------- /demo/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Polygon-Academy/Tutorial-DAO/a9d61dd2056bbf9390e0ff16294821995282b517/demo/src/assets/logo.png -------------------------------------------------------------------------------- /demo/src/components/Demo.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 196 | 197 | 198 | 226 | -------------------------------------------------------------------------------- /demo/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | import ElementUI from 'element-ui'; 5 | import 'element-ui/lib/theme-chalk/index.css'; 6 | 7 | 8 | Vue.use(ElementUI); 9 | Vue.config.productionTip = false 10 | 11 | new Vue({ 12 | el: '#app', 13 | render: h => h(App), 14 | }).$mount('#app') 15 | -------------------------------------------------------------------------------- /deployments/abi/DaoToken.json: -------------------------------------------------------------------------------- 1 | [ 2 | "event Approval(address indexed owner, address indexed spender, uint256 amount)", 3 | "event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate)", 4 | "event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance)", 5 | "event Transfer(address indexed from, address indexed to, uint256 amount)", 6 | "function DELEGATION_TYPEHASH() view returns (bytes32)", 7 | "function DOMAIN_TYPEHASH() view returns (bytes32)", 8 | "function allowance(address account, address spender) view returns (uint256)", 9 | "function approve(address spender, uint256 rawAmount) returns (bool)", 10 | "function balanceOf(address account) view returns (uint256)", 11 | "function burn(address account, uint96 amount)", 12 | "function checkpoints(address, uint32) view returns (uint32 fromBlock, uint96 votes)", 13 | "function decimals() view returns (uint8)", 14 | "function delegate(address delegatee)", 15 | "function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s)", 16 | "function delegates(address) view returns (address)", 17 | "function domainSeparator() view returns (bytes32)", 18 | "function getCurrentVotes(address account) view returns (uint96)", 19 | "function getPriorVotes(address account, uint256 blockNumber) view returns (uint96)", 20 | "function mint(address account, uint96 amount)", 21 | "function minter() view returns (address)", 22 | "function name() view returns (string)", 23 | "function nonces(address) view returns (uint256)", 24 | "function numCheckpoints(address) view returns (uint32)", 25 | "function symbol() view returns (string)", 26 | "function totalSupply() view returns (uint256)", 27 | "function transfer(address dst, uint256 rawAmount) returns (bool)", 28 | "function transferFrom(address src, address dst, uint256 rawAmount) returns (bool)", 29 | "function transferMinter(address newMinter)" 30 | ] 31 | -------------------------------------------------------------------------------- /deployments/abi/ERC20.json: -------------------------------------------------------------------------------- 1 | [ 2 | "event Approval(address indexed owner, address indexed spender, uint256 value)", 3 | "event Transfer(address indexed from, address indexed to, uint256 value)", 4 | "function allowance(address owner, address spender) view returns (uint256)", 5 | "function approve(address spender, uint256 amount) returns (bool)", 6 | "function balanceOf(address account) view returns (uint256)", 7 | "function decimals() view returns (uint8)", 8 | "function decreaseAllowance(address spender, uint256 subtractedValue) returns (bool)", 9 | "function increaseAllowance(address spender, uint256 addedValue) returns (bool)", 10 | "function name() view returns (string)", 11 | "function symbol() view returns (string)", 12 | "function totalSupply() view returns (uint256)", 13 | "function transfer(address recipient, uint256 amount) returns (bool)", 14 | "function transferFrom(address sender, address recipient, uint256 amount) returns (bool)" 15 | ] 16 | -------------------------------------------------------------------------------- /deployments/abi/FundDao.json: -------------------------------------------------------------------------------- 1 | [ 2 | "event Join(address indexed user, uint256 amount)", 3 | "event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)", 4 | "event Quit(address indexed user, uint256 amount)", 5 | "event Withdrawal(address indexed user, uint256 amount)", 6 | "function appayFund(address receiver, uint256 amount)", 7 | "function daotoken() view returns (address)", 8 | "function isOwner() view returns (bool)", 9 | "function join(uint256 amount)", 10 | "function owner() view returns (address)", 11 | "function quit()", 12 | "function renounceOwnership()", 13 | "function transferOwnership(address newOwner)", 14 | "function usdt() view returns (address)" 15 | ] 16 | -------------------------------------------------------------------------------- /deployments/abi/Governor.json: -------------------------------------------------------------------------------- 1 | [ 2 | "event ProposalCreated(uint256 id, address proposer, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, uint256 startBlock, uint256 endBlock, string description)", 3 | "event ProposalExecuted(uint256 id, uint256 eta)", 4 | "event VoteCast(address voter, uint256 proposalId, bool support, uint256 votes)", 5 | "function BALLOT_TYPEHASH() view returns (bytes32)", 6 | "function DOMAIN_TYPEHASH() view returns (bytes32)", 7 | "function castVote(uint256 proposalId, bool support)", 8 | "function castVoteBySig(uint256 proposalId, bool support, uint8 v, bytes32 r, bytes32 s)", 9 | "function domainSeparator() view returns (bytes32)", 10 | "function execute(uint256 proposalId) payable", 11 | "function getActions(uint256 proposalId) view returns (address[] targets, uint256[] values, string[] signatures, bytes[] calldatas)", 12 | "function getReceipt(uint256 proposalId, address voter) view returns (tuple(bool hasVoted, bool support, uint96 votes) )", 13 | "function latestProposalIds(address) view returns (uint256)", 14 | "function name() view returns (string)", 15 | "function proposalCount() view returns (uint256)", 16 | "function proposals(uint256) view returns (uint256 id, address proposer, uint256 eta, uint256 startBlock, uint256 endBlock, uint256 forVotes, uint256 againstVotes, bool executed, string desc)", 17 | "function propose(address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, string description) returns (uint256)", 18 | "function state(uint256 proposalId) view returns (uint8)", 19 | "function token() view returns (address)" 20 | ] 21 | -------------------------------------------------------------------------------- /deployments/abi/IDaoToken.json: -------------------------------------------------------------------------------- 1 | [ 2 | "function allowance(address owner, address spender) view returns (uint256)", 3 | "function approve(address spender, uint256 value) returns (bool)", 4 | "function balanceOf(address owner) view returns (uint256)", 5 | "function burn(address account, uint96 amount)", 6 | "function getPriorVotes(address account, uint256 blockNumber) view returns (uint96)", 7 | "function mint(address account, uint96 amount)", 8 | "function totalSupply() view returns (uint256)", 9 | "function transfer(address to, uint256 value) returns (bool)", 10 | "function transferFrom(address from, address to, uint256 value) returns (bool)" 11 | ] 12 | -------------------------------------------------------------------------------- /deployments/abi/IERC20.json: -------------------------------------------------------------------------------- 1 | [ 2 | "function balanceOf(address owner) view returns (uint256)", 3 | "function decimals() view returns (uint8)" 4 | ] 5 | -------------------------------------------------------------------------------- /deployments/abi/IERC20Metadata.json: -------------------------------------------------------------------------------- 1 | [ 2 | "event Approval(address indexed owner, address indexed spender, uint256 value)", 3 | "event Transfer(address indexed from, address indexed to, uint256 value)", 4 | "function allowance(address owner, address spender) view returns (uint256)", 5 | "function approve(address spender, uint256 amount) returns (bool)", 6 | "function balanceOf(address account) view returns (uint256)", 7 | "function decimals() view returns (uint8)", 8 | "function name() view returns (string)", 9 | "function symbol() view returns (string)", 10 | "function totalSupply() view returns (uint256)", 11 | "function transfer(address recipient, uint256 amount) returns (bool)", 12 | "function transferFrom(address sender, address recipient, uint256 amount) returns (bool)" 13 | ] 14 | -------------------------------------------------------------------------------- /deployments/abi/IFundDao.json: -------------------------------------------------------------------------------- 1 | [ 2 | "function initDaoToken()" 3 | ] 4 | -------------------------------------------------------------------------------- /deployments/abi/Ownable.json: -------------------------------------------------------------------------------- 1 | [ 2 | "event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)", 3 | "function isOwner() view returns (bool)", 4 | "function owner() view returns (address)", 5 | "function renounceOwnership()", 6 | "function transferOwnership(address newOwner)" 7 | ] 8 | -------------------------------------------------------------------------------- /deployments/abi/USDC.json: -------------------------------------------------------------------------------- 1 | [ 2 | "event Approval(address indexed owner, address indexed spender, uint256 value)", 3 | "event Transfer(address indexed from, address indexed to, uint256 value)", 4 | "function allowance(address owner, address spender) view returns (uint256)", 5 | "function approve(address spender, uint256 amount) returns (bool)", 6 | "function balanceOf(address account) view returns (uint256)", 7 | "function decimals() view returns (uint8)", 8 | "function decreaseAllowance(address spender, uint256 subtractedValue) returns (bool)", 9 | "function increaseAllowance(address spender, uint256 addedValue) returns (bool)", 10 | "function name() view returns (string)", 11 | "function symbol() view returns (string)", 12 | "function totalSupply() view returns (uint256)", 13 | "function transfer(address recipient, uint256 amount) returns (bool)", 14 | "function transferFrom(address sender, address recipient, uint256 amount) returns (bool)" 15 | ] 16 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomiclabs/hardhat-waffle"); 2 | require('hardhat-abi-exporter'); 3 | 4 | let dotenv = require('dotenv') 5 | dotenv.config({path:"./.env"}) 6 | 7 | const mnemonic = process.env.MNEMONIC 8 | 9 | // This is a sample Hardhat task. To learn how to create your own go to 10 | // https://hardhat.org/guides/create-task.html 11 | task("accounts", "Prints the list of accounts", async () => { 12 | const accounts = await ethers.getSigners(); 13 | 14 | for (const account of accounts) { 15 | console.log(account.address); 16 | } 17 | }); 18 | 19 | // You need to export an object to set up your config 20 | // Go to https://hardhat.org/config/ to learn more 21 | 22 | /** 23 | * @type import('hardhat/config').HardhatUserConfig 24 | */ 25 | module.exports = { 26 | solidity: { 27 | version: "0.8.6", 28 | settings: { 29 | optimizer: { 30 | enabled: true 31 | } 32 | } 33 | }, 34 | 35 | abiExporter: { 36 | path: './deployments/abi', 37 | clear: true, 38 | flat: true, 39 | only: [], 40 | spacing: 2, 41 | pretty: true, 42 | }, 43 | 44 | networks: { 45 | dev: { 46 | url: "http://127.0.0.1:8545", 47 | chainId: 31337, 48 | }, 49 | 50 | mumbai: { // Polygon TestNet 51 | url: "https://rpc-mumbai.matic.today", 52 | accounts: { 53 | mnemonic: mnemonic, 54 | }, 55 | chainId: 80001 56 | }, 57 | 58 | 59 | polygon: { 60 | url: "https://polygon-rpc.com", // https://rpc-mainnet.matic.network 61 | accounts: { 62 | count: 1, 63 | initialIndex: 0, 64 | mnemonic: mnemonic, 65 | path: "m/44'/60'/0'/0", 66 | }, 67 | chainId: 5, 68 | } 69 | } 70 | }; 71 | 72 | -------------------------------------------------------------------------------- /mock/transfer.js: -------------------------------------------------------------------------------- 1 | const { ethers, network } = require("hardhat"); 2 | 3 | 4 | async function getUSDC(signer) { 5 | const chainid = network.config.chainId; 6 | const USDCAddr = require(`../deployments/${chainid}/USDC.json`); 7 | 8 | return await ethers.getContractAt("USDC", 9 | USDCAddr.address, 10 | signer); 11 | } 12 | 13 | 14 | async function main() { 15 | let [ account0, account1, account2, account3, account4, account5, account6] = await ethers.getSigners(); 16 | let usdc = await getUSDC(account0); 17 | 18 | await usdc.transfer(account1.address, ethers.utils.parseEther("1000")); 19 | await usdc.transfer(account2.address, ethers.utils.parseEther("1000")); 20 | await usdc.transfer(account3.address, ethers.utils.parseEther("1000")); 21 | await usdc.transfer(account4.address, ethers.utils.parseEther("1000")); 22 | await usdc.transfer(account5.address, ethers.utils.parseEther("1000")); 23 | await usdc.transfer(account6.address, ethers.utils.parseEther("1000")); 24 | 25 | let b = await usdc.balanceOf(account6.address) 26 | console.log("b:" + b.toString()); 27 | console.log("transfer done"); 28 | } 29 | 30 | 31 | main() 32 | .then(() => process.exit(0)) 33 | .catch(error => { 34 | console.error(error); 35 | process.exit(1); 36 | }); 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dusd", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "hardhat.config.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git@gitee.com:upchain/dusd.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@nomiclabs/hardhat-ethers": "^2.0.2", 17 | "@nomiclabs/hardhat-etherscan": "^2.1.7", 18 | "@nomiclabs/hardhat-waffle": "^2.0.1", 19 | "@openzeppelin/contracts": "^4.3.2", 20 | "dotenv": "^10.0.0", 21 | "ethereum-waffle": "^3.4.0", 22 | "hardhat": "^2.6.6" 23 | }, 24 | "dependencies": { 25 | "@nomiclabs/hardhat-waffle": "^2.0.1", 26 | "ethers": "^5.5.2", 27 | "hardhat-abi-exporter": "^2.3.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /scripts/00_deploy_mock_usdc.js: -------------------------------------------------------------------------------- 1 | const { ethers, network } = require("hardhat"); 2 | const { writeAddr } = require('./artifact_log.js'); 3 | 4 | async function main() { 5 | let [owner] = await ethers.getSigners(); 6 | 7 | const USDC = await ethers.getContractFactory("USDC"); 8 | const usdc = await USDC.deploy(); 9 | await usdc.deployed(); 10 | 11 | console.log("USDC deployed to:", usdc.address); 12 | await writeAddr(usdc.address, "USDC") 13 | } 14 | 15 | // We recommend this pattern to be able to use async/await everywhere 16 | // and properly handle errors. 17 | main() 18 | .then(() => process.exit(0)) 19 | .catch(error => { 20 | console.error(error); 21 | process.exit(1); 22 | }); 23 | 24 | -------------------------------------------------------------------------------- /scripts/01_deploy_dao.js: -------------------------------------------------------------------------------- 1 | const { ethers, network } = require("hardhat"); 2 | const { writeAddr } = require('./artifact_log.js'); 3 | 4 | async function main() { 5 | let [owner] = await ethers.getSigners(); 6 | 7 | const usdcAddr = "0x5FbDB2315678afecb367f032d93F642f64180aa3"; 8 | 9 | const DaoToken = await ethers.getContractFactory("DaoToken"); 10 | const token = await DaoToken.deploy(); 11 | await token.deployed(); 12 | 13 | console.log("DaoToken deployed to:", token.address); 14 | await writeAddr(token.address, "DaoToken") 15 | 16 | const FundDao = await ethers.getContractFactory("FundDao"); 17 | const fundDao = await FundDao.deploy(usdcAddr, token.address); 18 | await fundDao.deployed(); 19 | 20 | console.log("FundDao deployed to:", fundDao.address); 21 | await writeAddr(fundDao.address, "FundDao") 22 | 23 | await token.transferMinter(fundDao.address); 24 | 25 | console.log("DaoToken transfer Minter to FundDao"); 26 | 27 | const Governor = await ethers.getContractFactory("Governor"); 28 | const gov = await Governor.deploy(token.address); 29 | await gov.deployed(); 30 | console.log("Governor deployed to:", fundDao.address); 31 | await writeAddr(gov.address, "Governor") 32 | 33 | await fundDao.transferOwnership(gov.address); 34 | console.log("FundDao transfer ownership to Governor"); 35 | 36 | } 37 | 38 | // We recommend this pattern to be able to use async/await everywhere 39 | // and properly handle errors. 40 | main() 41 | .then(() => process.exit(0)) 42 | .catch(error => { 43 | console.error(error); 44 | process.exit(1); 45 | }); 46 | 47 | -------------------------------------------------------------------------------- /scripts/artifact_log.js: -------------------------------------------------------------------------------- 1 | const { network } = require("hardhat"); 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const util = require('util'); 6 | 7 | const writeFile = util.promisify(fs.writeFile); 8 | 9 | /** 10 | * 记录合约发布地址 11 | * @param {*} deployments json 12 | * @param {*} name 类型 13 | */ 14 | async function writeAddr(addr, name){ 15 | const chainid = network.config.chainId; 16 | const saveDir = path.resolve(__dirname, `../deployments/${chainid}/`); 17 | 18 | if (!fs.existsSync(saveDir)) { 19 | fs.mkdirSync(saveDir); 20 | } 21 | 22 | const deploymentPath = saveDir + `/${name}.json`; 23 | 24 | const deployments = {}; 25 | deployments["address"] = addr; 26 | 27 | await writeFile(deploymentPath, JSON.stringify(deployments, null, 2)); 28 | console.log(`Exported deployments into ${deploymentPath}`); 29 | } 30 | 31 | module.exports = { 32 | writeAddr 33 | } -------------------------------------------------------------------------------- /start_hardhat.sh: -------------------------------------------------------------------------------- 1 | npx hardhat node -------------------------------------------------------------------------------- /分享.md: -------------------------------------------------------------------------------- 1 | 2 | Tiny 熊 - 登链社区(https://learnblockchain.cn/) 3 | 4 | ## 背景 5 | DAO: Decentralized Autonomous Organization(分布式自治组织) 6 | 7 | 单一决策 ------> 共同决策 8 | 9 | 投票: 10 | 1. 链下投票: https://snapshot.org/#/ 11 | 2. 链上投票 12 | 13 | 14 | ## 演示 Demo 15 | DAO 最下可用原型:用 DAO 方式管理资金 16 | 0. 合约部署、DEMO运行 17 | 1. 加入 DAO 组织 18 | 2. 提案 19 | 3. 投票 20 | 4. 执行 21 | 22 | Code: git@github.com:Polygon-Academy/Tutorial-DAO.git 23 | 24 | ## 代码讲解 25 | 26 | 1. 合约(contracts) 27 | 2. 部署(scripts) 28 | 3. 模拟测试(mock) 29 | 4. 前端 (demo) 30 | --------------------------------------------------------------------------------