-
Notifications
You must be signed in to change notification settings - Fork 11
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
Changes from 8 commits
42bc5c6
96ef7b6
58be846
b1fbf43
9a9f994
c013d6c
2d3acb7
90bfaf1
d221b24
863967a
dd66a40
496b4d9
1db23cf
4efd909
7a2f991
9e2023f
0529527
9d213ce
fd49990
8f07aba
710a944
762a30e
f5c1b40
a44c27c
ef7d2e8
b21dca2
82bc18f
d66ecbe
e0d5430
09c906c
bda1d55
4751dc7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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' | ||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
src/abi/ | ||
package-lock.json |
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 | ||
``` |
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 { | ||
} |
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; | ||
} | ||
} |
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); | ||
} | ||
} |
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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module.exports = [ | ||
{ | ||
questionId: | ||
"0x0000000000000000000000000000000000000000000000000000000000000001", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: |
||
}, | ||
]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = function (deployer) { | ||
deployer.deploy(artifacts.require('Migrations')) | ||
} |
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, | ||
}) | ||
} |
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 }); | ||
}; |
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); | ||
}; |
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 }); | ||
}; |
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) | ||
} | ||
}) | ||
} |
There was a problem hiding this comment.
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?