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

Implement split and merge for ERC-1155 collateral tokens #58

Merged
merged 2 commits into from
Jul 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ before_install:
before_script: greenkeeper-lockfile-update
script:
- npm run lint
- npm run lint-contracts
- npm test
- npx codechecks
after_script: greenkeeper-lockfile-upload
193 changes: 175 additions & 18 deletions contracts/ConditionalTokens.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
pragma solidity ^0.5.1;
import { IERC20 } from "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
import { IERC1155TokenReceiver } from "./ERC1155/IERC1155TokenReceiver.sol";
import { ERC1155TokenReceiver } from "./ERC1155/ERC1155TokenReceiver.sol";
import { IERC1155 } from "./ERC1155/IERC1155.sol";
import { ERC1155 } from "./ERC1155/ERC1155.sol";


contract ConditionalTokens is ERC1155 {
contract ConditionalTokens is ERC1155, ERC1155TokenReceiver {

/// @dev Emitted upon the successful preparation of a condition.
/// @param conditionId The condition's ID. This ID may be derived from the other three parameters via ``keccak256(abi.encodePacked(oracle, questionId, outcomeSlotCount))``.
Expand Down Expand Up @@ -35,6 +37,15 @@ contract ConditionalTokens is ERC1155 {
uint[] partition,
uint amount
);
event PositionSplit(
address indexed stakeholder,
IERC1155 collateralToken,
uint collateralTokenID,
bytes32 indexed parentCollectionId,
bytes32 indexed conditionId,
uint[] partition,
uint amount
);
/// @dev Emitted when positions are successfully merged.
event PositionsMerge(
address indexed stakeholder,
Expand All @@ -44,6 +55,15 @@ contract ConditionalTokens is ERC1155 {
uint[] partition,
uint amount
);
event PositionsMerge(
address indexed stakeholder,
IERC1155 collateralToken,
uint collateralTokenID,
bytes32 indexed parentCollectionId,
bytes32 indexed conditionId,
uint[] partition,
uint amount
);
event PayoutRedemption(
address indexed redeemer,
IERC20 indexed collateralToken,
Expand All @@ -53,6 +73,8 @@ contract ConditionalTokens is ERC1155 {
uint payout
);

enum CollateralTypes { ERC20, ERC1155 }

/// Mapping key is an condition ID. Value represents numerators of the payout vector associated with the condition. This array is initialized with a length equal to the outcome slot count.
mapping(bytes32 => uint[]) public payoutNumerators;
mapping(bytes32 => uint) public payoutDenominator;
Expand Down Expand Up @@ -104,6 +126,69 @@ contract ConditionalTokens is ERC1155 {
bytes32 conditionId,
uint[] calldata partition,
uint amount
) external {
(uint fullIndexSet, uint freeIndexSet) = mintSet(msg.sender, CollateralTypes.ERC20, address(collateralToken), 0, parentCollectionId, conditionId, partition, 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,
getPositionId(collateralToken, parentCollectionId),
amount
);
}
} else {
_burn(
msg.sender,
getPositionId(collateralToken,
getCollectionId(parentCollectionId, conditionId, fullIndexSet ^ freeIndexSet)),
amount
);
}

emit PositionSplit(msg.sender, collateralToken, parentCollectionId, conditionId, partition, amount);
}

function split1155Position(
IERC1155 collateralToken,
uint collateralTokenID,
bytes32 parentCollectionId,
bytes32 conditionId,
uint[] calldata partition,
uint amount
) external {
(uint fullIndexSet, uint freeIndexSet) = mintSet(msg.sender, CollateralTypes.ERC1155, address(collateralToken), collateralTokenID, parentCollectionId, conditionId, partition, amount);

if (freeIndexSet == 0) {
if (parentCollectionId == bytes32(0)) {
collateralToken.safeTransferFrom(msg.sender, address(this), collateralTokenID, amount, "");
} else {
_burn(
msg.sender,
getPositionId(collateralToken, collateralTokenID, parentCollectionId),
amount
);
}
} else {
_burn(
msg.sender,
getPositionId(collateralToken, collateralTokenID,
getCollectionId(parentCollectionId, conditionId, fullIndexSet ^ freeIndexSet)),
amount
);
}

emit PositionSplit(msg.sender, collateralToken, collateralTokenID, parentCollectionId, conditionId, partition, amount);
}

function mergePositions(
IERC20 collateralToken,
bytes32 parentCollectionId,
bytes32 conditionId,
uint[] calldata partition,
uint amount
) external {
uint outcomeSlotCount = payoutNumerators[conditionId].length;
require(outcomeSlotCount > 0, "condition not prepared yet");
Expand All @@ -115,38 +200,40 @@ contract ConditionalTokens is ERC1155 {
require(indexSet > 0 && indexSet < fullIndexSet, "got invalid index set");
require((indexSet & freeIndexSet) == indexSet, "partition not disjoint");
freeIndexSet ^= indexSet;
_mint(
_burn(
msg.sender,
getPositionId(collateralToken, getCollectionId(parentCollectionId, conditionId, indexSet)),
amount,
""
amount
);
}

if (freeIndexSet == 0) {
if (parentCollectionId == bytes32(0)) {
require(collateralToken.transferFrom(msg.sender, address(this), amount), "could not receive collateral tokens");
require(collateralToken.transfer(msg.sender, amount), "could not send collateral tokens");
} else {
_burn(
_mint(
msg.sender,
getPositionId(collateralToken, parentCollectionId),
amount
amount,
""
);
}
} else {
_burn(
_mint(
msg.sender,
getPositionId(collateralToken,
getCollectionId(parentCollectionId, conditionId, fullIndexSet ^ freeIndexSet)),
amount
amount,
""
);
}

emit PositionSplit(msg.sender, collateralToken, parentCollectionId, conditionId, partition, amount);
emit PositionsMerge(msg.sender, collateralToken, parentCollectionId, conditionId, partition, amount);
}

function mergePositions(
IERC20 collateralToken,
function merge1155Positions(
IERC1155 collateralToken,
uint collateralTokenID,
bytes32 parentCollectionId,
bytes32 conditionId,
uint[] calldata partition,
Expand All @@ -164,33 +251,67 @@ contract ConditionalTokens is ERC1155 {
freeIndexSet ^= indexSet;
_burn(
msg.sender,
getPositionId(collateralToken, getCollectionId(parentCollectionId, conditionId, indexSet)),
getPositionId(collateralToken, collateralTokenID,
getCollectionId(parentCollectionId, conditionId, indexSet)),
amount
);
}

if (freeIndexSet == 0) {
if (parentCollectionId == bytes32(0)) {
require(collateralToken.transfer(msg.sender, amount), "could not send collateral tokens");
collateralToken.safeTransferFrom(address(this), msg.sender, collateralTokenID, amount, "");
} else {
_mint(
msg.sender,
getPositionId(collateralToken, parentCollectionId),
getPositionId(collateralToken, collateralTokenID, parentCollectionId),
amount,
""
);
}
} else {
_mint(
msg.sender,
getPositionId(collateralToken,
getPositionId(collateralToken, collateralTokenID,
getCollectionId(parentCollectionId, conditionId, fullIndexSet ^ freeIndexSet)),
amount,
""
);
}

emit PositionsMerge(msg.sender, collateralToken, parentCollectionId, conditionId, partition, amount);
emit PositionsMerge(msg.sender, collateralToken, collateralTokenID, parentCollectionId, conditionId, partition, amount);
}

function mintSet(
address recipient,
CollateralTypes collateralType,
address collateralToken,
uint collateralTokenID,
bytes32 parentCollectionId,
bytes32 conditionId,
uint[] memory partition,
uint amount
) private returns (uint fullIndexSet, uint freeIndexSet) {
{
uint outcomeSlotCount = payoutNumerators[conditionId].length;
require(outcomeSlotCount > 0, "condition not prepared yet");
fullIndexSet = (1 << outcomeSlotCount) - 1;
freeIndexSet = fullIndexSet;
}

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;
_mint(
recipient,
collateralType == CollateralTypes.ERC20 ?
getPositionId(IERC20(collateralToken), getCollectionId(parentCollectionId, conditionId, indexSet)) :
getPositionId(IERC1155(collateralToken), collateralTokenID, getCollectionId(parentCollectionId, conditionId, indexSet)),
amount,
""
);
}
}

function redeemPositions(IERC20 collateralToken, bytes32 parentCollectionId, bytes32 conditionId, uint[] calldata indexSets) external {
Expand Down Expand Up @@ -232,6 +353,37 @@ contract ConditionalTokens is ERC1155 {
emit PayoutRedemption(msg.sender, collateralToken, parentCollectionId, conditionId, indexSets, totalPayout);
}

function onERC1155Received(
address operator,
address /* from */,
uint256 id,
uint256 value,
bytes calldata data
)
external
returns (bytes4)
{
if(operator != address(this)) {
(bytes32 conditionId, uint[] memory partition) = abi.decode(data, (bytes32, uint[]));
mintSet(operator, CollateralTypes.ERC1155, msg.sender, id, bytes32(0), conditionId, partition, value);
}

return this.onERC1155Received.selector;
}

function onERC1155BatchReceived(
address operator,
address /* from */,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
)
external
returns (bytes4)
{
revert("operation not supported");
}

/// @dev Gets the outcome slot count of a condition.
/// @param conditionId ID of the condition.
/// @return Number of outcome slots associated with a condition, or zero if condition has not been prepared yet.
Expand Down Expand Up @@ -263,4 +415,9 @@ contract ConditionalTokens is ERC1155 {
/// @param collectionId ID of the outcome collection associated with this position.
function getPositionId(IERC20 collateralToken, bytes32 collectionId) public pure returns (uint) {
return uint(keccak256(abi.encodePacked(collateralToken, collectionId)));
}}
}

function getPositionId(IERC1155 collateralToken, uint collateralTokenID, bytes32 collectionId) public pure returns (uint) {
return uint(keccak256(abi.encodePacked(collateralToken, collateralTokenID, collectionId)));
}
}
4 changes: 0 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,6 @@
"*.js": [
"eslint --fix",
"git add"
],
"*.sol": [
"solium --fix -f",
"git add"
]
}
}
Loading