编译器
0.8.28+commit.7893614a
文件 1 的 9:Context.sol
pragma solidity ^0.8.20;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
文件 2 的 9:IERC1363.sol
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";
interface IERC1363 is IERC20, IERC165 {
function transferAndCall(address to, uint256 value) external returns (bool);
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
function transferFromAndCall(address from, address to, uint256 value) external returns (bool);
function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);
function approveAndCall(address spender, uint256 value) external returns (bool);
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
文件 3 的 9:IERC165.sol
pragma solidity ^0.8.20;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 4 的 9:IERC20.sol
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";
文件 5 的 9:Ownable.sol
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
error OwnableUnauthorizedAccount(address account);
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 6 的 9:ReentrancyGuard.sol
pragma solidity ^0.8.20;
abstract contract ReentrancyGuard {
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
_status = ENTERED;
}
function _nonReentrantAfter() private {
_status = NOT_ENTERED;
}
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
文件 7 的 9:SafeERC20.sol
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
library SafeERC20 {
error SafeERC20FailedOperation(address token);
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
文件 8 的 9:TokenLocking.sol
pragma solidity 0.8.28;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./WODS_Market.sol";
contract TokenLocking {
using SafeERC20 for IERC20;
IERC20 private immutable _lockToken;
struct Lock {
uint256 amount;
address creator;
bool claimed;
}
mapping(address market => Lock lock) private _locks;
event TokensLocked(address indexed market, address indexed creator, uint256 amount);
event TokensUnlocked(address indexed market, address indexed creator, uint256 amount);
constructor(address tokenAddress) {
_lockToken = IERC20(tokenAddress);
}
function lockTokens(address market, uint256 amount, address creator) external {
require(_locks[market].amount == 0, "Tokens already locked for this market");
require(amount > 0, "Amount must be greater than zero");
_locks[market] = Lock(amount, creator, false);
_lockToken.safeTransferFrom(creator, address(this), amount);
emit TokensLocked(market, creator, amount);
}
function unlockTokens(address market) external {
WODS_Market wodsMarket = WODS_Market(market);
require(wodsMarket.isResolved(), "Market is not resolved yet");
Lock storage lock = _locks[market];
require(lock.amount > 0, "No tokens locked for this market");
require(!lock.claimed, "Tokens already claimed");
require(msg.sender == lock.creator, "Only creator can unlock tokens");
lock.claimed = true;
_lockToken.safeTransfer(lock.creator, lock.amount);
emit TokensUnlocked(market, lock.creator, lock.amount);
}
function getLockedTokens(address market) external view returns (uint256) {
return _locks[market].amount;
}
function isClaimed(address market) external view returns (bool) {
return _locks[market].claimed;
}
}
文件 9 的 9:WODS_Market.sol
pragma solidity 0.8.28;
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract WODS_Market is Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
IERC20 private _rewardToken;
address private _platformAddress;
address private _creatorAddress;
uint256 private _platformFee;
uint256 private _creatorFee;
struct Market {
address admin;
address tokenAddress;
string description;
uint256 tokenDecimals;
uint256 minPrice;
uint256 maxPrice;
uint256 fee;
uint256 totalRewards;
uint256 totalBets;
uint256 totalSponsors;
uint256 endTimestamp;
uint256 outcomes;
uint256 finalOutcome;
bool resolved;
bool cancelled;
}
struct Outcome {
string name;
uint256 totalAmount;
uint256 totalBets;
address[] users;
}
Market public market;
mapping(uint256 index => Outcome outcome) public outcomes;
mapping(uint256 index => address sponsor) public sponsors;
mapping(address user => uint256 amount) public userWithdrawals;
mapping(address user => uint256 outcome) public userBets;
mapping(address user => uint256 amount) public userAmounts;
mapping(address sponsor => uint256 amount) public sponsorAmounts;
event BetPlaced(address indexed bettor, uint256 outcome, uint256 amount);
event MarketUpdated();
event MarketCancelled();
event MarketResolved(uint256 outcome);
event RewardsAmplified(address indexed sponsor, uint256 amount);
event WinningsWithdrawn(address indexed user, uint256 amount);
event FundsWithdrawn(address indexed user, uint256 amount);
event PlatformDetailsUpdated(address platformAddress, uint256 platformFee);
error InvalidAmount(string error);
error InvalidMarketState(string error);
error InvalidAllowance(uint256 amount);
error InvalidOutcome(uint256 outcome);
error InvalidUserAction(string error);
modifier marketNotResolved() {
if (market.resolved || market.endTimestamp <= block.timestamp) {
revert InvalidMarketState("MARKET_RESOLVED");
}
_;
}
modifier marketNotCancelled() {
if (market.cancelled) {
revert InvalidMarketState("MARKET_CANCELLED");
}
_;
}
modifier marketResolved() {
if (!market.resolved || market.endTimestamp > block.timestamp) {
revert InvalidMarketState("MARKET_NOT_RESOLVED");
}
_;
}
modifier onlyParticipants(address _address) {
if (userBets[_address] == 0) {
revert InvalidUserAction("NOT_VALID_PARTICIPANT");
}
_;
}
modifier notAParticipant() {
if (userBets[msg.sender] > 0) {
revert InvalidUserAction("VALID_PARTICIPANT");
}
_;
}
modifier notWithdrawn() {
if (userWithdrawals[msg.sender] != 0) {
revert InvalidUserAction("NO_PENDING_REWARDS");
}
_;
}
modifier hasAllowance(uint256 _amount) {
if (_rewardToken.allowance(msg.sender, address(this)) < _amount) {
revert InvalidAllowance(_amount);
}
_;
}
constructor(
address _tokenAddress,
address _pAddress,
address _marketCreator,
string memory _description,
string[] memory _marketOutcomes,
uint256 _tokenDecimals,
uint256 _endTimestamp,
uint256 _minPrice,
uint256 _maxPrice,
uint256 platformFee_,
uint256 creatorFee_
) Ownable(_marketCreator) {
_rewardToken = IERC20(_tokenAddress);
_platformAddress = _pAddress;
_creatorAddress = _marketCreator;
_platformFee = platformFee_;
_creatorFee = creatorFee_;
market.admin = _marketCreator;
market.description = _description;
market.fee = 0;
market.minPrice = _minPrice;
market.maxPrice = _maxPrice;
market.totalBets = 0;
market.totalSponsors = 0;
market.endTimestamp = _endTimestamp;
market.resolved = false;
market.cancelled = false;
market.outcomes = _marketOutcomes.length;
market.tokenAddress = _tokenAddress;
market.tokenDecimals = _tokenDecimals;
for (uint256 i = 0; i < _marketOutcomes.length; i++) {
Outcome storage outcome = outcomes[i];
outcome.name = _marketOutcomes[i];
outcome.totalBets = 0;
outcome.totalAmount = 0;
}
}
function participate(uint256 _outcome, uint256 _amount)
external
notAParticipant
marketNotResolved
marketNotCancelled
hasAllowance(_amount)
{
if (_amount < market.minPrice) {
revert InvalidAmount("LESS_PRICE");
}
if (_amount > market.maxPrice) {
revert InvalidAmount("MORE_PRICE");
}
if (market.outcomes <= _outcome) {
revert InvalidOutcome(_outcome);
}
Outcome storage extOutcome = outcomes[_outcome];
extOutcome.totalBets++;
extOutcome.totalAmount += _amount;
extOutcome.users.push(msg.sender);
userBets[msg.sender] = _outcome + 1;
userAmounts[msg.sender] = _amount;
market.totalBets++;
market.totalRewards += _amount;
_rewardToken.safeTransferFrom(msg.sender, address(this), _amount);
emit BetPlaced(msg.sender, _outcome, _amount);
}
function getParticipants(uint256 outcome) external view returns (address[] memory) {
return outcomes[outcome].users;
}
function updateMarket(string memory _description, uint256 _endTimestamp, uint256 _minPrice, uint256 _maxPrice)
external
onlyOwner
marketNotCancelled
{
if (market.resolved) {
revert InvalidMarketState("MARKET_RESOLVED");
}
market.description = _description;
market.endTimestamp = _endTimestamp;
market.minPrice = _minPrice;
market.maxPrice = _maxPrice;
emit MarketUpdated();
}
function cancelMarket() external onlyOwner nonReentrant marketNotCancelled {
if (market.resolved) {
revert InvalidMarketState("MARKET_RESOLVED");
}
market.cancelled = true;
market.endTimestamp = block.timestamp;
for (uint256 index = 0; index < market.outcomes; index++) {
Outcome memory outcome = outcomes[index];
for (uint256 uIndex = 0; uIndex < outcome.totalBets; uIndex++) {
_rewardToken.safeTransfer(outcome.users[uIndex], userAmounts[outcome.users[uIndex]]);
}
}
for (uint256 index = 0; index < market.totalSponsors; index++) {
address sponsor = sponsors[index];
uint256 amount = sponsorAmounts[sponsor];
if (getContractBalance() >= amount) {
_rewardToken.safeTransfer(sponsor, amount);
}
}
emit MarketCancelled();
}
function resolveMarket(uint256 _outcome) external onlyOwner marketNotCancelled {
require(!market.resolved, "Market already resolved");
require(_outcome < market.outcomes, "Invalid outcome");
require(block.timestamp > market.endTimestamp, "Market need to End First ");
market.resolved = true;
market.endTimestamp = block.timestamp;
market.finalOutcome = _outcome;
uint256 platformFeeAmount = (market.totalRewards * _platformFee) / 10000;
uint256 creatorFeeAmount = (market.totalRewards * _creatorFee) / 10000;
market.fee = platformFeeAmount + creatorFeeAmount;
market.totalRewards -= market.fee;
_rewardToken.safeTransfer(_platformAddress, platformFeeAmount);
_rewardToken.safeTransfer(_creatorAddress, creatorFeeAmount);
emit MarketResolved(_outcome);
}
function withdrawWinnings() external onlyParticipants(msg.sender) marketResolved notWithdrawn {
uint256 userBet = userBets[msg.sender];
if (market.finalOutcome + 1 != userBet) {
revert InvalidUserAction("NOT_A_WINNER");
}
uint256 winnings = calculateWinnings(msg.sender);
if (winnings == 0) {
revert InvalidAmount("ZERO_WINNINGS_AMOUNT");
}
userWithdrawals[msg.sender] = winnings;
_rewardToken.safeTransfer(msg.sender, winnings);
emit WinningsWithdrawn(msg.sender, winnings);
}
function calculateWinnings(address userAddress)
public
view
onlyParticipants(userAddress)
marketResolved
returns (uint256)
{
uint256 outcomeAmount = outcomes[market.finalOutcome].totalAmount;
if (outcomeAmount == 0) {
revert InvalidAmount("ZERO_OUTCOME_AMOUNT");
}
uint256 multiplier = uint256(10) ** market.tokenDecimals;
uint256 winPercentage = (userAmounts[userAddress] * multiplier) / outcomeAmount;
uint256 winnings = (market.totalRewards * winPercentage) / multiplier;
return winnings;
}
function retrieveFunds() external onlyOwner {
uint256 total_funds = getContractBalance();
_rewardToken.safeTransfer(msg.sender, total_funds);
emit FundsWithdrawn(owner(), total_funds);
}
function amplifyRewards(uint256 _amount)
external
marketNotResolved
marketNotCancelled
hasAllowance(_amount)
returns (uint256)
{
market.totalRewards += _amount;
if (sponsorAmounts[msg.sender] == 0) {
sponsors[market.totalSponsors] = msg.sender;
sponsorAmounts[msg.sender] = _amount;
market.totalSponsors += 1;
} else {
sponsorAmounts[msg.sender] += _amount;
}
_rewardToken.safeTransferFrom(msg.sender, address(this), _amount);
emit RewardsAmplified(msg.sender, sponsorAmounts[msg.sender]);
return getContractBalance();
}
function updatePlatformDetails(address _address, uint256 _fee)
public
onlyOwner
marketNotResolved
marketNotCancelled
{
_platformFee = _fee;
_platformAddress = _address;
emit PlatformDetailsUpdated(_platformAddress, _fee);
}
function getContractBalance() public view returns (uint256) {
return _rewardToken.balanceOf(address(this));
}
function isResolved() external view returns (bool) {
return market.resolved;
}
}
{
"compilationTarget": {
"src/TokenLocking.sol": "TokenLocking"
},
"evmVersion": "cancun",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/"
],
"viaIR": true
}
[{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"market","type":"address"},{"indexed":true,"internalType":"address","name":"creator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokensLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"market","type":"address"},{"indexed":true,"internalType":"address","name":"creator","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokensUnlocked","type":"event"},{"inputs":[{"internalType":"address","name":"market","type":"address"}],"name":"getLockedTokens","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"market","type":"address"}],"name":"isClaimed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"market","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"creator","type":"address"}],"name":"lockTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"market","type":"address"}],"name":"unlockTokens","outputs":[],"stateMutability":"nonpayable","type":"function"}]