Bitcoinu

Description:

Governance contract for decentralized decision-making.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/Bitcoinu.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @title Bitcoinu (BTCu)
 * @dev 投票機能付きERC20トークン
 * イーサリアムメインネットにデプロイ
 * トークンホルダーの投票により名前とシンボルをBitcoinとBTCに変更可能
 * 投票は総発行枚数の10%以上の投票数で有効となり、賛成多数の場合のみ変更される。
 * 変更されなかった場合は半年間の間隔を空けて再度投票を開催することができる。
 */
contract Bitcoinu {
    // ========== ERC20基本情報 ==========
    
    string private _name;
    string private _symbol;
    uint8 private constant _decimals = 18;
    uint256 private constant _totalSupply = 21_000_000 * 10**18;
    
    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;
    
    // ========== 投票関連の定数 ==========
    
    uint256 public constant VOTING_DURATION = 5 minutes;        // 投票期間: 6ヶ月
    uint256 public constant WAITING_PERIOD = 5 minutes;         // 待機期間: 6ヶ月
    // uint256 public constant VOTING_DURATION = 180 days;        // 投票期間: 6ヶ月
    // uint256 public constant WAITING_PERIOD = 180 days;         // 待機期間: 6ヶ月
    uint256 public constant MIN_VOTES_REQUIRED = 2_100_000 * 10**18;  // 最小投票数: 210万枚
    
    // ========== 投票状態管理 ==========
    
    enum VotingStatus {
        NotStarted,    // 未開始
        Active,        // 進行中
        Approved,      // 承認済み(名前変更済み)
        Rejected,      // 否決
        QuorumNotMet   // 定足数未達(再投票までの待機期間)
    }
    
    struct VotingRound {
        uint256 startTime;           // 投票開始時刻
        uint256 endTime;             // 投票終了時刻
        uint256 totalVotes;          // 総投票数
        uint256 votesFor;            // 賛成票
        uint256 votesAgainst;        // 反対票
        VotingStatus status;         // 投票状態
        mapping(address => uint256) voterDeposits;  // 各投票者のデポジット額
        mapping(address => bool) voterChoice;       // 各投票者の選択(true=賛成, false=反対)
    }
    
    uint256 public currentRound;                    // 現在の投票ラウンド
    uint256 public votingEnabledTime;               // 投票開始可能時刻
    mapping(uint256 => VotingRound) public votingRounds;  // ラウンドごとの投票情報
    
    bool public nameChanged;                        // 名前が変更されたかどうか
    
    // ========== イベント ==========
    
    // ERC20標準イベント
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    
    // 投票関連イベント
    event VotingStarted(uint256 indexed round, uint256 startTime, uint256 endTime);
    event VoteCast(uint256 indexed round, address indexed voter, uint256 amount, bool voteFor);
    event VotingEnded(uint256 indexed round, VotingStatus status, uint256 totalVotes, uint256 votesFor, uint256 votesAgainst);
    event NameChanged(string newName, string newSymbol);
    event TokensWithdrawn(uint256 indexed round, address indexed voter, uint256 amount);
    
    // ========== コンストラクタ ==========
    
    /**
     * @dev コントラクトを初期化
     * @param _votingEnabledTime 投票開始可能時刻(Unixタイムスタンプ)
     */
    constructor(uint256 _votingEnabledTime) {
        require(_votingEnabledTime > block.timestamp, "Voting enabled time must be in the future");
        
        _name = "Bitcoinu";
        _symbol = "BTCu";
        votingEnabledTime = _votingEnabledTime;
        nameChanged = false;
        currentRound = 0;
        
        // 総供給量をデプロイヤーに割り当て
        _balances[msg.sender] = _totalSupply;
        emit Transfer(address(0), msg.sender, _totalSupply);
    }
    
    // ========== ERC20標準関数 ==========
    
    function name() public view returns (string memory) {
        return _name;
    }
    
    function symbol() public view returns (string memory) {
        return _symbol;
    }
    
    function decimals() public pure returns (uint8) {
        return _decimals;
    }
    
    function totalSupply() public pure returns (uint256) {
        return _totalSupply;
    }
    
    function balanceOf(address account) public view returns (uint256) {
        return _balances[account];
    }
    
    function transfer(address to, uint256 amount) public returns (bool) {
        _transfer(msg.sender, to, amount);
        return true;
    }
    
    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowances[owner][spender];
    }
    
    function approve(address spender, uint256 amount) public returns (bool) {
        _approve(msg.sender, spender, amount);
        return true;
    }
    
    function transferFrom(address from, address to, uint256 amount) public returns (bool) {
        _spendAllowance(from, msg.sender, amount);
        _transfer(from, to, amount);
        return true;
    }
    
    // ========== 投票機能 ==========
    
    /**
     * @dev 新しい投票を開始
     */
    function startVoting() external {
        require(block.timestamp >= votingEnabledTime, "Voting not enabled yet");
        require(!nameChanged, "Name already changed");
        
        // 現在アクティブな投票がないことを確認
        if (currentRound > 0) {
            require(
                votingRounds[currentRound].status != VotingStatus.Active,
                "Voting already active"
            );
        }
        
        currentRound++;
        VotingRound storage newRound = votingRounds[currentRound];
        newRound.startTime = block.timestamp;
        newRound.endTime = block.timestamp + VOTING_DURATION;
        newRound.status = VotingStatus.Active;
        
        emit VotingStarted(currentRound, newRound.startTime, newRound.endTime);
    }
    
    /**
     * @dev 投票を行う
     * @param amount 投票に使用するトークン数
     * @param voteFor true=賛成、false=反対
     */
    function vote(uint256 amount, bool voteFor) external {
        require(currentRound > 0, "No active voting");
        VotingRound storage round = votingRounds[currentRound];
        require(round.status == VotingStatus.Active, "Voting not active");
        require(block.timestamp < round.endTime, "Voting period ended");
        require(amount > 0, "Amount must be greater than 0");
        require(_balances[msg.sender] >= amount, "Insufficient balance");
        
        // 既に投票している場合、同じ選択であることを確認
        if (round.voterDeposits[msg.sender] > 0) {
            require(round.voterChoice[msg.sender] == voteFor, "Cannot change vote choice");
        }
        
        // トークンをコントラクトにデポジット
        _balances[msg.sender] -= amount;
        _balances[address(this)] += amount;
        
        // 投票を記録(既存の場合は追加)
        round.voterDeposits[msg.sender] += amount;
        round.voterChoice[msg.sender] = voteFor;
        round.totalVotes += amount;
        
        if (voteFor) {
            round.votesFor += amount;
        } else {
            round.votesAgainst += amount;
        }
        
        emit VoteCast(currentRound, msg.sender, amount, voteFor);
        emit Transfer(msg.sender, address(this), amount);
    }
    
    /**
     * @dev 投票を終了し、結果を確定
     */
    function finalizeVoting() external {
        require(currentRound > 0, "No voting to finalize");
        VotingRound storage round = votingRounds[currentRound];
        require(round.status == VotingStatus.Active, "Voting not active");
        require(block.timestamp >= round.endTime, "Voting period not ended yet");
        
        // 投票結果を判定
        if (round.totalVotes >= MIN_VOTES_REQUIRED) {
            // 最小投票数を満たしている場合、賛成多数かどうかを判定
            if (round.votesFor > round.votesAgainst) {
                // 承認: 名前とシンボルを変更
                round.status = VotingStatus.Approved;
                _name = "Bitcoin";
                _symbol = "BTC";
                nameChanged = true;
                emit NameChanged("Bitcoin", "BTC");
                // 名前変更済みなので、次の投票は不要
            } else {
                // 否決: 最小投票数は満たしたが賛成が多数派ではない
                round.status = VotingStatus.Rejected;
                // すぐに再投票可能
                votingEnabledTime = block.timestamp;
            }
        } else {
            // 定足数未達: 最小投票数に達していない
            round.status = VotingStatus.QuorumNotMet;
            // 6ヶ月後に再投票可能
            votingEnabledTime = round.endTime + WAITING_PERIOD;
        }
        
        emit VotingEnded(currentRound, round.status, round.totalVotes, round.votesFor, round.votesAgainst);
    }
    
    /**
     * @dev 投票終了後にデポジットしたトークンを引き出す
     * @param roundNumber 引き出し対象の投票ラウンド番号
     */
    function withdrawVoteTokens(uint256 roundNumber) external {
        require(roundNumber > 0 && roundNumber <= currentRound, "Invalid round number");
        
        VotingRound storage round = votingRounds[roundNumber];
        
        // 投票が終了しているかチェック
        require(
            round.status == VotingStatus.Approved || 
            round.status == VotingStatus.Rejected || 
            round.status == VotingStatus.QuorumNotMet,
            "Voting not finalized yet"
        );
        
        uint256 amount = round.voterDeposits[msg.sender];
        require(amount > 0, "No tokens to withdraw");
        
        // デポジットをゼロにしてから送金(再入攻撃対策)
        round.voterDeposits[msg.sender] = 0;
        
        _balances[address(this)] -= amount;
        _balances[msg.sender] += amount;
        
        emit TokensWithdrawn(roundNumber, msg.sender, amount);
        emit Transfer(address(this), msg.sender, amount);
    }
    
    // ========== ビュー関数 ==========
    
    /**
     * @dev 現在の投票ラウンドの情報を取得
     */
    function getCurrentVotingInfo() external view returns (
        uint256 round,
        uint256 startTime,
        uint256 endTime,
        uint256 totalVotes,
        uint256 votesFor,
        uint256 votesAgainst,
        VotingStatus status
    ) {
        if (currentRound == 0) {
            return (0, 0, 0, 0, 0, 0, VotingStatus.NotStarted);
        }
        
        VotingRound storage voting = votingRounds[currentRound];
        return (
            currentRound,
            voting.startTime,
            voting.endTime,
            voting.totalVotes,
            voting.votesFor,
            voting.votesAgainst,
            voting.status
        );
    }
    
    /**
     * @dev 特定のアドレスが特定のラウンドでデポジットしているトークン数を取得
     * @param roundNumber 投票ラウンド番号
     * @param voter 投票者のアドレス
     */
    function getVoterDeposit(uint256 roundNumber, address voter) external view returns (uint256) {
        if (roundNumber == 0 || roundNumber > currentRound) {
            return 0;
        }
        
        return votingRounds[roundNumber].voterDeposits[voter];
    }
    
    /**
     * @dev 自分が特定のラウンドでデポジットしているトークン数を取得
     * @param roundNumber 投票ラウンド番号
     */
    function getVoterDeposit(uint256 roundNumber) external view returns (uint256) {
        return this.getVoterDeposit(roundNumber, msg.sender);
    }
    
    /**
     * @dev 投票が開始可能かチェック
     */
    function canStartVoting() external view returns (bool) {
        if (nameChanged) return false;
        if (block.timestamp < votingEnabledTime) return false;
        if (currentRound > 0 && votingRounds[currentRound].status == VotingStatus.Active) return false;
        
        return true;
    }
    
    /**
     * @dev 次の投票開始可能時刻を取得
     */
    function getNextVotingStartTime() external view returns (uint256) {
        if (nameChanged) return 0;
        return votingEnabledTime;
    }
    
    // ========== 内部関数 ==========
    
    function _transfer(address from, address to, uint256 amount) internal {
        require(from != address(0), "Transfer from zero address");
        require(to != address(0), "Transfer to zero address");
        require(_balances[from] >= amount, "Insufficient balance");
        
        _balances[from] -= amount;
        _balances[to] += amount;
        
        emit Transfer(from, to, amount);
    }
    
    function _approve(address owner, address spender, uint256 amount) internal {
        require(owner != address(0), "Approve from zero address");
        require(spender != address(0), "Approve to zero address");
        
        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }
    
    function _spendAllowance(address owner, address spender, uint256 amount) internal {
        uint256 currentAllowance = _allowances[owner][spender];
        require(currentAllowance >= amount, "Insufficient allowance");
        
        _allowances[owner][spender] = currentAllowance - amount;
    }
}"
    }
  },
  "settings": {
    "evmVersion": "paris",
    "optimizer": {
      "enabled": false,
      "runs": 200
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    }
  }
}}

Tags:
ERC20, Governance, Voting, Factory|addr:0x2b30c3be1a157f8b63ee316700d57f3a305625cf|verified:true|block:23732346|tx:0xddc5ed2fbba4e39e18275a89f416ec1bb7dc90c285682392fedef1e90746e22c|first_check:1762347710

Submitted on: 2025-11-05 14:01:52

Comments

Log in to comment.

No comments yet.