账户
0xa6...b893
0xA6...B893

0xA6...B893

$500
此合同的源代码已经过验证!
合同元数据
编译器
0.8.11+commit.d7f03943
语言
Solidity
合同源代码
文件 1 的 5:IERC20.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

/**
 * @title ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/20
 */
interface IERC20 {
    function totalSupply() external view returns (uint256);

    function balanceOf(address who) external view returns (uint256);

    function allowance(address owner, address spender) external view returns (uint256);

    function transfer(address to, uint256 value) external returns (bool);

    function approve(address spender, uint256 value) external returns (bool);

    function transferFrom(
        address from,
        address to,
        uint256 value
    ) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);

    event Approval(address indexed owner, address indexed spender, uint256 value);
}
合同源代码
文件 2 的 5:IOwnable.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

/// @author thirdweb

/**
 *  Thirdweb's `Ownable` is a contract extension to be used with any base contract. It exposes functions for setting and reading
 *  who the 'owner' of the inheriting smart contract is, and lets the inheriting contract perform conditional logic that uses
 *  information about who the contract's owner is.
 */

interface IOwnable {
    /// @dev Returns the owner of the contract.
    function owner() external view returns (address);

    /// @dev Lets a module admin set a new owner for the contract. The new owner must be a module admin.
    function setOwner(address _newOwner) external;

    /// @dev Emitted when a new Owner is set.
    event OwnerUpdated(address indexed prevOwner, address indexed newOwner);
}
合同源代码
文件 3 的 5:MatchStakingPoolV3.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;

import "@thirdweb-dev/contracts/eip/interface/IERC20.sol";
import "@thirdweb-dev/contracts/extension/Ownable.sol";

contract MatchStakingPoolV3 is Ownable {
  enum Winner { Pending, Home, Away, Draw }

  struct Stake {
    uint256 amount;
    bool claimed;
  }

  struct TeamClaim {
    uint256 amount;
    bool claimed;
  }

  struct Claim {
    TeamClaim home;
    TeamClaim away;
  }

  IERC20 public token;
  uint256 public startDate;
  uint256 public endDate;
  uint256 public totalRewards;
  uint8 public feePercentage;
  Winner public winner;
  address admin;

  mapping(address => mapping(Winner => Stake)) public stakes;
  mapping(Winner => uint256) public totalStaked;

  event Deposited(uint256 amount);
  event Withdrawn(uint256 amount);
  event Staked(address indexed user, uint256 amount, Winner prediction);
  event Unstaked(address indexed user, uint256 amount, Winner prediction);
  event WinnerDeclared(Winner winner);
  event Claimed(address indexed user, uint256 amount);

  constructor(
    uint256 _startDate,
    uint256 _endDate,
    uint8 _feePercentage,
    address _token,
    address _admin
  ) {
    require(_startDate < _endDate, "Start date must be before end date");
    require(_feePercentage <= 100, "Invalid fee percentage");

    startDate = _startDate;
    endDate = _endDate;
    feePercentage = _feePercentage;
    token = IERC20(_token);
    winner = Winner.Pending;
    admin = _admin;
    _setupOwner(msg.sender);
  }

  function getClaim(address user) public view returns (Claim memory) {
    Claim memory userClaim = Claim(TeamClaim(0, false), TeamClaim(0, false));
    if (winner == Winner.Pending) {
      return userClaim;
    }

    Stake storage homeStake = stakes[user][Winner.Home];
    userClaim.home.claimed = homeStake.claimed;
    if (homeStake.amount > 0) {
        uint256 fee = (homeStake.amount * feePercentage) / 100;
        uint256 reward = 0;

        if (winner == Winner.Home) {
            uint256 winnerTotalStaked = totalStaked[winner];
            reward = (totalRewards * homeStake.amount) / winnerTotalStaked;
        }

        userClaim.home.amount = homeStake.amount - fee + reward;
    }

    Stake storage awayStake = stakes[user][Winner.Away];
    userClaim.away.claimed = awayStake.claimed;
    if (awayStake.amount > 0) {
        uint256 fee = (awayStake.amount * feePercentage) / 100;
        uint256 reward = 0;

        if (winner == Winner.Away) {
            uint256 winnerTotalStaked = totalStaked[winner];
            reward = (totalRewards * awayStake.amount) / winnerTotalStaked;
        }

        userClaim.away.amount = awayStake.amount - fee + reward;
    }

    return userClaim;
  }

  function deposit(uint256 amount) external onlyOwner {
    require(block.timestamp < startDate, "Deposit period is over");

    totalRewards += amount;

    require(token.transferFrom(admin, address(this), amount), "Deposit failed");

    emit Deposited(amount);
  }

  function withdraw(uint256 amount) external onlyOwner {
    require(block.timestamp < startDate, "Withdraw period is over");

    totalRewards -= amount;

    require(token.transfer(admin, amount), "Withdraw failed");

    emit Withdrawn(amount);
  }

   function declareWinner(Winner _winner) external onlyOwner {
    require(block.timestamp > endDate, "Match not yet ended");
    require(_winner == Winner.Home || _winner == Winner.Away || _winner == Winner.Draw, "Invalid winner");

    winner = _winner;

    emit WinnerDeclared(_winner);
  }

  function claimRewards() external onlyOwner {
    require(block.timestamp > endDate, "Claim period not yet started");
    require(winner == Winner.Draw, "Invalid winner for claiming rewards");

    require(token.transfer(admin, totalRewards), "Claim transfer failed");

    emit Claimed(admin, totalRewards);
  }

  function setStake(address user, uint256 desiredHomeAmount, uint256 desiredAwayAmount) external onlyOwner {
    require(block.timestamp < startDate, "Staking period is over");

    Stake storage homeStake = stakes[user][Winner.Home];
    Stake storage awayStake = stakes[user][Winner.Away];

    // Calculate differences
    int256 homeDifference = int256(desiredHomeAmount) - int256(homeStake.amount);
    int256 awayDifference = int256(desiredAwayAmount) - int256(awayStake.amount);
    
    // Calculate net token movement
    int256 netDifference = homeDifference + awayDifference;

    // Update internal state for home stake
    if (homeDifference != 0) {
        if (homeDifference > 0) {
            homeStake.amount += uint256(homeDifference);
            totalStaked[Winner.Home] += uint256(homeDifference);
            totalRewards += (uint256(homeDifference) * feePercentage) / 100;
            emit Staked(user, uint256(homeDifference), Winner.Home);
        } else {
            uint256 reduceAmount = uint256(-homeDifference);
            require(homeStake.amount >= reduceAmount, "Insufficient staked amount for home");
            homeStake.amount -= reduceAmount;
            totalStaked[Winner.Home] -= reduceAmount;
            totalRewards -= (reduceAmount * feePercentage) / 100;
            emit Unstaked(user, reduceAmount, Winner.Home);
        }
    }

    // Update internal state for away stake
    if (awayDifference != 0) {
        if (awayDifference > 0) {
            awayStake.amount += uint256(awayDifference);
            totalStaked[Winner.Away] += uint256(awayDifference);
            totalRewards += (uint256(awayDifference) * feePercentage) / 100;
            emit Staked(user, uint256(awayDifference), Winner.Away);
        } else {
            uint256 reduceAmount = uint256(-awayDifference);
            require(awayStake.amount >= reduceAmount, "Insufficient staked amount for away");
            awayStake.amount -= reduceAmount;
            totalStaked[Winner.Away] -= reduceAmount;
            totalRewards -= (reduceAmount * feePercentage) / 100;
            emit Unstaked(user, reduceAmount, Winner.Away);
        }
    }

    // Perform single token transfer if needed
    if (netDifference > 0) {
        // Need to receive tokens from user
        require(token.transferFrom(user, address(this), uint256(netDifference)), 
            "Stake transfer failed");
    } else if (netDifference < 0) {
        // Need to return tokens to user
        require(token.transfer(user, uint256(-netDifference)), 
            "Unstake transfer failed");
    }
  }

  function claim(address user) external onlyOwner {
    require(block.timestamp > endDate, "Claim period not yet started");
    require(winner != Winner.Pending, "Winner not declared yet");

    uint256 totalClaimAmount = 0;

    Claim memory userClaim = getClaim(user);
    if (!userClaim.home.claimed) {
      totalClaimAmount += userClaim.home.amount;
      stakes[user][Winner.Home].claimed = true;
    }
    if (!userClaim.away.claimed) {
      totalClaimAmount += userClaim.away.amount;
      stakes[user][Winner.Away].claimed = true;
    }

    require(totalClaimAmount > 0, "No valid claims to process");
    require(token.transfer(user, totalClaimAmount), "Claim transfer failed");

    emit Claimed(user, totalClaimAmount);
  }

  function _canSetOwner() internal view virtual override returns (bool) {
    return msg.sender == owner();
  }
}
合同源代码
文件 4 的 5:MatchStakingPoolV3Factory.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;

import "./MatchStakingPoolV3.sol";
import "@thirdweb-dev/contracts/extension/Ownable.sol";

contract MatchStakingPoolV3Factory is Ownable {
    event PoolCreated(address indexed poolAddress, uint256 startDate, uint256 endDate);

    mapping(address => bool) public pools;

    constructor() {
        _setupOwner(msg.sender);
    }

    function deploy(
        uint256 _startDate,
        uint256 _endDate,
        uint8 _feePercentage,
        address _token
    ) external onlyOwner returns (address) {
        MatchStakingPoolV3 pool = new MatchStakingPoolV3(
            _startDate,
            _endDate,
            _feePercentage,
            _token,
            msg.sender
        );
        pools[address(pool)] = true;

        emit PoolCreated(address(pool), _startDate, _endDate);

        return address(pool);
    }

    function deposit(address poolAddress, uint256 amount) external onlyOwner {
        require(pools[poolAddress], "Invalid pool");
        MatchStakingPoolV3(poolAddress).deposit(amount);
    }

    function withdraw(address poolAddress, uint256 amount) external onlyOwner {
        require(pools[poolAddress], "Invalid pool");
        MatchStakingPoolV3(poolAddress).withdraw(amount);
    }

    function declareWinner(address poolAddress, MatchStakingPoolV3.Winner winner) external onlyOwner {
        require(pools[poolAddress], "Invalid pool");
        MatchStakingPoolV3(poolAddress).declareWinner(winner);
    }

    function claimRewards(address poolAddress) external onlyOwner {
        require(pools[poolAddress], "Invalid pool");
        MatchStakingPoolV3(poolAddress).claimRewards();
    }

    function setStake(address poolAddress, uint256 desiredHomeAmount, uint256 desiredAwayAmount) external {
        require(pools[poolAddress], "Invalid pool");
        MatchStakingPoolV3(poolAddress).setStake(msg.sender, desiredHomeAmount, desiredAwayAmount);
    }

    function claim(address poolAddress) external {
        require(pools[poolAddress], "Invalid pool");
        MatchStakingPoolV3(poolAddress).claim(msg.sender);
    }

    function _canSetOwner() internal view virtual override returns (bool) {
        return msg.sender == owner();
    }
}
合同源代码
文件 5 的 5:Ownable.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

/// @author thirdweb

import "./interface/IOwnable.sol";

/**
 *  @title   Ownable
 *  @notice  Thirdweb's `Ownable` is a contract extension to be used with any base contract. It exposes functions for setting and reading
 *           who the 'owner' of the inheriting smart contract is, and lets the inheriting contract perform conditional logic that uses
 *           information about who the contract's owner is.
 */

abstract contract Ownable is IOwnable {
    /// @dev Owner of the contract (purpose: OpenSea compatibility)
    address private _owner;

    /// @dev Reverts if caller is not the owner.
    modifier onlyOwner() {
        if (msg.sender != _owner) {
            revert("Not authorized");
        }
        _;
    }

    /**
     *  @notice Returns the owner of the contract.
     */
    function owner() public view override returns (address) {
        return _owner;
    }

    /**
     *  @notice Lets an authorized wallet set a new owner for the contract.
     *  @param _newOwner The address to set as the new owner of the contract.
     */
    function setOwner(address _newOwner) external override {
        if (!_canSetOwner()) {
            revert("Not authorized");
        }
        _setupOwner(_newOwner);
    }

    /// @dev Lets a contract admin set a new owner for the contract. The new owner must be a contract admin.
    function _setupOwner(address _newOwner) internal {
        address _prevOwner = _owner;
        _owner = _newOwner;

        emit OwnerUpdated(_prevOwner, _newOwner);
    }

    /// @dev Returns whether owner can be set in the given execution context.
    function _canSetOwner() internal view virtual returns (bool);
}
设置
{
  "compilationTarget": {
    "contracts/MatchStakingPoolV3Factory.sol": "MatchStakingPoolV3Factory"
  },
  "evmVersion": "london",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "remappings": []
}
ABI
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"prevOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"poolAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"startDate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endDate","type":"uint256"}],"name":"PoolCreated","type":"event"},{"inputs":[{"internalType":"address","name":"poolAddress","type":"address"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"poolAddress","type":"address"}],"name":"claimRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"poolAddress","type":"address"},{"internalType":"enum MatchStakingPoolV3.Winner","name":"winner","type":"uint8"}],"name":"declareWinner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_startDate","type":"uint256"},{"internalType":"uint256","name":"_endDate","type":"uint256"},{"internalType":"uint8","name":"_feePercentage","type":"uint8"},{"internalType":"address","name":"_token","type":"address"}],"name":"deploy","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"poolAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"pools","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"poolAddress","type":"address"},{"internalType":"uint256","name":"desiredHomeAmount","type":"uint256"},{"internalType":"uint256","name":"desiredAwayAmount","type":"uint256"}],"name":"setStake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"poolAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]