Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

De-Prize prediction markets #283

Merged
merged 32 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
42bc5c6
betting market
jaderiverstokes Dec 5, 2024
96ef7b6
wip
jaderiverstokes Dec 10, 2024
58be846
max outcomes to 128
jaderiverstokes Dec 12, 2024
b1fbf43
wip
jaderiverstokes Dec 13, 2024
9a9f994
wip
jaderiverstokes Dec 27, 2024
c013d6c
adds prediction market deploy script
jaderiverstokes Dec 28, 2024
2d3acb7
abi one liners
jaderiverstokes Dec 28, 2024
90bfaf1
Merge branch 'main' into jade/bet
jaderiverstokes Dec 28, 2024
d221b24
preparing ispace firefly deprize
jaderiverstokes Jan 9, 2025
863967a
Merge branch 'main' into jade/bet
jaderiverstokes Jan 9, 2025
dd66a40
adding arbsep contracts
jaderiverstokes Jan 10, 2025
496b4d9
updates to LMSR with twap
jaderiverstokes Jan 10, 2025
1db23cf
wip adding arb sep
jaderiverstokes Jan 13, 2025
4efd909
wip
jaderiverstokes Jan 14, 2025
7a2f991
Merge branch 'main' into jade/bet
jaderiverstokes Jan 14, 2025
9e2023f
wip
jaderiverstokes Jan 14, 2025
0529527
Merge branch 'main' into jade/bet
jaderiverstokes Jan 14, 2025
9d213ce
update gitignore
jaderiverstokes Jan 14, 2025
fd49990
wip
jaderiverstokes Jan 14, 2025
8f07aba
wip
jaderiverstokes Jan 15, 2025
710a944
Merge branch 'main' into jade/bet
jaderiverstokes Jan 15, 2025
762a30e
wip
jaderiverstokes Jan 16, 2025
f5c1b40
Merge branch 'main' into jade/bet
jaderiverstokes Jan 24, 2025
a44c27c
v5 migration
jaderiverstokes Jan 24, 2025
ef7d2e8
cleanup
jaderiverstokes Jan 24, 2025
b21dca2
fix build error
jaderiverstokes Jan 28, 2025
82bc18f
cleanup
jaderiverstokes Jan 28, 2025
d66ecbe
build error
jaderiverstokes Jan 28, 2025
e0d5430
cleanup
jaderiverstokes Jan 28, 2025
09c906c
build error
jaderiverstokes Jan 28, 2025
bda1d55
Merge branch 'main' into jade/bet
jaderiverstokes Jan 28, 2025
4751dc7
cleanup
jaderiverstokes Jan 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions prediction/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
REACT_APP_OPERATOR_MNEMONIC='myth like bonus scare over problem client lizard pioneer submit female collect'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it okay to publish this on github?

REACT_APP_OPERATOR_ADDRESS=0x08B3e694caA2F1fcF8eF71095CED1326f3454B89
REACT_APP_ORACLE_ADDRESS=0x08B3e694caA2F1fcF8eF71095CED1326f3454B89
REACT_APP_FUNDING=1000000000000000
REACT_APP_INFURA_ID=
REACT_APP_NETWORK_ID=
REACT_APP_NETWORK=sepolia
2 changes: 2 additions & 0 deletions prediction/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
src/abi/
package-lock.json
17 changes: 17 additions & 0 deletions prediction/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Prediction Markets


## Setup
```
npm install
```

## Deployment
```shell
# Deploy to local network
npx truffle migrate --network development --reset
# Deploy to arbitrum sepolia
npx truffle migrate --network arbsep --reset
# Deploy to arbitrum
npx truffle migrate --network arbitrum --reset
```
11 changes: 11 additions & 0 deletions prediction/contracts/AppDependencies.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pragma solidity ^0.5.1;

// NOTE: This file porpouse is just to make sure truffle compiles all of depending
// contracts when we are in development.

import '@gnosis.pm/conditional-tokens-market-makers/contracts/LMSRMarketMaker.sol';
import '@gnosis.pm/conditional-tokens-market-makers/contracts/LMSRMarketMakerFactory.sol';
import 'canonical-weth/contracts/WETH9.sol';

contract AppDependencies {
}
94 changes: 94 additions & 0 deletions prediction/contracts/LMSRWithTWAP.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: MIT
//pragma solidity ^0.8.0;

import '@gnosis.pm/conditional-tokens-market-makers/contracts/LMSRMarketMaker.sol';


contract LMSRWithTWAP is LMSRMarketMaker {
uint256 public startTime;
// Cumulative probabilities over time: sum(prob[i](t) * dt) from startTime to now.
uint256[] public cumulativeProbabilities;
uint256 public lastUpdateTime;

constructor() public {
uint256 outcomes = this.atomicOutcomeSlotCount();
cumulativeProbabilities = new uint256[](outcomes);
startTime = block.timestamp;
lastUpdateTime = block.timestamp;
}

/**
* @notice Updates the cumulativeProbabilities based on elapsed time and current probabilities.
*/
function updateCumulativeTWAP() public {
uint256 elapsed = block.timestamp - lastUpdateTime;
if (elapsed == 0) {
return;
}

// Get current outcome probabilities from the LMSR.
uint256 length = cumulativeProbabilities.length;

// Accumulate probabilities * time
for (uint8 i = 0; i < length; i++) {
// Assuming calcMarginalPrice returns a normalized probability in [0, 1] scaled to 1e18 (for example).
// If not normalized, you might need to normalize them first.
// For now, assume they sum to 1e18 and represent probabilities as fixed-point numbers.
uint256 currentPrice = this.calcMarginalPrice(i);
if (currentPrice == 0) {
continue;
}
cumulativeProbabilities[i] += currentPrice * elapsed;
}

lastUpdateTime = block.timestamp;
}

/**
* @notice Trades on the LMSR and updates TWAP before doing so.
* @param outcomeTokenAmounts The outcome to buy/sell.
* @param collateralLimit The amount to buy (positive) or sell (negative).
*/
function tradeWithTWAP(int[] memory outcomeTokenAmounts, int collateralLimit) public {
// Update TWAP before trade.
updateCumulativeTWAP();
require(outcomeTokenAmounts.length == cumulativeProbabilities.length, "Mismatched array lengths");


// Perform the trade on the LMSR using delegated call.
// Note: Make sure the caller has given allowances for collateral tokens or handle that logic externally.
this.trade(outcomeTokenAmounts, collateralLimit);
//bytes memory payload = abi.encodeWithSignature("trade(int256[],int256)", outcomeTokenAmounts, collateralLimit);
jaderiverstokes marked this conversation as resolved.
Show resolved Hide resolved
//(bool success, ) = address(marketMaker).call(payload);
//require(success, "Trade execution failed");

}

/**
* @notice Returns the current TWAP probabilities over the entire period from startTime to now.
* This is cumulativeProb / totalElapsedTime.
* @dev If needed, you can call updateCumulativeTWAP() first to get fresh values.
*/
function getTWAP() external view returns (uint256[] memory) {
uint256 totalTime = block.timestamp - startTime;
if (totalTime == 0) {
return cumulativeProbabilities;
}
uint256 length = cumulativeProbabilities.length;
uint256[] memory twap = new uint256[](length);

// To get the freshest TWAP, you'd need to incorporate the current probabilities
// since lastUpdateTime. For a read-only approximation, let's just return what is stored.
// If you want exact up-to-the-second TWAP, you'd replicate the logic in updateCumulativeTWAP()
// off-chain or implement a view function that simulates it.
//uint256[] memory currentPrices = marketMaker.calcMarginalPrice();
uint256 elapsed = block.timestamp - lastUpdateTime;

for (uint8 i = 0; i < length; i++) {
uint256 currentPrice = this.calcMarginalPrice(i);
uint256 adjustedCumulative = cumulativeProbabilities[i] + currentPrice * elapsed;
twap[i] = adjustedCumulative / totalTime;
}
return twap;
}
}
135 changes: 135 additions & 0 deletions prediction/contracts/LMSRWithTWAPFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
pragma solidity ^0.5.1;

import { IERC20 } from "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";

import { ConditionalTokens } from "@gnosis.pm/conditional-tokens-contracts/contracts/ConditionalTokens.sol";
import { CTHelpers } from "@gnosis.pm/conditional-tokens-contracts/contracts/CTHelpers.sol";
import { ConstructedCloneFactory } from "@gnosis.pm/util-contracts/contracts/ConstructedCloneFactory.sol";
import { LMSRWithTWAP } from "./LMSRWithTWAP.sol";
import { Whitelist} from '@gnosis.pm/conditional-tokens-market-makers/contracts/Whitelist.sol';
import { ERC1155TokenReceiver } from "@gnosis.pm/conditional-tokens-contracts/contracts/ERC1155/ERC1155TokenReceiver.sol";

//interface IERC20 {
jaderiverstokes marked this conversation as resolved.
Show resolved Hide resolved
//function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
//function approve(address spender, uint256 amount) external returns (bool);
//}


contract LMSRWithTWAPData {
address internal _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);


bytes4 internal constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
mapping(bytes4 => bool) internal _supportedInterfaces;


uint64 constant FEE_RANGE = 10**18;
event AMMCreated(uint initialFunding);
ConditionalTokens internal pmSystem;
IERC20 internal collateralToken;
bytes32[] internal conditionIds;
uint internal atomicOutcomeSlotCount;
uint64 internal fee;
uint internal funding;
Stage internal stage;
Whitelist internal whitelist;

uint[] internal outcomeSlotCounts;
bytes32[][] internal collectionIds;
uint[] internal positionIds;

enum Stage {
Running,
Paused,
Closed
}
}

contract LMSRWithTWAPFactory is ConstructedCloneFactory, LMSRWithTWAPData {
event LMSRWithTWAPCreation(address indexed creator, LMSRWithTWAP lmsrWithTWAP, ConditionalTokens pmSystem, IERC20 collateralToken, bytes32[] conditionIds, uint64 fee, uint funding);

LMSRWithTWAP public implementationMaster;

constructor() public {
implementationMaster = new LMSRWithTWAP();
}

function cloneConstructor(bytes calldata consData) external {
(
ConditionalTokens _pmSystem,
IERC20 _collateralToken,
bytes32[] memory _conditionIds,
uint64 _fee,
Whitelist _whitelist
) = abi.decode(consData, (ConditionalTokens, IERC20, bytes32[], uint64, Whitelist));

_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);

_supportedInterfaces[_INTERFACE_ID_ERC165] = true;
_supportedInterfaces[
ERC1155TokenReceiver(0).onERC1155Received.selector ^
ERC1155TokenReceiver(0).onERC1155BatchReceived.selector
] = true;

// Validate inputs
require(address(_pmSystem) != address(0) && _fee < FEE_RANGE);
pmSystem = _pmSystem;
collateralToken = _collateralToken;
conditionIds = _conditionIds;
fee = _fee;
whitelist = _whitelist;

atomicOutcomeSlotCount = 1;
outcomeSlotCounts = new uint[](conditionIds.length);
for (uint i = 0; i < conditionIds.length; i++) {
uint outcomeSlotCount = pmSystem.getOutcomeSlotCount(conditionIds[i]);
atomicOutcomeSlotCount *= outcomeSlotCount;
outcomeSlotCounts[i] = outcomeSlotCount;
}
require(atomicOutcomeSlotCount > 1, "conditions must be valid");

collectionIds = new bytes32[][](conditionIds.length);
_recordCollectionIDsForAllConditions(conditionIds.length, bytes32(0));

stage = Stage.Paused;
emit AMMCreated(funding);
}

function _recordCollectionIDsForAllConditions(uint conditionsLeft, bytes32 parentCollectionId) private {
if(conditionsLeft == 0) {
positionIds.push(CTHelpers.getPositionId(collateralToken, parentCollectionId));
return;
}

conditionsLeft--;

uint outcomeSlotCount = outcomeSlotCounts[conditionsLeft];

collectionIds[conditionsLeft].push(parentCollectionId);
for(uint i = 0; i < outcomeSlotCount; i++) {
_recordCollectionIDsForAllConditions(
conditionsLeft,
CTHelpers.getCollectionId(
parentCollectionId,
conditionIds[conditionsLeft],
1 << i
)
);
}
}

function createLMSRWithTWAP(ConditionalTokens pmSystem, IERC20 collateralToken, bytes32[] calldata conditionIds, uint64 fee, Whitelist whitelist, uint funding)
external
returns (LMSRWithTWAP lmsrWithTWAP)
{
lmsrWithTWAP = LMSRWithTWAP(createClone(address(implementationMaster), abi.encode(pmSystem, collateralToken, conditionIds, fee, whitelist)));
collateralToken.transferFrom(msg.sender, address(this), funding);
collateralToken.approve(address(lmsrWithTWAP), funding);
lmsrWithTWAP.changeFunding(int(funding));
lmsrWithTWAP.resume();
lmsrWithTWAP.transferOwnership(msg.sender);
emit LMSRWithTWAPCreation(msg.sender, lmsrWithTWAP, pmSystem, collateralToken, conditionIds, fee, funding);
}
}
23 changes: 23 additions & 0 deletions prediction/contracts/Migrations.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
pragma solidity >=0.4.21 <0.7.0;

contract Migrations {
address public owner;
uint public last_completed_migration;

constructor() public {
owner = msg.sender;
}

modifier restricted() {
if (msg.sender == owner) _;
}

function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}

function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
Empty file added prediction/contracts/README.md
Empty file.
6 changes: 6 additions & 0 deletions prediction/markets.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = [
{
questionId:
"0x0000000000000000000000000000000000000000000000000000000000000001",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this do? Do we only need 1 question ID for now?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read up from the gnosis conditional tokens docs, I guess for our usecase the question is always "who will complete the objective first?" and then we have the objective live separately? not really sure how to best structure that, any thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as we add new markets we can add here. we also need to supply a max number of outcomes at the start too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right now i maintain a map from question id to title as a json in the ui directory:
ui/const/betting/config.sepolia.json

},
];
3 changes: 3 additions & 0 deletions prediction/migrations/01_initial_migration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function (deployer) {
deployer.deploy(artifacts.require('Migrations'))
}
5 changes: 5 additions & 0 deletions prediction/migrations/02_pm_system.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = function (deployer) {
deployer.deploy(artifacts.require('ConditionalTokens'), {
overwrite: true,
})
}
3 changes: 3 additions & 0 deletions prediction/migrations/03_fixed_math.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function(deployer) {
deployer.deploy(artifacts.require("Fixed192x64Math"), { overwrite: false });
};
9 changes: 9 additions & 0 deletions prediction/migrations/04_lmsr_mm_factory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const Fixed192x64Math = artifacts.require("Fixed192x64Math");
const LMSRMarketMakerFactory = artifacts.require("LMSRMarketMakerFactory");
const LMSRMarketMaker = artifacts.require("LMSRMarketMaker");

module.exports = function(deployer) {
deployer.link(Fixed192x64Math, LMSRMarketMakerFactory);
deployer.link(Fixed192x64Math, LMSRMarketMaker);
deployer.deploy(LMSRMarketMakerFactory);
};
3 changes: 3 additions & 0 deletions prediction/migrations/05_collateral.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function(deployer) {
deployer.deploy(artifacts.require("WETH9"), { overwrite: false });
};
13 changes: 13 additions & 0 deletions prediction/migrations/06_prepare_conditions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const deployConfig = require('./utils/deployConfig')(artifacts)
const ConditionalTokens = artifacts.require('ConditionalTokens')

module.exports = function (deployer) {
deployer.then(async () => {
const MAX_OUTCOMES = 8
const pmSystem = await ConditionalTokens.deployed()
const markets = require('../markets.config')
for (const { questionId } of markets) {
await pmSystem.prepareCondition(deployConfig.oracle, questionId, MAX_OUTCOMES)
}
})
}
Loading
Loading