Smart Contracts¶
Overview of the smart contracts powering the FairWins P2P wager platform and ClearPath governance system.
Contract Architecture¶
The system has two main contract groups: the P2P Wager contracts (FairWins) and the Governance contracts (ClearPath), sharing common infrastructure.
graph TD
subgraph FairWins [FairWins - P2P Wagers]
H[FriendGroupMarketFactory] --> D[ConditionalMarketFactory]
H --> F[OracleResolver]
H --> I[CTF1155]
end
subgraph ClearPath [ClearPath - Governance]
A[FutarchyGovernor] --> B[WelfareMetricRegistry]
A --> C[ProposalRegistry]
A --> D
A --> E[PrivacyCoordinator]
A --> F
A --> G[RagequitModule]
end
E --> D
F --> D
P2P Wager Contracts¶
FriendGroupMarketFactory.sol¶
Purpose: Creates and manages private P2P wager markets between trusted parties
Key Responsibilities:
- Wager creation with configurable binary outcome types
- Stake escrow and management
- Oracle source selection (Polymarket, Chainlink, UMA, manual)
- Invite link / QR code generation
- Resolution and payout settlement
This is the primary entry point for the FairWins platform. See contracts/README.md for the full contract directory structure.
Governance Contracts¶
FutarchyGovernor.sol¶
Purpose: Main governance coordinator and entry point
Key Responsibilities:
- Manages proposal lifecycle
- Coordinates all other contracts
- Implements timelock mechanism
- Controls treasury access
- Emergency pause functionality
Key Functions:
function activateProposal(uint256 proposalId) external
function finalizeProposal(uint256 proposalId) external
function executeProposal(uint256 proposalId) external
function pause() external onlyGuardian
Events:
event ProposalActivated(uint256 indexed proposalId, uint256 marketId);
event ProposalFinalized(uint256 indexed proposalId, bool approved);
event ProposalExecuted(uint256 indexed proposalId);
WelfareMetricRegistry.sol¶
Purpose: Manages welfare metrics and their weights
Key Features:
- Stores welfare metric definitions
- Manages metric weights
- Versioning support
- Democratic updates
Welfare Metrics:
- Primary: Treasury Value (TWAP)
- Secondary: Network Activity
- Tertiary: Hash Rate Security
- Quaternary: Developer Activity
Key Functions:
function registerMetric(string memory name, string memory description) external
function updateMetricWeight(uint256 metricId, uint256 weight) external
function getMetricValue(uint256 metricId) external view returns (uint256)
ProposalRegistry.sol¶
Purpose: Handles proposal submission and storage
Key Features:
- Permissionless submission with bond
- Standardized metadata schemas
- Milestone tracking
- Status management
Proposal Structure:
struct Proposal {
uint256 id;
address proposer;
string title;
string description;
uint256 fundingAmount;
address recipient;
uint256 welfareMetricId;
uint256 bond;
ProposalStatus status;
uint256 submissionTime;
Milestone[] milestones;
}
Key Functions:
function submitProposal(
string memory title,
string memory description,
uint256 fundingAmount,
address recipient,
uint256 welfareMetricId
) external payable returns (uint256)
function getProposal(uint256 proposalId) external view returns (Proposal memory)
ConditionalMarketFactory.sol¶
Purpose: Creates and manages prediction markets with flexible binary outcome types
Key Features:
- Deploys outcome token pairs with configurable bet types
- Support for multiple binary bet formats (Yes/No, Above/Below, Pass/Fail, etc.)
- LMSR automated market making
- Gnosis CTF compatibility
- Token redemption logic
Supported Bet Types:
The contract supports the following binary bet types through the BetType enum:
YesNo- Standard Yes / No outcomesPassFail- Pass / Fail outcomes (default for governance)AboveBelow- Above / Below a thresholdHigherLower- Higher / Lower than referenceInOut- In / Out of rangeOverUnder- Over / Under a valueForAgainst- For / Against a proposalTrueFalse- True / False statementWinLose- Win / Lose outcomeUpDown- Up / Down movement
Market Structure:
struct Market {
uint256 proposalId;
address passToken;
address failToken;
uint256 liquidityParameter; // b in LMSR
uint256 startTime;
uint256 endTime;
bool resolved;
bool outcome; // true = positive outcome wins
BetType betType; // Type of binary bet
}
Key Functions:
function getOutcomeLabels(BetType betType)
public pure returns (string memory positiveOutcome, string memory negativeOutcome)
function deployMarketPair(
uint256 proposalId,
address collateralToken,
uint256 liquidityAmount,
uint256 liquidityParameter,
uint256 tradingPeriod,
BetType betType
) external returns (uint256 marketId)
LMSR Pricing:
function calculateCost(
uint256 passShares,
uint256 failShares,
int256 sharesDelta,
bool buyingPass
) public view returns (uint256)
function getPrice(uint256 marketId, bool isPass) public view returns (uint256)
PrivacyCoordinator.sol¶
Purpose: Manages privacy and anti-collusion features
Key Features:
- Nightmarket-style position encryption
- MACI-style key-change messages
- Poseidon hash commitments
- Groth16 zkSNARK verification
- Batch processing
Privacy Flow:
// 1. Register public key
function registerKey(uint256 publicKeyX, uint256 publicKeyY) external
// 2. Submit encrypted position
function submitEncryptedPosition(
uint256 marketId,
bytes32 commitment,
bytes memory zkProof
) external
// 3. Optional: Change key to prevent collusion
function submitKeyChange(
bytes memory encryptedMessage,
bytes memory newPublicKey
) external
OracleResolver.sol¶
Purpose: Multi-stage oracle resolution with dispute mechanism
Key Features:
- Designated reporter system
- Bond-based incentives
- Challenge mechanism
- UMA escalation
- Evidence requirements
Resolution Flow:
// 1. Submit report
function submitReport(
uint256 proposalId,
uint256 passValue,
uint256 failValue,
string memory evidenceURI
) external payable
// 2. Challenge (if needed)
function challengeReport(
uint256 proposalId,
string memory challengeReason,
string memory counterEvidenceURI
) external payable
// 3. Escalate to UMA (if challenged)
function escalateToUMA(uint256 proposalId) external
RagequitModule.sol¶
Purpose: Minority protection mechanism
Key Features:
- Moloch-style ragequit
- Proportional treasury share
- Time-windowed execution
- Prevents forced participation
Key Functions:
function ragequit(
uint256 proposalId,
uint256 tokenAmount
) external
function calculateRagequitValue(
uint256 tokenAmount
) public view returns (uint256)
Contract Interactions¶
Proposal Submission Flow¶
// 1. User submits proposal
ProposalRegistry.submitProposal{value: 50 ether}(...)
-> Emits ProposalSubmitted event
-> FutarchyGovernor notified
// 2. After review period
FutarchyGovernor.activateProposal(proposalId)
-> ConditionalMarketFactory.createMarket(proposalId)
-> Markets created with PASS/FAIL tokens
Trading Flow¶
// 1. User registers with privacy coordinator
PrivacyCoordinator.registerKey(pubKeyX, pubKeyY)
// 2. User submits encrypted trade
PrivacyCoordinator.submitEncryptedPosition(marketId, commitment, proof)
-> Validates zkProof
-> Adds to batch
// 3. Batch processing
PrivacyCoordinator.processBatch()
-> ConditionalMarketFactory.executeTrades(...)
-> Prices updated via LMSR
Resolution Flow¶
// 1. Oracle reports
OracleResolver.submitReport{value: 100 ether}(proposalId, passValue, failValue, evidence)
// 2. Challenge period (2 days)
OracleResolver.challengeReport{value: 150 ether}(proposalId, reason, counterEvidence)
// 3. Finalization
FutarchyGovernor.finalizeProposal(proposalId)
-> ConditionalMarketFactory.resolveMarket(marketId, outcome)
-> Users can redeem tokens
Security Features¶
Access Control¶
// Role-based access control
modifier onlyGovernor()
modifier onlyGuardian()
modifier onlyProposer(uint256 proposalId)
// Multi-sig guardian
address[] public guardians;
uint256 public guardianThreshold;
Bond System¶
// Proposal bond: 50 ETC
uint256 public constant PROPOSAL_BOND = 50 ether;
// Oracle bond: 100 ETC
uint256 public constant ORACLE_BOND = 100 ether;
// Challenge bond: 150 ETC
uint256 public constant CHALLENGE_BOND = 150 ether;
Spending Limits¶
// Per-proposal limit
uint256 public constant MAX_PROPOSAL_AMOUNT = 50_000 ether;
// Daily aggregate limit
uint256 public constant DAILY_SPENDING_LIMIT = 100_000 ether;
Timelock¶
// Minimum timelock before execution
uint256 public constant TIMELOCK_PERIOD = 2 days;
// Challenge period
uint256 public constant CHALLENGE_PERIOD = 2 days;
Gas Optimization¶
Storage Optimization¶
// Pack struct fields efficiently
struct Proposal {
address proposer; // 20 bytes
uint96 fundingAmount; // 12 bytes (fits in same slot)
uint256 id; // new slot
// ...
}
Batch Operations¶
// Process multiple trades in single transaction
function processBatch(Trade[] calldata trades) external {
for (uint256 i = 0; i < trades.length; i++) {
_processTrade(trades[i]);
}
}
View Functions¶
// Use view for read-only operations
function getProposalStatus(uint256 proposalId)
external
view
returns (ProposalStatus)
{
return proposals[proposalId].status;
}
Testing Contracts¶
Unit Tests¶
describe("ProposalRegistry", function () {
it("should submit proposal with correct bond", async function () {
const tx = await proposalRegistry.submitProposal(
"Title",
"Description",
ethers.parseEther("1000"),
recipient.address,
1,
{ value: ethers.parseEther("50") }
);
await expect(tx)
.to.emit(proposalRegistry, "ProposalSubmitted");
});
});
Integration Tests¶
describe("End-to-End Proposal Flow", function () {
it("should execute approved proposal", async function () {
// Submit proposal
await proposalRegistry.submitProposal(...);
// Activate proposal
await futarchyGovernor.activateProposal(0);
// Trade on market
await privacyCoordinator.submitEncryptedPosition(...);
// Resolve
await oracleResolver.submitReport(...);
// Execute
await futarchyGovernor.executeProposal(0);
});
});
Deployment¶
See deployment scripts for details.
// scripts/deploy.js
async function main() {
// Deploy in correct order
const WelfareMetricRegistry = await ethers.deployContract("WelfareMetricRegistry");
await WelfareMetricRegistry.waitForDeployment();
const ProposalRegistry = await ethers.deployContract("ProposalRegistry");
await ProposalRegistry.waitForDeployment();
// ... deploy other contracts
const FutarchyGovernor = await ethers.deployContract(
"FutarchyGovernor",
[welfareRegistry.target, proposalRegistry.target, /* ... */]
);
console.log("FutarchyGovernor deployed to:", FutarchyGovernor.target);
}
Upgradeability¶
Using UUPS proxy pattern:
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract FutarchyGovernor is UUPSUpgradeable {
function _authorizeUpgrade(address newImplementation)
internal
override
onlyGovernor
{}
}