Skip to content

Commit

Permalink
Adding basic intended functionality and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nanspro committed Feb 4, 2019
1 parent c384bb3 commit baeee6f
Show file tree
Hide file tree
Showing 2 changed files with 389 additions and 0 deletions.
284 changes: 284 additions & 0 deletions contracts/ExitHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,64 @@ contract ExitHandler is DepositHandler {
uint256 amount
);

event LimboExitStarted(bytes32 indexed exitId, uint256 color);
event LimboExitChallengePublished(bytes32 indexed exitId, address indexed _from, uint8 _challengeNumber, uint8 _inputNumber);

struct Exit {
uint256 amount;
uint16 color;
address owner;
bool finalized;
uint32 priorityTimestamp;
uint256 stake;
bool isLimbo;
}

struct LimboExit {
LimboIn[] input;
LimboOut[] output;
bool finalized;
uint256 stake;
address exitor;
bool isValid;
LimboChallenge[] challenge;
}

struct LimboIn {
address owner;
bool isPegged;
bool exitable;
}

struct LimboOut {
uint256 amount;
address owner;
bool isPegged;
uint256 color;
bool exitable;
}

struct LimboChallenge {
address owner;
uint8 inputNo;
bool resolved;
}

uint256 public exitDuration;
uint256 public limboPeriod;
uint256 public piggybackStake;
uint256 public challengeStake;
uint256 public exitStake;
uint256 public nftExitCounter;

uint256 public constant LimboJoinDelay = (12 seconds);

/**
* UTXO → Exit mapping. Contains exits for both NFT and ERC20 colors
*/
mapping(bytes32 => Exit) public exits;
mapping(bytes32 => LimboExit) public limboExits;
mapping(bytes22 => bool) public succesfulLimboExits;

function initializeWithExit(
Bridge _bridge,
Expand All @@ -65,6 +106,249 @@ contract ExitHandler is DepositHandler {
exitDuration = _exitDuration;
}

function startLimboExit(bytes memory inTxData)
public payable returns (bytes32 utxoId) {
require(msg.value >= exitStake, "Not enough ether sent to pay for exit stake");
TxLib.Tx memory transferTx = TxLib.parseTx(inTxData);

// assuming tx have one input and one output only
uint8 _outputIndex = 0;
uint8 _inputIndex = 0;

TxLib.Output memory out = transferTx.outs[_outputIndex];

mapping(uint8 => LimboIn) public inputs;
mapping(uint8 => LimboOut) public outputs;

LimboOut memory output;
outputs[_outputIndex].owner = out.owner;
outputs[_outputIndex].color = out.color;
outputs[_outputIndex].amount = out.value;
outputs[_outputIndex].isPegged = false;
outputs[_outputIndex].exitable = true;

inputs[_inputIndex].isPegged = false;
inputs[_inputIndex].exitable = true;

bytes32 inTxHash = keccak256(inTxData);

bytes32 utxoId = bytes32(uint256(_outputIndex) << 120 | uint120(uint256(inTxHash)));
uint256 priority;

if (isNft(out.color)) {
priority = (nftExitCounter << 128) | uint128(uint256(utxoId));
nftExitCounter++;
} else {
priority = getERC20ExitPriority(*, utxoId, txPos);
}
limboExits[utxoId] = LimboExit({
output: outputs,
input: inputs,
finalized: false,
stake: exitStake,
exitor: msg.sender,
isValid: true,
challenges:{}
});

emit LimboExitStarted(
inTxHash,
out.color
);
tokens[out.color].insert(priority);

return utxoId;
}

function joinLimboExit(bytes32 exitId, uint8 _index) public payable {
require(msg.value >= piggybackStake, "Not enough ether sent to join the exit");

address owner = msg.sender;
LimboExit memory limboExit = limboExits[exitId];

if (limboExit.input[_index].owner == owner){
// input is piggybacking
require(limboExit.input[_index].isPegged = false, "Already joined the exit");

limboExit.input[_index].isPegged = true;
} else if (limboExit.output[_index].owner == owner) {
// output is piggybacking
require(limboExit.output[_index].isPegged = false, "Already joined the exit");

limboExit.output[_index].isPegged = true;
}
}

function putChallengeOnLimboExitInput(
bytes32 exitId,
uint8 _inputIndex
) public payable returns (bool success) {
require(msg.value >= challengeStake);
LimboExit memory exit = limboExits[exitId];
require(exit.isValid == true);
for (uint8 i = 0; i < exit.challenge.length; i++) {
require(_inputIndex != exit.challenge[i].inputNo);
}
LimboChallenge memory limboInputChallenge;
limboInputChallenge.from = msg.sender;
limboInputChallenge.inputNo = _inputIndex;
limboInputChallenge.resolved = false;
exit.challenge.push(limboInputChallenge);
emit LimboExitChallengePublished(exitId, msg.sender, uint8(exit.challenge.length-1), _inputIndex);
return true;
}

function challengeLimboExitByInclusionProof(
bytes32 exitId,
bytes inTxData, uint8 inputNo)
public payable {
require(msg.value >= challengeStake, "Not enough ether sent to challenge exit");
LimboExit memory limboExit = limboExits[exitId];
bytes32 inTxHash = keccak256(inTxdata);
require(limboExit.txHash == inTxHash);
require(limboExit.isValid == true);

require(block.timestamp <= limboExit.timePublished + LimboChallangesDelay);
TxLib.Tx memory transferTx = Tx.parseTx(inTxData);

// check if this tx is included or not
// TxLib.Tx memory includedTx = checkForValidityAndInclusion(blockNumber, includedTxData, includedProof);

// not a valid tx because tx is included in the chain
// will block whole tx from exiitng
limboExit.isValid = false;
// payments?
}

function challengeLimboExitByInputSpend(
bytes32 exitId,
bytes inTxData, uint8 inInputNo,
bytes includedTxData, bytes includedProof, uint8 includedInputNo, uint32 blockNumber)
public payable {
require(msg.value >= challengeStake, "Not enough ether sent to challenge exit");
LimboExit memory limboExit = limboExits[exitId];
bytes32 inTxHash = keccak256(inTxdata);

require(limboExit.txHash == inTxHash);
require(limboExit.isValid == true);
require(block.timestamp <= limboExit.timePublished + LimboChallangesDelay);

TxLib.Tx memory transferTx = Tx.parseTx(inTxData);
TxLib.Tx memory includedTx = checkForValidityAndInclusion(blockNumber, includedTxData, includedProof);

require(transferTx.sender == includedTx.sender);
TxLib.Input memory exitingInput = transferTx.inputs[inInputNo];
TxLib.Input memory includedInput = includedTx.inputs[includedInputNo];
require(exitingInput.blockNumber == includedInput.blockNumber);
require(exitingInput.amount == includedInput.amount);

// not a valid tx because canonical
// will block spent inputs from exiitng
limboExit.isValid = false;
// payments?
}

function challengeLimboExitByOutputSpend(
bytes32 exitId,
bytes inTxData, uint8 inOutputNo,
bytes includedTxData, bytes includedProof, uint8 includedInputNo, uint32 blockNumber)
public payable {
require(msg.value >= challengeStake, "Not enough ether sent to challenge exit");
LimboExit memory limboExit = limboExits[exitId];
bytes32 inTxHash = keccak256(inTxdata);

require(limboExit.txHash == inTxHash);
require(limboExit.isValid == true);
require(block.timestamp <= limboExit.timePublished + LimboChallangesDelay);

TxLib.Tx memory transferTx = Tx.parseTx(inTxData);
TxLib.Tx memory includedTx = checkForValidityAndInclusion(blockNumber, includedTxData, includedProof);

require(transferTx.sender == includedTx.sender);

// which piggybacked output of exit
TxLib.Input memory exitingOutput = transferTx.outputs[inOutputNo];
TxLib.Input memory includedInput = includedTx.inputs[includedInputNo];
require(exitingInput.blockNumber == includedInput.blockNumber);
require(exitingOutput.amount == includedInput.amount);

// not a valid tx because not exitable
// will block spent outputs from exiitng
limboExit.isValid = false;
// payments?
}

function challengeLimboExitByNonCanonicalInput(
bytes32 exitId,
bytes inTxData, uint8 inInputNo,
bytes includedTxData, bytes includedProof, uint8 includedOutputNo, uint32 blockNumber)
public payable {
require(msg.value >= challengeStake, "Not enough ether sent to challenge exit");
LimboExit memory limboExit = limboExits[exitId];
bytes32 inTxHash = keccak256(inTxdata);

require(limboExit.txHash == inTxHash);
require(limboExit.isValid == true);
require(block.timestamp <= limboExit.timePublished + LimboChallangesDelay);

TxLib.Tx memory transferTx = Tx.parseTx(inTxData);
TxLib.Tx memory includedTx = checkForValidityAndInclusion(blockNumber, includedTxData, includedProof);

require(transferTx.sender == includedTx.sender);

// which piggybacked input of exit
TxLib.Input memory exitingIntput = transferTx.inputs[inIntputNo];
TxLib.Output memory includedOutput = includedTx.outputs[includedOutputNo];
require(exitingInput.blockNumber == includedInput.blockNumber);
require(exitingOutput.amount == includedInput.amount);

// not a valid tx because input was not created by a canonical tx
// will block non canonical inputs from exiitng
limboExit.isValid = false;
// payments?
}

function resolveChallengeOnLimbo(
bytes32 exitId, bytes inTxData, uint256 challengeNo,
bytes includedTxData, bytes includedProof, uint8 includedOutputNo, uint32 blockNumber
) public {

LimboExit memory limboExit = limboExits[exitId];
LimboChallenge memory challenge = limboExit.challenge[challengeNo];

bytes32 inTxHash = keccak256(inTxdata);
require(limboExit.isValid == true);

TxLib.Tx memory exitingTx = Tx.parseTx(inTxData);
TxLib.Input memory exitingInput = exitingTx.input[challenge.inputNo];

// check for validity and inclusion?
challenge.resolved = true;
}

function finalizeTopLimboExit(uint16 _color) public {
bytes32 utxoId;
uint256 exitableAt;
(utxoId, exitableAt) = getNextExit(_color);

require(exitableAt <= block.timestamp, "The top exit can not be exited yet");
require(tokens[_color].currentSize > 0, "The exit queue for color is empty");

LimboExit memory currentExit = limboExits[utxoId];
if (limboExit.isValid == true){
// assuming 1 output
LimboOut memory out = limboExit.output[0];
uint256 amount;
if (out.exitable){
amount = limboExit.stake + piggybackStake;
tokens[out.color].addr.transferFrom(address(this), out.owner, amount);
} else {
limboExit.isValid = false;
}
}
delete limboExits[utxoId];
}

function startExit(
bytes32[] memory _youngestInputProof, bytes32[] memory _proof,
uint8 _outputIndex, uint8 _inputIndex
Expand Down
Loading

0 comments on commit baeee6f

Please sign in to comment.