forked from butterygg/Predy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
1,425 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
// This file is part of Ghee. | ||
// | ||
// Ghee is a derivative work of ConditionalTokens by Gnosis. | ||
// | ||
// The original file was obtained from https://github.com/gnosis/conditional-tokens-contracts/tree/eeefca66eb46c800a9aaab88db2064a99026fde5. | ||
|
||
pragma solidity ^0.8.1; | ||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; | ||
import { CTHelpers } from "./CTHelpers.sol"; | ||
|
||
contract ConditionalTokens is ERC1155 { | ||
event ConditionPreparation( | ||
bytes32 indexed conditionId, | ||
address indexed oracle, | ||
bytes32 indexed questionId, | ||
uint outcomeSlotCount | ||
); | ||
|
||
event ConditionResolution( | ||
bytes32 indexed conditionId, | ||
address indexed oracle, | ||
bytes32 indexed questionId, | ||
uint outcomeSlotCount, | ||
uint[] payoutNumerators | ||
); | ||
|
||
event PositionSplit( | ||
address indexed stakeholder, | ||
IERC20 collateralToken, | ||
bytes32 indexed parentCollectionId, | ||
bytes32 indexed conditionId, | ||
uint[] partition, | ||
uint amount | ||
); | ||
|
||
event PositionsMerge( | ||
address indexed stakeholder, | ||
IERC20 collateralToken, | ||
bytes32 indexed parentCollectionId, | ||
bytes32 indexed conditionId, | ||
uint[] partition, | ||
uint amount | ||
); | ||
|
||
event PayoutRedemption( | ||
address indexed redeemer, | ||
IERC20 indexed collateralToken, | ||
bytes32 indexed parentCollectionId, | ||
bytes32 conditionId, | ||
uint[] indexSets, | ||
uint payout | ||
); | ||
|
||
mapping(bytes32 => uint[]) public payoutNumerators; | ||
mapping(bytes32 => uint) public payoutDenominator; | ||
|
||
constructor() ERC1155("") {} | ||
|
||
function prepareCondition(address oracle, bytes32 questionId, uint outcomeSlotCount) external { | ||
require(outcomeSlotCount <= 256, "Too many outcome slots"); | ||
require(outcomeSlotCount > 1, "There should be more than one outcome slot"); | ||
bytes32 conditionId = CTHelpers.getConditionId(oracle, questionId, outcomeSlotCount); | ||
require(payoutNumerators[conditionId].length == 0, "Condition already prepared"); | ||
payoutNumerators[conditionId] = new uint[](outcomeSlotCount); | ||
emit ConditionPreparation(conditionId, oracle, questionId, outcomeSlotCount); | ||
} | ||
|
||
function reportPayouts(bytes32 questionId, uint[] calldata payouts) external { | ||
uint outcomeSlotCount = payouts.length; | ||
require(outcomeSlotCount > 1, "There should be more than one outcome slot"); | ||
bytes32 conditionId = CTHelpers.getConditionId(msg.sender, questionId, outcomeSlotCount); | ||
require(payoutNumerators[conditionId].length == outcomeSlotCount, "Condition not prepared or found"); | ||
require(payoutDenominator[conditionId] == 0, "Payout denominator already set"); | ||
|
||
uint den = 0; | ||
for (uint i = 0; i < outcomeSlotCount; i++) { | ||
uint num = payouts[i]; | ||
den += num; | ||
require(payoutNumerators[conditionId][i] == 0, "Payout numerator already set"); | ||
payoutNumerators[conditionId][i] = num; | ||
} | ||
require(den > 0, "Payout is all zeroes"); | ||
payoutDenominator[conditionId] = den; | ||
emit ConditionResolution(conditionId, msg.sender, questionId, outcomeSlotCount, payoutNumerators[conditionId]); | ||
} | ||
|
||
function splitPosition( | ||
IERC20 collateralToken, | ||
bytes32 parentCollectionId, | ||
bytes32 conditionId, | ||
uint[] calldata partition, | ||
uint amount | ||
) external { | ||
require(partition.length > 1, "Got empty or singleton partition"); | ||
uint outcomeSlotCount = payoutNumerators[conditionId].length; | ||
require(outcomeSlotCount > 0, "Condition not prepared yet"); | ||
|
||
uint fullIndexSet = (1 << outcomeSlotCount) - 1; | ||
uint freeIndexSet = fullIndexSet; | ||
uint[] memory positionIds = new uint[](partition.length); | ||
uint[] memory amounts = new uint[](partition.length); | ||
for (uint i = 0; i < partition.length; i++) { | ||
uint indexSet = partition[i]; | ||
require(indexSet > 0 && indexSet < fullIndexSet, "Got invalid index set"); | ||
require((indexSet & freeIndexSet) == indexSet, "Partition not disjoint"); | ||
freeIndexSet ^= indexSet; | ||
positionIds[i] = CTHelpers.getPositionId(collateralToken, CTHelpers.getCollectionId(parentCollectionId, conditionId, indexSet)); | ||
amounts[i] = amount; | ||
} | ||
|
||
if (freeIndexSet == 0) { | ||
if (parentCollectionId == bytes32(0)) { | ||
require(collateralToken.transferFrom(msg.sender, address(this), amount), "Could not receive collateral tokens"); | ||
} else { | ||
_burn( | ||
msg.sender, | ||
CTHelpers.getPositionId(collateralToken, parentCollectionId), | ||
amount | ||
); | ||
} | ||
} else { | ||
_burn( | ||
msg.sender, | ||
CTHelpers.getPositionId(collateralToken, | ||
CTHelpers.getCollectionId(parentCollectionId, conditionId, fullIndexSet ^ freeIndexSet)), | ||
amount | ||
); | ||
} | ||
|
||
_mintBatch( | ||
msg.sender, | ||
positionIds, | ||
amounts, | ||
"" | ||
); | ||
emit PositionSplit(msg.sender, collateralToken, parentCollectionId, conditionId, partition, amount); | ||
} | ||
|
||
function mergePositions( | ||
IERC20 collateralToken, | ||
bytes32 parentCollectionId, | ||
bytes32 conditionId, | ||
uint[] calldata partition, | ||
uint amount | ||
) external { | ||
require(partition.length > 1, "Got empty or singleton partition"); | ||
uint outcomeSlotCount = payoutNumerators[conditionId].length; | ||
require(outcomeSlotCount > 0, "Condition not prepared yet"); | ||
|
||
uint fullIndexSet = (1 << outcomeSlotCount) - 1; | ||
uint freeIndexSet = fullIndexSet; | ||
uint[] memory positionIds = new uint[](partition.length); | ||
uint[] memory amounts = new uint[](partition.length); | ||
for (uint i = 0; i < partition.length; i++) { | ||
uint indexSet = partition[i]; | ||
require(indexSet > 0 && indexSet < fullIndexSet, "Got invalid index set"); | ||
require((indexSet & freeIndexSet) == indexSet, "Partition not disjoint"); | ||
freeIndexSet ^= indexSet; | ||
positionIds[i] = CTHelpers.getPositionId(collateralToken, CTHelpers.getCollectionId(parentCollectionId, conditionId, indexSet)); | ||
amounts[i] = amount; | ||
} | ||
_burnBatch( | ||
msg.sender, | ||
positionIds, | ||
amounts | ||
); | ||
|
||
if (freeIndexSet == 0) { | ||
if (parentCollectionId == bytes32(0)) { | ||
require(collateralToken.transfer(msg.sender, amount), "Could not send collateral tokens"); | ||
} else { | ||
_mint( | ||
msg.sender, | ||
CTHelpers.getPositionId(collateralToken, parentCollectionId), | ||
amount, | ||
"" | ||
); | ||
} | ||
} else { | ||
_mint( | ||
msg.sender, | ||
CTHelpers.getPositionId(collateralToken, | ||
CTHelpers.getCollectionId(parentCollectionId, conditionId, fullIndexSet ^ freeIndexSet)), | ||
amount, | ||
"" | ||
); | ||
} | ||
|
||
emit PositionsMerge(msg.sender, collateralToken, parentCollectionId, conditionId, partition, amount); | ||
} | ||
|
||
function redeemPositions(IERC20 collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint[] calldata indexSets) external { | ||
uint den = payoutDenominator[conditionId]; | ||
require(den > 0, "Result for condition not received yet"); | ||
uint outcomeSlotCount = payoutNumerators[conditionId].length; | ||
require(outcomeSlotCount > 0, "Condition not prepared yet"); | ||
|
||
uint totalPayout = 0; | ||
|
||
uint fullIndexSet = (1 << outcomeSlotCount) - 1; | ||
for (uint i = 0; i < indexSets.length; i++) { | ||
uint indexSet = indexSets[i]; | ||
require(indexSet > 0 && indexSet < fullIndexSet, "Got invalid index set"); | ||
uint positionId = CTHelpers.getPositionId(collateralToken, | ||
CTHelpers.getCollectionId(parentCollectionId, conditionId, indexSet)); | ||
|
||
uint payoutNumerator = 0; | ||
for (uint j = 0; j < outcomeSlotCount; j++) { | ||
if (indexSet & (1 << j) != 0) { | ||
payoutNumerator += payoutNumerators[conditionId][j]; | ||
} | ||
} | ||
|
||
uint payoutStake = balanceOf(msg.sender, positionId); | ||
if (payoutStake > 0) { | ||
totalPayout += payoutStake * payoutNumerator / den; | ||
_burn(msg.sender, positionId, payoutStake); | ||
} | ||
} | ||
|
||
if (totalPayout > 0) { | ||
if (parentCollectionId == bytes32(0)) { | ||
require(collateralToken.transfer(msg.sender, totalPayout), "Could not transfer payout to message sender"); | ||
} else { | ||
_mint(msg.sender, CTHelpers.getPositionId(collateralToken, parentCollectionId), totalPayout, ""); | ||
} | ||
} | ||
emit PayoutRedemption(msg.sender, collateralToken, parentCollectionId, conditionId, indexSets, totalPayout); | ||
} | ||
|
||
function getOutcomeSlotCount(bytes32 conditionId) external view returns (uint) { | ||
return payoutNumerators[conditionId].length; | ||
} | ||
|
||
function getConditionId(address oracle, bytes32 questionId, uint outcomeSlotCount) external pure returns (bytes32) { | ||
return CTHelpers.getConditionId(oracle, questionId, outcomeSlotCount); | ||
} | ||
|
||
function getCollectionId(bytes32 parentCollectionId, bytes32 conditionId, uint indexSet) external view returns (bytes32) { | ||
return CTHelpers.getCollectionId(parentCollectionId, conditionId, indexSet); | ||
} | ||
|
||
function getPositionId(IERC20 collateralToken, bytes32 collectionId) external pure returns (uint) { | ||
return CTHelpers.getPositionId(collateralToken, collectionId); | ||
} | ||
} |
Oops, something went wrong.