Contract Source Code:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
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;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
library LMSRMath {
// Fixed point precision
uint256 constant FIXED_ONE = 1e18;
// Maximum value for exponentiation to avoid overflow
uint256 constant MAX_EXPONENT = 100e18;
function calcCost(
uint256[] memory outcomeShares,
int256[] memory shareDeltas,
uint256 b
) internal pure returns (int256) {
require(outcomeShares.length == shareDeltas.length, "Array length mismatch");
require(outcomeShares.length > 0, "Empty arrays");
require(b > 0, "Invalid liquidity parameter");
// Calculate cost before trade
uint256 costBefore = calcCostFromShares(outcomeShares, b);
// Calculate new outcome shares after trade
uint256[] memory newShares = new uint256[](outcomeShares.length);
for (uint256 i = 0; i < outcomeShares.length; i++) {
if (shareDeltas[i] >= 0) {
newShares[i] = outcomeShares[i] + uint256(shareDeltas[i]);
} else {
require(outcomeShares[i] >= uint256(-shareDeltas[i]), "Insufficient shares");
newShares[i] = outcomeShares[i] - uint256(-shareDeltas[i]);
}
}
// Calculate cost after trade
uint256 costAfter = calcCostFromShares(newShares, b);
// Return difference in costs
if (costAfter >= costBefore) {
return int256(costAfter - costBefore);
} else {
return -int256(costBefore - costAfter);
}
}
function calcCostFromShares(
uint256[] memory shares,
uint256 b
) internal pure returns (uint256) {
uint256 sum = 0;
// Calculate sum of exp(q_i/b) for all outcomes
for (uint256 i = 0; i < shares.length; i++) {
// Normalize shares by dividing by b
uint256 normalizedShares = (shares[i] * FIXED_ONE) / b;
// Prevent overflow by capping the exponent
if (normalizedShares > MAX_EXPONENT) {
normalizedShares = MAX_EXPONENT;
}
// Calculate exp(q_i/b) and add to sum
sum += exp(normalizedShares);
}
// Calculate b * ln(sum)
return (b * ln(sum)) / FIXED_ONE;
}
function calcMarginalPrice(
uint256 outcomeIndex,
uint256[] memory shares,
uint256 b
) internal pure returns (uint256) {
require(outcomeIndex < shares.length, "Invalid outcome index");
// Calculate sum of exp(q_i/b) for all outcomes
uint256 sum = 0;
for (uint256 i = 0; i < shares.length; i++) {
uint256 normalizedShares = (shares[i] * FIXED_ONE) / b;
if (normalizedShares > MAX_EXPONENT) {
normalizedShares = MAX_EXPONENT;
}
sum += exp(normalizedShares);
}
// Calculate exp(q_i/b) for the specific outcome
uint256 normalizedOutcomeShares = (shares[outcomeIndex] * FIXED_ONE) / b;
if (normalizedOutcomeShares > MAX_EXPONENT) {
normalizedOutcomeShares = MAX_EXPONENT;
}
uint256 outcomeExp = exp(normalizedOutcomeShares);
// Calculate price = exp(q_i/b) / sum(exp(q_j/b))
return (outcomeExp * FIXED_ONE) / sum;
}
function exp(uint256 x) internal pure returns (uint256) {
// If x is 0, e^0 = 1
if (x == 0) return FIXED_ONE;
// If x is very large, return maximum value to avoid overflow
if (x > MAX_EXPONENT) return type(uint256).max;
// Taylor series approximation for e^x
// e^x = 1 + x + x^2/2! + x^3/3! + ... + x^n/n!
uint256 result = FIXED_ONE; // 1
uint256 term = FIXED_ONE; // Start with 1
// Add x
term = (term * x) / FIXED_ONE;
result += term;
// Add x^2/2!
term = (term * x) / (2 * FIXED_ONE);
result += term;
// Add x^3/3!
term = (term * x) / (3 * FIXED_ONE);
result += term;
// Add x^4/4!
term = (term * x) / (4 * FIXED_ONE);
result += term;
// Add x^5/5!
term = (term * x) / (5 * FIXED_ONE);
result += term;
// Add x^6/6!
term = (term * x) / (6 * FIXED_ONE);
result += term;
// Add x^7/7!
term = (term * x) / (7 * FIXED_ONE);
result += term;
// Add x^8/8!
term = (term * x) / (8 * FIXED_ONE);
result += term;
return result;
}
function ln(uint256 x) internal pure returns (uint256) {
// If x is 0 or very small, return a very negative number
// In practice, this should never happen in LMSR
if (x < FIXED_ONE / 1000) {
return 0; // Should revert in practice
}
// If x is 1, ln(1) = 0
if (x == FIXED_ONE) return 0;
// If x < 1, use ln(x) = -ln(1/x)
if (x < FIXED_ONE) {
return type(uint256).max - ln((FIXED_ONE * FIXED_ONE) / x) + 1;
}
// For x > 1, use binary search to find y such that e^y = x
uint256 low = 0;
uint256 high = MAX_EXPONENT;
uint256 mid;
uint256 midExp;
// Binary search for 32 iterations (sufficient precision)
for (uint256 i = 0; i < 32; i++) {
mid = (low + high) / 2;
midExp = exp(mid);
if (midExp < x) {
low = mid;
} else if (midExp > x) {
high = mid;
} else {
return mid; // Exact match found
}
}
// Return the closest approximation
return (low + high) / 2;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./LMSRMath.sol";
contract Market is Ownable, Pausable, ReentrancyGuard {
// State variables
address public factory;
address public feeRecipient;
address public reporter;
address public governor;
address public susdToken;
uint256 public b;
uint256 public resolutionDelay;
uint256 public tradingFee;
uint256 public disputeBondAmount;
uint256 public reporterResolutionDeadline;
uint256 public resolutionTimestamp;
uint256 public totalVotes;
uint256 public mostVotedOutcome;
uint256 public winningOutcome;
bool public resolved;
string[] public outcomes;
mapping(uint256 => uint256) public outcomeShares;
mapping(address => mapping(uint256 => uint256)) public userShares;
mapping(address => uint256) public disputeBonds;
mapping(uint256 => uint256) public disputeVotes;
mapping(address => bool) public hasVoted;
uint256 public constant DISPUTE_PERIOD = 7 days;
uint256 public constant MAX_TRADE_PERCENTAGE = 10;
// Events
event Trade(
address indexed user,
int256[] amounts,
uint256[] shareBalances,
uint256 feesPaid
);
event MarketResolved(uint256 indexed outcome);
event ResolutionProposed(uint256 indexed outcome, uint256 resolutionTimestamp);
event DisputeSubmitted(
address indexed disputer,
uint256 indexed proposedOutcome,
uint256 bondAmount
);
event VoteCast(address indexed voter, uint256 indexed outcome);
event Payout(address indexed user, uint256 amount);
event ReporterDeadlineSet(uint256 deadline);
event EmergencyResolution(uint256 indexed outcome, address resolver);
struct MarketParams {
string[] outcomes;
address feeRecipient;
uint256 resolutionDelay;
address reporter;
address governor;
uint256 b;
uint256 tradingFee;
address susdToken;
uint256 disputeBondAmount;
}
constructor(
string[] memory _outcomes,
address _feeRecipient,
uint256 _resolutionDelay,
address _reporter,
address _governor,
uint256 _b,
uint256 _tradingFee,
address _susdToken,
uint256 _disputeBondAmount
) Ownable(_governor) Pausable() ReentrancyGuard() {
require(_outcomes.length >= 2, "Must have at least 2 outcomes");
require(_feeRecipient != address(0), "Invalid fee recipient");
require(_reporter != address(0), "Invalid reporter");
require(_governor != address(0), "Invalid governor");
require(_susdToken != address(0), "Invalid SUSD token");
require(_b > 0, "Invalid liquidity parameter");
factory = msg.sender;
feeRecipient = _feeRecipient;
reporter = _reporter;
governor = _governor;
susdToken = _susdToken;
b = _b;
resolutionDelay = _resolutionDelay;
tradingFee = _tradingFee;
disputeBondAmount = _disputeBondAmount;
for (uint256 i = 0; i < _outcomes.length; i++) {
outcomes.push(_outcomes[i]);
outcomeShares[i] = 1e18;
}
reporterResolutionDeadline = block.timestamp + 30 days;
emit ReporterDeadlineSet(reporterResolutionDeadline);
_transferOwnership(governor);
}
// Modifiers
modifier onlyReporter() {
require(msg.sender == reporter, "Only reporter can call this function");
_;
}
modifier onlyGovernor() {
require(msg.sender == governor, "Only governor can call this function");
_;
}
modifier onlyFactory() {
require(msg.sender == factory, "Only factory can call this function");
_;
}
// Existing Market contract functions
function getCurrentShares() public view returns (uint256[] memory) {
uint256[] memory shares = new uint256[](outcomes.length);
for (uint256 i = 0; i < outcomes.length; i++) {
shares[i] = outcomeShares[i];
}
return shares;
}
function getCost(int256[] memory shareDeltas) public view returns (int256) {
require(shareDeltas.length == outcomes.length, "Invalid array length");
// Get current shares
uint256[] memory shares = getCurrentShares();
// Calculate cost using LMSR math
return LMSRMath.calcCost(shares, shareDeltas, b);
}
function getNetCost(int256[] memory shareDeltas) public view returns (uint256) {
int256 cost = getCost(shareDeltas);
// If cost is negative (selling), return 0 as net cost
if (cost <= 0) return 0;
// Apply trading fee
uint256 fee = (uint256(cost) * tradingFee) / 10000;
return uint256(cost) + fee;
}
function getMarginalPrice(uint256 outcomeIndex) public view returns (uint256) {
require(outcomeIndex < outcomes.length, "Invalid outcome index");
uint256[] memory shares = getCurrentShares();
return LMSRMath.calcMarginalPrice(outcomeIndex, shares, b);
}
function trade(int256[] calldata shareDeltas, uint256 maxCost) external nonReentrant whenNotPaused {
// CHECKS
require(!resolved, "Market already resolved");
require(shareDeltas.length == outcomes.length, "Invalid array length");
// Validate trade size to prevent manipulation
uint256[] memory shares = getCurrentShares();
for (uint256 i = 0; i < shareDeltas.length; i++) {
if (shareDeltas[i] > 0) {
// Ensure buy orders don't exceed maximum percentage of current market shares
require(
uint256(shareDeltas[i]) <= (shares[i] * MAX_TRADE_PERCENTAGE) / 100,
"Trade size too large"
);
} else if (shareDeltas[i] < 0) {
// Ensure sell orders don't exceed user's balance
require(
uint256(-shareDeltas[i]) <= userShares[msg.sender][i],
"Insufficient shares"
);
}
}
// Calculate cost using LMSR
int256 cost = getCost(shareDeltas);
// Apply trading fee (only for buys)
uint256 fee = 0;
uint256 tradeTotalCost = 0;
if (cost > 0) {
fee = (uint256(cost) * tradingFee) / 10000;
tradeTotalCost = uint256(cost) + fee;
// Slippage protection
require(tradeTotalCost <= maxCost, "Slippage exceeded");
}
// EFFECTS
// Update state variables
for (uint256 i = 0; i < shareDeltas.length; i++) {
if (shareDeltas[i] > 0) {
outcomeShares[i] += uint256(shareDeltas[i]);
userShares[msg.sender][i] += uint256(shareDeltas[i]);
} else if (shareDeltas[i] < 0) {
outcomeShares[i] -= uint256(-shareDeltas[i]);
userShares[msg.sender][i] -= uint256(-shareDeltas[i]);
}
}
if (cost > 0) {
// User is buying, transfer SUSD from user
require(
IERC20(susdToken).transferFrom(msg.sender, address(this), uint256(cost)),
"Trade transfer failed"
);
// Transfer fee
if (fee > 0) {
require(
IERC20(susdToken).transferFrom(msg.sender, feeRecipient, fee),
"Fee transfer failed"
);
}
} else if (cost < 0) {
// User is selling, transfer SUSD to user
require(
IERC20(susdToken).transfer(msg.sender, uint256(-cost)),
"Payout transfer failed"
);
}
// Get updated share balances for the event
uint256[] memory newBalances = getCurrentShares();
emit Trade(msg.sender, shareDeltas, newBalances, fee);
}
function resolveMarket(uint256 outcome) external onlyReporter {
require(!resolved, "Market already resolved");
require(block.timestamp >= reporterResolutionDeadline, "Too early");
require(outcome < outcomes.length, "Invalid outcome");
winningOutcome = outcome;
resolutionTimestamp = block.timestamp;
resolved = true;
emit ResolutionProposed(outcome, resolutionTimestamp);
emit MarketResolved(outcome);
}
function overrideResolution(uint256 outcome) external onlyGovernor {
require(!resolved, "Market already resolved");
require(block.timestamp < resolutionTimestamp, "Dispute period ended");
require(outcome < outcomes.length, "Invalid outcome");
resolved = true;
winningOutcome = outcome;
resolutionTimestamp = 0;
emit MarketResolved(outcome);
}
function payout() external nonReentrant {
require(resolved, "Market not resolved");
uint256 shares = userShares[msg.sender][winningOutcome];
require(shares > 0, "No winning shares");
uint256 payoutAmount = shares;
userShares[msg.sender][winningOutcome] = 0;
require(IERC20(susdToken).transfer(msg.sender, payoutAmount), "Payout transfer failed");
emit Payout(msg.sender, payoutAmount);
}
// Dispute mechanism for users
function submitDispute(uint256 proposedOutcome) external nonReentrant {
require(!resolved, "Market already resolved");
require(proposedOutcome < outcomes.length, "Invalid outcome");
require(!hasVoted[msg.sender], "Already voted");
require(block.timestamp <= resolutionTimestamp + DISPUTE_PERIOD, "Dispute period ended");
require(IERC20(susdToken).transferFrom(msg.sender, address(this), disputeBondAmount), "Bond transfer failed");
disputeBonds[msg.sender] = disputeBondAmount;
disputeVotes[proposedOutcome] += disputeBondAmount;
totalVotes += disputeBondAmount;
hasVoted[msg.sender] = true;
if (disputeVotes[proposedOutcome] > disputeVotes[mostVotedOutcome]) {
mostVotedOutcome = proposedOutcome;
}
emit DisputeSubmitted(msg.sender, proposedOutcome, disputeBondAmount);
emit VoteCast(msg.sender, proposedOutcome);
}
function finalizeDispute() external nonReentrant {
require(!resolved, "Market already resolved");
require(block.timestamp > resolutionTimestamp + DISPUTE_PERIOD, "Dispute period not ended");
require(totalVotes > 0, "No disputes submitted");
winningOutcome = mostVotedOutcome;
resolved = true;
emit MarketResolved(mostVotedOutcome);
}
function claimDisputeBond() external nonReentrant {
require(resolved, "Market not resolved");
require(disputeBonds[msg.sender] > 0, "No bond to claim");
require(block.timestamp > resolutionTimestamp + DISPUTE_PERIOD, "Dispute period not ended");
uint256 bondAmount = disputeBonds[msg.sender];
disputeBonds[msg.sender] = 0;
// If voted for winning outcome, get reward from losing votes
if (hasVoted[msg.sender]) {
uint256 shareOfWinningVotes = (bondAmount * 1e18) / disputeVotes[winningOutcome];
bondAmount += (totalVotes - disputeVotes[winningOutcome]) * shareOfWinningVotes / 1e18;
}
require(IERC20(susdToken).transfer(msg.sender, bondAmount), "Bond return failed");
}
// Emergency resolution if reporter never resolves
function emergencyResolve() external {
require(!resolved, "Market already resolved");
require(block.timestamp > reporterResolutionDeadline, "Reporter deadline not passed");
// If disputes exist, use most voted outcome
uint256 finalOutcome = totalVotes > 0 ? mostVotedOutcome : 0;
resolved = true;
winningOutcome = finalOutcome;
emit EmergencyResolution(finalOutcome, msg.sender);
emit MarketResolved(finalOutcome);
}
// Allow governor to set a new reporter deadline
function setReporterDeadline(uint256 newDeadline) external onlyGovernor {
require(newDeadline > block.timestamp, "Deadline must be in future");
reporterResolutionDeadline = newDeadline;
emit ReporterDeadlineSet(newDeadline);
}
function verifyInitialization(
string[] calldata _outcomes,
uint256 _resolutionDelay,
address _reporter,
address _governor
) external view returns (bool) {
// Verify that initialization parameters match
if (_outcomes.length != outcomes.length) return false;
if (_reporter != reporter) return false;
if (_governor != governor) return false;
if (_resolutionDelay != resolutionDelay) return false;
// Verify outcomes match
for (uint256 i = 0; i < _outcomes.length; i++) {
if (keccak256(bytes(_outcomes[i])) != keccak256(bytes(outcomes[i]))) {
return false;
}
}
return true;
}
// View functions
function getMarketInfo() external view returns (
string[] memory,
uint256[] memory,
uint256,
bool,
uint256
) {
uint256[] memory shares = getCurrentShares();
return (outcomes, shares, b, resolved, winningOutcome);
}
function getResolutionDelay() external view returns (uint256) {
return resolutionDelay;
}
function getProposedOutcome() external view returns (uint256) {
return winningOutcome;
}
function getResolutionTimestamp() external view returns (uint256) {
return resolutionTimestamp;
}
function getReporter() external view returns (address) {
return reporter;
}
function getGovernor() external view returns (address) {
return governor;
}
function getAIAddress() external view returns (address) {
return address(this);
}
function getTradingFee() external view returns (uint256) {
return tradingFee;
}
// Pause/unpause functions
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
}