From 3e1d164b51038023071447844cd48f5c5ee604f9 Mon Sep 17 00:00:00 2001 From: fnatic Date: Sat, 2 Feb 2019 06:43:36 +0530 Subject: [PATCH 1/6] Adding basic intended functionality and tests --- contracts/ExitHandler.sol | 284 ++++++++++++++++++++++++++++++++++++++ test/exitHandler.js | 105 ++++++++++++++ 2 files changed, 389 insertions(+) diff --git a/contracts/ExitHandler.sol b/contracts/ExitHandler.sol index 69dc512..5ec90ea 100644 --- a/contracts/ExitHandler.sol +++ b/contracts/ExitHandler.sol @@ -29,6 +29,9 @@ 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; @@ -36,16 +39,54 @@ contract ExitHandler is DepositHandler { 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, @@ -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 diff --git a/test/exitHandler.js b/test/exitHandler.js index 8834742..e8d9cee 100644 --- a/test/exitHandler.js +++ b/test/exitHandler.js @@ -147,6 +147,111 @@ contract('ExitHandler', (accounts) => { assert(aliceBalanceBefore.add(new BN(50)).eq(aliceBalanceAfter)); }); + it('Should allow to challenge tx and prevent exit', async () => { + const period = await submitNewPeriod([depositTx, transferTx]); + const transferProof = period.proof(transferTx); + + //bob spends utxo1 by sending it to charlie + const spendTx = Tx.transfer( + [new Input(new Outpoint(transferTx.hash(), 0))], + [new Output(50, charlie)] + ).sign([bobPriv]); + + const exitStake = exitHandler.exitStake(); + const piggybackStake = exitHandler.piggybackStake(); + const challengeStake = exitHandler.challengeStake(); + + const inTxData = Tx.parseToParams(transferTx); + const spendTxData = Tx.parseToParams(spendTx); + + // any user can start the exit for this transaction + exitId = await exitHandler.startLimboExit(inTxData, {from: bob, value: exitStake}); + + // Bob piggybacks and joins a Limo exit by its id + await exitHandler.joinLimboExit(exitId, 0, {from: bob, value: piggybackStake}); + + const bobBalanceBefore = await nativeToken.balanceOf(bob); + await exitHandler.challengeLimboExit(exitId, spendTxData, {from: pete, value: challengeStake}); + + const challengeTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); + await time.increaseTo(challengeTime); + + await exitHandler.finalizeTopLimboExit(nativeTokenColor); + + // const responseTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); + // await time.increaseTo(responseTime); + + const bobBalanceAfter = await nativeToken.balanceOf(bob); + assert(bobBalanceAfter.eq(bobBalanceBefore)); + }); + + it('Should allow user to challenge double spent inflight tx', async () => { + // const period = await submitNewPeriod([depositTx); + + //alice double spends utxo1 by sending it to charlie + const spendTx = Tx.transfer( + [new Input(new Outpoint(depositTx.hash(), 0))], + [new Output(50, charlie)] + ).sign([alicePriv]); + + const exitStake = exitHandler.exitStake(); + const piggybackStake = exitHandler.piggybackStake(); + const challengeStake = exitHandler.challengeStake(); + + const inTxData = Tx.parseToParams(transferTx); + const spendTxData = Tx.parseToParams(spendTx); + + exitId = await exitHandler.startLimboExit(inTxData, {from: bob, value: exitStake}); + + // Bob piggybacks and joins a Limo exit by its id + await exitHandler.joinLimboExit(exitId, 0, {from: bob, value: piggybackStake}); + + const bobBalanceBefore = await nativeToken.balanceOf(bob); + // challenge to tx's canonicity + await exitHandler.challengeLimboExit(exitId, spendTxData, {from: pete, value: challengeStake}); + + const challengeTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); + await time.increaseTo(challengeTime); + + await exitHandler.finalizeTopLimboExit(nativeTokenColor); + + // const responseTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); + // await time.increaseTo(responseTime); + + const bobBalanceAfter = await nativeToken.balanceOf(bob); + assert(bobBalanceAfter.eq(bobBalanceBefore)); + }); + + it('Should resolve a challenge and exit', async () => { + const period = await submitNewPeriod([depositTx); + const depositProof = period.proof(depositTx); + + const exitStake = exitHandler.exitStake(); + const piggybackStake = exitHandler.piggybackStake(); + const challengeStake = exitHandler.challengeStake(); + + const inTxData = Tx.parseToParams(transferTx); + + exitId = await exitHandler.startLimboExit(inTxData, {from: bob, value: exitStake}); + + // Alice piggybacks and joins a Limo exit by its id + await exitHandler.joinLimboExit(exitId, 0, {from: alice, value: piggybackStake}); + + const aliceBalanceBefore = await nativeToken.balanceOf(alice); + await exitHandler.challengeLimboExit(exitId, randomTx, {from: pete, value: challengeStake}); + + const challengeTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); + await time.increaseTo(challengeTime); + + await exitHandler.resolveChallengeOnInput(exitId, inTxData, 0, depositData, depositProof, 0, blockNo); + + const responseTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); + await time.increaseTo(responseTime); + + const aliceBalanceAfter = await nativeToken.balanceOf(alice); + assert(aliceBalanceAfter.eq(aliceBalanceBefore.add(new BN(50)))); + }); + it('Should allow to exit deposit utxo', async () => { const period = await submitNewPeriod([depositTx]); From 22ef038190ca199539151a0a05d84633a8adc981 Mon Sep 17 00:00:00 2001 From: fnatic Date: Tue, 5 Feb 2019 07:22:45 +0530 Subject: [PATCH 2/6] Handling challenges and resolvers --- contracts/ExitHandler.sol | 250 ++++++++++++++++++-------------------- 1 file changed, 119 insertions(+), 131 deletions(-) diff --git a/contracts/ExitHandler.sol b/contracts/ExitHandler.sol index 5ec90ea..d8756f8 100644 --- a/contracts/ExitHandler.sol +++ b/contracts/ExitHandler.sol @@ -46,16 +46,15 @@ contract ExitHandler is DepositHandler { LimboIn[] input; LimboOut[] output; bool finalized; - uint256 stake; address exitor; - bool isValid; - LimboChallenge[] challenge; + bool isValid; // a tx is valid if it is canonical and exitable and comes from valid txs } struct LimboIn { address owner; bool isPegged; bool exitable; + LimboChallenge challenge; } struct LimboOut { @@ -64,11 +63,11 @@ contract ExitHandler is DepositHandler { bool isPegged; uint256 color; bool exitable; + LimboChallenge challenge; } struct LimboChallenge { address owner; - uint8 inputNo; bool resolved; } @@ -106,32 +105,36 @@ contract ExitHandler is DepositHandler { exitDuration = _exitDuration; } - function startLimboExit(bytes memory inTxData) - public payable returns (bytes32 utxoId) { + function startLimboExit(bytes memory inTxData) public payable returns (bytes32 utxoId) { + + // exitor assumes tx to be canonical 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; + for (uint256 i =0; i < transferTx.outs.length; i++){ + TxLib.Output memory out = transferTx.outs[i]; + outputs[i] = LimboOut({ + amount: out.value, + owner: out.owner, + isPegged: false, + color: out.color, + exitable: true }); + } + for (uint256 i =0; i < transferTx.ins.length; i++){ + TxLib.Input memory ins = transferTx.ins[i]; + inputs[i] = LimboIn({ + owner: out.owner, + isPegged: false, + exitable: true }); + } bytes32 inTxHash = keccak256(inTxData); + // for now assume outputIndex 0 bytes32 utxoId = bytes32(uint256(_outputIndex) << 120 | uint120(uint256(inTxHash))); uint256 priority; @@ -145,10 +148,9 @@ contract ExitHandler is DepositHandler { output: outputs, input: inputs, finalized: false, - stake: exitStake, exitor: msg.sender, isValid: true, - challenges:{} + challenges:[] }); emit LimboExitStarted( @@ -179,24 +181,24 @@ contract ExitHandler is DepositHandler { } } - 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 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 Challenge; + // Challenge.from = msg.sender; + // Challenge.inputNo = _inputIndex; + // Challenge.resolved = false; + // exit.challenge.push(Challenge); + // emit LimboExitChallengePublished(exitId, msg.sender, uint8(exit.challenge.length-1), _inputIndex); + // return true; + // } function challengeLimboExitByInclusionProof( bytes32 exitId, @@ -205,125 +207,102 @@ contract ExitHandler is DepositHandler { 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 + // [TODO]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? + if(success){ + // no one can exit + } } function challengeLimboExitByInputSpend( bytes32 exitId, bytes inTxData, uint8 inInputNo, - bytes includedTxData, bytes includedProof, uint8 includedInputNo, uint32 blockNumber) + bytes competingTxData, bytes proof, uint8 competingInputNo, 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); + // if canonical till now + if (limboExit.isValid == true){ + TxLib.Tx memory transferTx = Tx.parseTx(inTxData); + TxLib.Tx memory competingTx = checkForValidityAndInclusion(blockNumber, competingTxData, proof); - 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); + // proving non-canonical + require(transferTx.ins[inInputNo] == competingTx.ins[competingInputNo]); + + TxLib.Input memory inInput = transferTx.ins[inInputNo]; + TxLib.Input memory competingInput = competingTx.ins[competingInputNo]; + require(inInput.blockNumber == competingInput.blockNumber); + require(inInput.amount == competingInput.amount); + } else { + // if someone challenged it before and became successful + LimboChallenge memory prevChallenge = limboExit.challenge; + // [TODO] Which challenger is showing oldest spend of inInputNo + // [TODO] pay challengeStake to winner - // not a valid tx because canonical - // will block spent inputs from exiitng + } limboExit.isValid = false; - // payments? + limboExit.input[inInputNo].exitable = false; + limboExit.challenge = LimboChallenge({ + owner: msg.sender, + resolved: false + }); } 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) + bytes competingTxData, bytes proof, uint8 competingInputNo, 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); + if (limboExit.isValid == true){ + TxLib.Tx memory transferTx = Tx.parseTx(inTxData); + TxLib.Tx memory competingTx = checkForValidityAndInclusion(blockNumber, competingTxData, proof); - // 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); + require(transferTx.ins[inOutputNo] == competingTx.ins[competingInputNo]); + + TxLib.Output memory inOutput = transferTx.ins[inOutputNo]; + TxLib.Input memory competingInput = competingTx.ins[competingInputNo]; + // check if same or not + // require(inOutput.blockNumber == competingInput.blockNumber); + // require(inInput.amount == competingInput.amount); + } else { + // if someone challenged it before and became successful + LimboChallenge memory prevChallenge = limboExit.challenge; + // [TODO] Which challenger is showing oldest spend of inInputNo + // [TODO] pay challengeStake to winner - // not a valid tx because input was not created by a canonical tx - // will block non canonical inputs from exiitng + } limboExit.isValid = false; - // payments? + limboExit.output[inOutputNo].exitable = false; + limboExit.challenge = LimboChallenge({ + owner: msg.sender, + resolved: false + }); } - function resolveChallengeOnLimbo( - bytes32 exitId, bytes inTxData, uint256 challengeNo, + function respondInputSpendChallenge( + bytes32 exitId, bytes inTxData, uint8 inInputNo, 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; + LimboChallenge memory challenge = limboExit.input[inInputNo].challenge; + + //[TODO] check if exiting tx inclusion proof is valid or not (bool success) + if(success){ + limboExit.isValid = true; + limboExit.input[inInputNo].exitable = true; + limboExit.input[inInputNo].challenge.resolved = true; + //[TODO] pay resolver exitStake + } } function finalizeTopLimboExit(uint16 _color) public { @@ -335,15 +314,24 @@ contract ExitHandler is DepositHandler { require(tokens[_color].currentSize > 0, "The exit queue for color is empty"); LimboExit memory currentExit = limboExits[utxoId]; + uint256 amount; 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; + // all piggybacked outputs are paid out + for(uint256 i=0; i < limboExit.output.length; i++){ + LimboOut memory out = limboExit.output[i]; + if (out.exitable){ + amount = piggybackStake + out.value; + tokens[out.color].addr.transferFrom(address(this), out.owner, amount); + } + } + } else { + // all piggybacked inputs are paid out + for(uint256 i=0; i < limboExit.input.length; i++){ + LimboIn memory in = limboExit.input[i]; + if (in.exitable){ + // amount = + tokens[in.color].addr.transferFrom(address(this), in.owner, amount); + } } } delete limboExits[utxoId]; From 7427aed689b682f2af3ac082e78532113226fb92 Mon Sep 17 00:00:00 2001 From: fnatic Date: Sun, 10 Feb 2019 13:51:01 +0530 Subject: [PATCH 3/6] Adding proofs to validate tx flow --- contracts/ExitHandler.sol | 147 +++++++++++++++++++++++++------------- test/exitHandler.js | 47 ++++++------ 2 files changed, 124 insertions(+), 70 deletions(-) diff --git a/contracts/ExitHandler.sol b/contracts/ExitHandler.sol index d8756f8..c73f621 100644 --- a/contracts/ExitHandler.sol +++ b/contracts/ExitHandler.sol @@ -64,11 +64,13 @@ contract ExitHandler is DepositHandler { uint256 color; bool exitable; LimboChallenge challenge; + // bytes32 utxoId; } struct LimboChallenge { address owner; bool resolved; + bytes32[] proof; } uint256 public exitDuration; @@ -122,7 +124,8 @@ contract ExitHandler is DepositHandler { owner: out.owner, isPegged: false, color: out.color, - exitable: true }); + exitable: true, + // utxoId: bytes32(uint256(i) << 120 | uint120(uint256(inTxHash))); }); } for (uint256 i =0; i < transferTx.ins.length; i++){ TxLib.Input memory ins = transferTx.ins[i]; @@ -133,9 +136,7 @@ contract ExitHandler is DepositHandler { } bytes32 inTxHash = keccak256(inTxData); - - // for now assume outputIndex 0 - bytes32 utxoId = bytes32(uint256(_outputIndex) << 120 | uint120(uint256(inTxHash))); + bytes32 utxoId: bytes32(uint256(_outputIndex) << 120 | uint120(uint256(inTxHash))); uint256 priority; if (isNft(out.color)) { @@ -150,7 +151,7 @@ contract ExitHandler is DepositHandler { finalized: false, exitor: msg.sender, isValid: true, - challenges:[] + challenge:{} }); emit LimboExitStarted( @@ -189,11 +190,11 @@ contract ExitHandler is DepositHandler { // LimboExit memory exit = limboExits[exitId]; // require(exit.isValid == true); // for (uint8 i = 0; i < exit.challenge.length; i++) { - // require(_inputIndex != exit.challenge[i].inputNo); + // require(_inputIndex != exit.challenge[i].InputNo); // } // LimboChallenge memory Challenge; // Challenge.from = msg.sender; - // Challenge.inputNo = _inputIndex; + // Challenge.InputNo = _inputIndex; // Challenge.resolved = false; // exit.challenge.push(Challenge); // emit LimboExitChallengePublished(exitId, msg.sender, uint8(exit.challenge.length-1), _inputIndex); @@ -201,52 +202,75 @@ contract ExitHandler is DepositHandler { // } function challengeLimboExitByInclusionProof( - bytes32 exitId, - bytes inTxData, uint8 inputNo) + bytes32 exitId, bytes inTxData, + uint8 InputNo, bytes32[] memory _youngestInputProof, bytes32[] memory _inclusionProof) public payable { require(msg.value >= challengeStake, "Not enough ether sent to challenge exit"); LimboExit memory limboExit = limboExits[exitId]; + bytes32 inTxHash = keccak256(inTxdata); require(limboExit.isValid == true); TxLib.Tx memory transferTx = Tx.parseTx(inTxData); + + bytes32 txHash; + bytes memory txData; + (, txHash, txData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _inclusionProof); + require(inTxHash == txHash, "Invalid inclusion proof"); - // [TODO]check if this tx is included or not - // TxLib.Tx memory includedTx = checkForValidityAndInclusion(blockNumber, includedTxData, includedProof); - if(success){ - // no one can exit - } + limboExit.isValid = false; + limboExit.input[InputNo].exitable = false; + limboExit.input[InputNo].challenge = LimboChallenge({ + owner: msg.sender, + resolved: false + }); } function challengeLimboExitByInputSpend( bytes32 exitId, - bytes inTxData, uint8 inInputNo, - bytes competingTxData, bytes proof, uint8 competingInputNo, uint32 blockNumber) + bytes inTxData, uint8 InputNo, + bytes32[] memory _youngestInputProof, bytes32[] _spendProof) public payable { require(msg.value >= challengeStake, "Not enough ether sent to challenge exit"); LimboExit memory limboExit = limboExits[exitId]; bytes32 inTxHash = keccak256(inTxdata); + bytes32 competingHash; + bytes memory competingTxData; + uint32 blockHeight; + + TxLib.Tx memory transferTx = Tx.parseTx(inTxData); + (, competingHash, competingTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _spendProof); + TxLib.Tx memory competingTx = Tx.parseTx(competingTxData); + + blockHeight = bridge.periods[_spendProof[0]].height; + // if canonical till now if (limboExit.isValid == true){ - TxLib.Tx memory transferTx = Tx.parseTx(inTxData); - TxLib.Tx memory competingTx = checkForValidityAndInclusion(blockNumber, competingTxData, proof); - // proving non-canonical - require(transferTx.ins[inInputNo] == competingTx.ins[competingInputNo]); + require(transferTx.ins[InputNo] == competingTx.ins[InputNo]); - TxLib.Input memory inInput = transferTx.ins[inInputNo]; - TxLib.Input memory competingInput = competingTx.ins[competingInputNo]; + TxLib.Input memory inInput = transferTx.ins[InputNo]; + TxLib.Input memory competingInput = competingTx.ins[InputNo]; require(inInput.blockNumber == competingInput.blockNumber); require(inInput.amount == competingInput.amount); + require(inTxHash == competingHash, "Transactions not same"); + } else { + // if someone challenged it before and became successful LimboChallenge memory prevChallenge = limboExit.challenge; - // [TODO] Which challenger is showing oldest spend of inInputNo + bytes _prevProof = prevChallenge.proof; + (, previousHash, previousTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _prevProof); + TxLib.Tx memory previousTx = Tx.parseTx(previousTxData); + prevHeight = bridge.periods[_prevProof[0]].height; + + require(blockHeight < prevHeight); + // [TODO] pay challengeStake to winner } limboExit.isValid = false; - limboExit.input[inInputNo].exitable = false; + limboExit.input[InputNo].exitable = false; limboExit.challenge = LimboChallenge({ owner: msg.sender, resolved: false @@ -255,54 +279,77 @@ contract ExitHandler is DepositHandler { function challengeLimboExitByOutputSpend( bytes32 exitId, - bytes inTxData, uint8 inOutputNo, - bytes competingTxData, bytes proof, uint8 competingInputNo, uint32 blockNumber) + bytes inTxData, uint8 OutputNo, + bytes _youngestInputProof, bytes _spendProof, uint8 InputNo) public payable { require(msg.value >= challengeStake, "Not enough ether sent to challenge exit"); LimboExit memory limboExit = limboExits[exitId]; bytes32 inTxHash = keccak256(inTxdata); - if (limboExit.isValid == true){ - TxLib.Tx memory transferTx = Tx.parseTx(inTxData); - TxLib.Tx memory competingTx = checkForValidityAndInclusion(blockNumber, competingTxData, proof); + bytes32 competingHash; + bytes memory competingTxData; + + TxLib.Tx memory transferTx = Tx.parseTx(inTxData); + (, competingHash, competingTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _spendProof); + TxLib.Tx memory competingTx = Tx.parseTx(competingTxData); - require(transferTx.ins[inOutputNo] == competingTx.ins[competingInputNo]); + uint32 blockHeight = bridge.periods[_spendProof[0]].height; + + if (limboExit.isValid == true){ + require(transferTx.ins[OutputNo] == competingTx.ins[InputNo]); - TxLib.Output memory inOutput = transferTx.ins[inOutputNo]; - TxLib.Input memory competingInput = competingTx.ins[competingInputNo]; - // check if same or not - // require(inOutput.blockNumber == competingInput.blockNumber); - // require(inInput.amount == competingInput.amount); + // check if same or not ? + TxLib.Output memory inOutput = transferTx.ins[OutputNo]; + TxLib.Input memory competingInput = competingTx.ins[InputNo]; + require(inInput.blockNumber == competingInput.blockNumber); + require(inInput.amount == competingInput.amount); + require(inTxHash == competingHash, "Transactions not same"); + } else { // if someone challenged it before and became successful LimboChallenge memory prevChallenge = limboExit.challenge; - // [TODO] Which challenger is showing oldest spend of inInputNo + bytes _prevProof = prevChallenge.proof; + (, previousHash, previousTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _prevProof); + TxLib.Tx memory previousTx = Tx.parseTx(previousTxData); + prevHeight = bridge.periods[_prevProof[0]].height; + + require(blockHeight < prevHeight); + // [TODO] pay challengeStake to winner } limboExit.isValid = false; - limboExit.output[inOutputNo].exitable = false; + limboExit.output[OutputNo].exitable = false; limboExit.challenge = LimboChallenge({ owner: msg.sender, resolved: false }); } - function respondInputSpendChallenge( - bytes32 exitId, bytes inTxData, uint8 inInputNo, - bytes includedTxData, bytes includedProof, uint8 includedOutputNo, uint32 blockNumber + function resolveInputSpendChallenge( + bytes32 exitId, bytes inTxData, uint8 InputNo, + bytes32[] _txProof, bytes32[] _youngestInputProof ) public { - LimboExit memory limboExit = limboExits[exitId]; - LimboChallenge memory challenge = limboExit.input[inInputNo].challenge; - - //[TODO] check if exiting tx inclusion proof is valid or not (bool success) - if(success){ - limboExit.isValid = true; - limboExit.input[inInputNo].exitable = true; - limboExit.input[inInputNo].challenge.resolved = true; - //[TODO] pay resolver exitStake - } + LimboChallenge memory challenge = limboExit.input[InputNo].challenge; + bytes32[] _prevProof = challenge.proof; + TxLib.Tx memory transferTx = Tx.parseTx(inTxData); + bytes32 inTxHash = keccak256(inTxData); + + bytes32 previousHash; + bytes memory previousTxData; + + (, previousHash, previousTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _prevProof); + TxLib.Tx memory previousTx = Tx.parseTx(previousTxData); + uint32 prevHeight = bridge.periods[_prevProof[0]].height; + + uint32 blockHeight = bridge.periods[_txProof[0]].height; + require(blockHeight < prevHeight); + + limboExit.isValid = true; + limboExit.input[inInputNo].exitable = true; + limboExit.input[inInputNo].challenge.resolved = true; + //[TODO] pay resolver exitStake } function finalizeTopLimboExit(uint16 _color) public { diff --git a/test/exitHandler.js b/test/exitHandler.js index e8d9cee..8d57cd2 100644 --- a/test/exitHandler.js +++ b/test/exitHandler.js @@ -147,9 +147,9 @@ contract('ExitHandler', (accounts) => { assert(aliceBalanceBefore.add(new BN(50)).eq(aliceBalanceAfter)); }); - it('Should allow to challenge tx and prevent exit', async () => { - const period = await submitNewPeriod([depositTx, transferTx]); - const transferProof = period.proof(transferTx); + it('Should allow to challenge tx by output spend and prevent inflight exit', async () => { + const period = await submitNewPeriod([depositTx]); + const depositProof = period.proof(depositTx); //bob spends utxo1 by sending it to charlie const spendTx = Tx.transfer( @@ -161,8 +161,8 @@ contract('ExitHandler', (accounts) => { const piggybackStake = exitHandler.piggybackStake(); const challengeStake = exitHandler.challengeStake(); - const inTxData = Tx.parseToParams(transferTx); - const spendTxData = Tx.parseToParams(spendTx); + const inTxData = transferTx.hex(); + const spendTxData = spendTx.hex(); // any user can start the exit for this transaction exitId = await exitHandler.startLimboExit(inTxData, {from: bob, value: exitStake}); @@ -171,23 +171,24 @@ contract('ExitHandler', (accounts) => { await exitHandler.joinLimboExit(exitId, 0, {from: bob, value: piggybackStake}); const bobBalanceBefore = await nativeToken.balanceOf(bob); - await exitHandler.challengeLimboExit(exitId, spendTxData, {from: pete, value: challengeStake}); + await exitHandler.challengeLimboExit(exitId, inTxData, 0, depositProof, spendProof, 0, {from: pete, value: challengeStake}); const challengeTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); await time.increaseTo(challengeTime); - await exitHandler.finalizeTopLimboExit(nativeTokenColor); - // const responseTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); // await time.increaseTo(responseTime); + + await exitHandler.finalizeTopLimboExit(nativeTokenColor); const bobBalanceAfter = await nativeToken.balanceOf(bob); assert(bobBalanceAfter.eq(bobBalanceBefore)); }); - it('Should allow user to challenge double spent inflight tx', async () => { - // const period = await submitNewPeriod([depositTx); - + it('Should allow user to challenge input spent and prevent inflight tx', async () => { + const period = await submitNewPeriod([depositTx); + const depositProof = period.proof(depositTx); + //alice double spends utxo1 by sending it to charlie const spendTx = Tx.transfer( [new Input(new Outpoint(depositTx.hash(), 0))], @@ -198,17 +199,17 @@ contract('ExitHandler', (accounts) => { const piggybackStake = exitHandler.piggybackStake(); const challengeStake = exitHandler.challengeStake(); - const inTxData = Tx.parseToParams(transferTx); - const spendTxData = Tx.parseToParams(spendTx); + const inTxData = transferTx.hex(); + const spendTxData = spendTx.hex(); - exitId = await exitHandler.startLimboExit(inTxData, {from: bob, value: exitStake}); + exitId = await exitHandler.startLimboExit(inTxData, 0, {from: bob, value: exitStake}); // Bob piggybacks and joins a Limo exit by its id await exitHandler.joinLimboExit(exitId, 0, {from: bob, value: piggybackStake}); const bobBalanceBefore = await nativeToken.balanceOf(bob); // challenge to tx's canonicity - await exitHandler.challengeLimboExit(exitId, spendTxData, {from: pete, value: challengeStake}); + await exitHandler.challengeLimboExit(exitId, inTxData, 0, depositProof, spendProof, 0, {from: pete, value: challengeStake}); const challengeTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); await time.increaseTo(challengeTime); @@ -222,28 +223,34 @@ contract('ExitHandler', (accounts) => { assert(bobBalanceAfter.eq(bobBalanceBefore)); }); - it('Should resolve a challenge and exit', async () => { + it('Should resolve a output challenge and exit inflight tx', async () => { const period = await submitNewPeriod([depositTx); const depositProof = period.proof(depositTx); + + const spendTx = Tx.transfer( + [new Input(new Outpoint(transferTx.hash(), 0))], + [new Output(50, charlie)] + ).sign([bobPriv]); const exitStake = exitHandler.exitStake(); const piggybackStake = exitHandler.piggybackStake(); const challengeStake = exitHandler.challengeStake(); - const inTxData = Tx.parseToParams(transferTx); + const inTxData = transferTx.hex(); + const spendTxData = spendTx.hex(); - exitId = await exitHandler.startLimboExit(inTxData, {from: bob, value: exitStake}); + exitId = await exitHandler.startLimboExit(inTxData, 0, {from: bob, value: exitStake}); // Alice piggybacks and joins a Limo exit by its id await exitHandler.joinLimboExit(exitId, 0, {from: alice, value: piggybackStake}); const aliceBalanceBefore = await nativeToken.balanceOf(alice); - await exitHandler.challengeLimboExit(exitId, randomTx, {from: pete, value: challengeStake}); + await exitHandler.challengeLimboExit(exitId, inTxData, 0, depositProof, spendProof, 0, {from: pete, value: challengeStake}); const challengeTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); await time.increaseTo(challengeTime); - await exitHandler.resolveChallengeOnInput(exitId, inTxData, 0, depositData, depositProof, 0, blockNo); + await exitHandler.resolveInputSpendChallenge(exitId, inTxData, 0, depositData, txProof, depositProof); const responseTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); await time.increaseTo(responseTime); From 91c7d737c7ea372ad0b280ffaeec1a749380238c Mon Sep 17 00:00:00 2001 From: fnatic Date: Tue, 12 Feb 2019 19:56:46 +0530 Subject: [PATCH 4/6] Everything except working tests --- contracts/ExitHandler.sol | 239 +++++++++++++++++++++----------------- test/exitHandler.js | 21 ++-- 2 files changed, 142 insertions(+), 118 deletions(-) diff --git a/contracts/ExitHandler.sol b/contracts/ExitHandler.sol index c73f621..82a60c2 100644 --- a/contracts/ExitHandler.sol +++ b/contracts/ExitHandler.sol @@ -51,6 +51,7 @@ contract ExitHandler is DepositHandler { } struct LimboIn { + uint256 amount; address owner; bool isPegged; bool exitable; @@ -92,10 +93,16 @@ contract ExitHandler is DepositHandler { function initializeWithExit( Bridge _bridge, uint256 _exitDuration, - uint256 _exitStake) public initializer { + uint256 _exitStake, + uint256 _piggybackStake, + uint256 _challengeStake, + uint256 _limboPeriod) public initializer { initialize(_bridge); exitDuration = _exitDuration; exitStake = _exitStake; + challengeStake = _challengeStake; + piggybackStake = _piggybackStake; + limboPeriod = _limboPeriod; emit MinGasPrice(0); } @@ -103,67 +110,94 @@ contract ExitHandler is DepositHandler { exitStake = _exitStake; } + function setPiggybackStake(uint256 _piggybackStake) public ifAdmin { + piggybackStake = _piggybackStake; + } + + function setChallengeStake(uint256 _challengeStake) public ifAdmin { + challengeStake = _challengeStake; + } + function setExitDuration(uint256 _exitDuration) public ifAdmin { exitDuration = _exitDuration; } - function startLimboExit(bytes memory inTxData) public payable returns (bytes32 utxoId) { + function startLimboExit(bytes memory inTxData, bytes32[] memory _youngestInputProof) public payable returns (bytes32 utxoId) { // exitor assumes tx to be canonical require(msg.value >= exitStake, "Not enough ether sent to pay for exit stake"); TxLib.Tx memory transferTx = TxLib.parseTx(inTxData); uint8 _outputIndex = 0; - mapping(uint8 => LimboIn) public inputs; - mapping(uint8 => LimboOut) public outputs; + mapping(uint8 => LimboIn) storage inputs; + mapping(uint8 => LimboOut) storage outputs; - for (uint256 i =0; i < transferTx.outs.length; i++){ - TxLib.Output memory out = transferTx.outs[i]; + for (uint8 i =0; i < transferTx.outs.length; i++){ + TxLib.Output memory outs = transferTx.outs[i]; outputs[i] = LimboOut({ - amount: out.value, - owner: out.owner, + amount: outs.value, + owner: outs.owner, isPegged: false, - color: out.color, + color: outs.color, exitable: true, + challenge: {} // utxoId: bytes32(uint256(i) << 120 | uint120(uint256(inTxHash))); }); - } - for (uint256 i =0; i < transferTx.ins.length; i++){ + }); + for (uint8 i =0; i < transferTx.ins.length; i++){ TxLib.Input memory ins = transferTx.ins[i]; + TxLib.Output memory out = transferTx.outs[ins.outpoint.pos]; inputs[i] = LimboIn({ + amount: out.value, owner: out.owner, isPegged: false, - exitable: true }); - } + exitable: true, + challenge: {} + }); + } bytes32 inTxHash = keccak256(inTxData); - bytes32 utxoId: bytes32(uint256(_outputIndex) << 120 | uint120(uint256(inTxHash))); + bytes32 utxoId = bytes32(uint256(_outputIndex) << 120 | uint120(uint256(inTxHash))); uint256 priority; + uint32 height; + uint32 timestamp; - if (isNft(out.color)) { + // priority based on youngestInput + if (_youngestInputProof.length > 0) { + (height, timestamp) = bridge.periods(_youngestInputProof[0]); + require(timestamp > 0, "The referenced period was not submitted to bridge"); + } + bytes32 inputTxHash; + uint64 txPos; + (txPos, inputTxHash,) = TxLib.validateProof(96, _youngestInputProof); + + if (isNft(outs.color)) { priority = (nftExitCounter << 128) | uint128(uint256(utxoId)); nftExitCounter++; } else { - priority = getERC20ExitPriority(*, utxoId, txPos); + priority = getERC20ExitPriority(timestamp, utxoId, txPos); } limboExits[utxoId] = LimboExit({ - output: outputs, input: inputs, + output: outputs, finalized: false, exitor: msg.sender, - isValid: true, - challenge:{} + isValid: true }); emit LimboExitStarted( inTxHash, - out.color + outs.color ); - tokens[out.color].insert(priority); + tokens[outs.color].insert(priority); return utxoId; } - function joinLimboExit(bytes32 exitId, uint8 _index) public payable { + } + + function joinLimboExit( + bytes32 exitId, uint8 _index) + public payable { require(msg.value >= piggybackStake, "Not enough ether sent to join the exit"); address owner = msg.sender; @@ -180,175 +214,172 @@ contract ExitHandler is DepositHandler { 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 Challenge; - // Challenge.from = msg.sender; - // Challenge.InputNo = _inputIndex; - // Challenge.resolved = false; - // exit.challenge.push(Challenge); - // emit LimboExitChallengePublished(exitId, msg.sender, uint8(exit.challenge.length-1), _inputIndex); - // return true; - // } - function challengeLimboExitByInclusionProof( - bytes32 exitId, bytes inTxData, + bytes32 exitId, bytes memory inTxData, uint8 InputNo, bytes32[] memory _youngestInputProof, bytes32[] memory _inclusionProof) public payable { require(msg.value >= challengeStake, "Not enough ether sent to challenge exit"); LimboExit memory limboExit = limboExits[exitId]; - bytes32 inTxHash = keccak256(inTxdata); + bytes32 inTxHash = keccak256(inTxData); require(limboExit.isValid == true); - TxLib.Tx memory transferTx = Tx.parseTx(inTxData); + TxLib.Tx memory transferTx = TxLib.parseTx(inTxData); bytes32 txHash; bytes memory txData; - (, txHash, txData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _inclusionProof); + uint64 txPos; + (txPos, txHash, txData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _inclusionProof); + require(inTxHash == txHash, "Invalid inclusion proof"); limboExit.isValid = false; limboExit.input[InputNo].exitable = false; limboExit.input[InputNo].challenge = LimboChallenge({ owner: msg.sender, - resolved: false + resolved: false, + proof: _inclusionProof }); } function challengeLimboExitByInputSpend( bytes32 exitId, - bytes inTxData, uint8 InputNo, - bytes32[] memory _youngestInputProof, bytes32[] _spendProof) + bytes memory inTxData, uint8 InputNo, uint8 _spendInputNo, + bytes32[] memory _youngestInputProof, bytes32[] memory _spendProof) public payable { require(msg.value >= challengeStake, "Not enough ether sent to challenge exit"); LimboExit memory limboExit = limboExits[exitId]; - bytes32 inTxHash = keccak256(inTxdata); + bytes32 inTxHash = keccak256(inTxData); bytes32 competingHash; bytes memory competingTxData; uint32 blockHeight; + uint64 competingPos; - TxLib.Tx memory transferTx = Tx.parseTx(inTxData); - (, competingHash, competingTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _spendProof); - TxLib.Tx memory competingTx = Tx.parseTx(competingTxData); + TxLib.Tx memory transferTx = TxLib.parseTx(inTxData); + (competingPos, competingHash, competingTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _spendProof); + TxLib.Tx memory competingTx = TxLib.parseTx(competingTxData); blockHeight = bridge.periods[_spendProof[0]].height; // if canonical till now + if (limboExit.isValid == true){ - // proving non-canonical - require(transferTx.ins[InputNo] == competingTx.ins[InputNo]); - TxLib.Input memory inInput = transferTx.ins[InputNo]; - TxLib.Input memory competingInput = competingTx.ins[InputNo]; - require(inInput.blockNumber == competingInput.blockNumber); - require(inInput.amount == competingInput.amount); - require(inTxHash == competingHash, "Transactions not same"); + TxLib.Input memory competingInput = competingTx.ins[_spendInputNo]; + // proving non-canonical + // if same inputs, add challenge + require(inInput.r == competingInput.r); + require(inInput.s == competingInput.s); + require(inInput.v == competingInput.v); } else { // if someone challenged it before and became successful + bytes32 previousHash; + bytes memory previousTxData; + uint64 previousPos; LimboChallenge memory prevChallenge = limboExit.challenge; - bytes _prevProof = prevChallenge.proof; - (, previousHash, previousTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _prevProof); - TxLib.Tx memory previousTx = Tx.parseTx(previousTxData); - prevHeight = bridge.periods[_prevProof[0]].height; + bytes memory _prevProof = prevChallenge.proof; + (previousPos, previousHash, previousTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _prevProof); + TxLib.Tx memory previousTx = TxLib.parseTx(previousTxData); + uint32 prevHeight = bridge.periods[_prevProof[0]].height; require(blockHeight < prevHeight); - - // [TODO] pay challengeStake to winner + // pay challengeStake to winner from exitHandler + tokens[transferTx.outs[0].color].addr.transferFrom(address(this), msg.sender, challengeStake); } limboExit.isValid = false; limboExit.input[InputNo].exitable = false; limboExit.challenge = LimboChallenge({ owner: msg.sender, - resolved: false + resolved: false, + proof: _spendProof }); } function challengeLimboExitByOutputSpend( bytes32 exitId, - bytes inTxData, uint8 OutputNo, - bytes _youngestInputProof, bytes _spendProof, uint8 InputNo) + bytes memory inTxData, uint8 OutputNo, + bytes memory _youngestInputProof, bytes memory _spendProof, uint8 InputNo) public payable { require(msg.value >= challengeStake, "Not enough ether sent to challenge exit"); LimboExit memory limboExit = limboExits[exitId]; - bytes32 inTxHash = keccak256(inTxdata); + bytes32 inTxHash = keccak256(inTxData); bytes32 competingHash; bytes memory competingTxData; + uint64 competingPos; - TxLib.Tx memory transferTx = Tx.parseTx(inTxData); - (, competingHash, competingTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _spendProof); - TxLib.Tx memory competingTx = Tx.parseTx(competingTxData); + TxLib.Tx memory transferTx = TxLib.parseTx(inTxData); + (competingPos, competingHash, competingTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _spendProof); + TxLib.Tx memory competingTx = TxLib.parseTx(competingTxData); uint32 blockHeight = bridge.periods[_spendProof[0]].height; if (limboExit.isValid == true){ - require(transferTx.ins[OutputNo] == competingTx.ins[InputNo]); - - // check if same or not ? - TxLib.Output memory inOutput = transferTx.ins[OutputNo]; - TxLib.Input memory competingInput = competingTx.ins[InputNo]; - require(inInput.blockNumber == competingInput.blockNumber); - require(inInput.amount == competingInput.amount); - require(inTxHash == competingHash, "Transactions not same"); + uint256 offset = uint8(uint256(_spendProof[1] >> 248)); + // getting spendTx input address + address owner = TxLib.recoverTxSigner(offset, _spendProof); + LimboOut memory inOutput = limboExit.output[OutputNo]; + TxLib.Input memory competingInput = competingTx.input[InputNo]; + + // check if same or not ? + require(inOutput.owner == owner); + require(inOutput.amount == competingTx.outs[competingInput.outpoint.pos]); } else { // if someone challenged it before and became successful LimboChallenge memory prevChallenge = limboExit.challenge; - bytes _prevProof = prevChallenge.proof; - (, previousHash, previousTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _prevProof); - TxLib.Tx memory previousTx = Tx.parseTx(previousTxData); - prevHeight = bridge.periods[_prevProof[0]].height; + bytes32[] memory _prevProof = prevChallenge.proof; + uint64 previousPos; + bytes32 previousHash; + bytes memory previousTxData; + + (previousPos, previousHash, previousTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _prevProof); + TxLib.Tx memory previousTx = TxLib.parseTx(previousTxData); + uint32 prevHeight = bridge.periods[_prevProof[0]].height; require(blockHeight < prevHeight); - - // [TODO] pay challengeStake to winner + tokens[transferTx.outs[0].color].addr.transferFrom(address(this), msg.sender, challengeStake); } limboExit.isValid = false; limboExit.output[OutputNo].exitable = false; limboExit.challenge = LimboChallenge({ owner: msg.sender, - resolved: false + resolved: false, + proof: _spendProof }); } function resolveInputSpendChallenge( - bytes32 exitId, bytes inTxData, uint8 InputNo, - bytes32[] _txProof, bytes32[] _youngestInputProof + bytes32 exitId, bytes memory inTxData, uint8 InputNo, + bytes32[] memory _txProof, bytes32[] memory _youngestInputProof ) public { LimboExit memory limboExit = limboExits[exitId]; LimboChallenge memory challenge = limboExit.input[InputNo].challenge; - bytes32[] _prevProof = challenge.proof; - TxLib.Tx memory transferTx = Tx.parseTx(inTxData); + bytes32[] memory _prevProof = challenge.proof; + TxLib.Tx memory transferTx = TxLib.parseTx(inTxData); bytes32 inTxHash = keccak256(inTxData); bytes32 previousHash; bytes memory previousTxData; + uint64 previousPos; - (, previousHash, previousTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _prevProof); - TxLib.Tx memory previousTx = Tx.parseTx(previousTxData); + (previousPos, previousHash, previousTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _prevProof); + TxLib.Tx memory previousTx = TxLib.parseTx(previousTxData); uint32 prevHeight = bridge.periods[_prevProof[0]].height; uint32 blockHeight = bridge.periods[_txProof[0]].height; require(blockHeight < prevHeight); limboExit.isValid = true; - limboExit.input[inInputNo].exitable = true; - limboExit.input[inInputNo].challenge.resolved = true; + limboExit.input[InputNo].exitable = true; + limboExit.input[InputNo].challenge.resolved = true; //[TODO] pay resolver exitStake } @@ -362,22 +393,22 @@ contract ExitHandler is DepositHandler { LimboExit memory currentExit = limboExits[utxoId]; uint256 amount; - if (limboExit.isValid == true){ + if (currentExit.isValid == true){ // all piggybacked outputs are paid out - for(uint256 i=0; i < limboExit.output.length; i++){ - LimboOut memory out = limboExit.output[i]; + for(uint8 i=0; i < currentExit.output.length; i++){ + LimboOut memory out = currentExit.output[i]; if (out.exitable){ - amount = piggybackStake + out.value; + amount = piggybackStake + out.amount; tokens[out.color].addr.transferFrom(address(this), out.owner, amount); } } } else { // all piggybacked inputs are paid out - for(uint256 i=0; i < limboExit.input.length; i++){ - LimboIn memory in = limboExit.input[i]; - if (in.exitable){ - // amount = - tokens[in.color].addr.transferFrom(address(this), in.owner, amount); + for(uint8 i=0; i < currentExit.input.length; i++){ + LimboIn memory input = currentExit.input[i]; + if (input.exitable){ + amount = piggybackStake + input.amount; + tokens[input.color].addr.transferFrom(address(this), input.owner, amount); } } } diff --git a/test/exitHandler.js b/test/exitHandler.js index 8d57cd2..09e23ca 100644 --- a/test/exitHandler.js +++ b/test/exitHandler.js @@ -33,6 +33,7 @@ contract('ExitHandler', (accounts) => { const bob = accounts[1]; const bobPriv = '0x7bc8feb5e1ce2927480de19d8bc1dc6874678c016ae53a2eec6a6e9df717bfac'; const charlie = accounts[2]; + const pete = accounts[3]; describe('Test', () => { // contracts @@ -58,7 +59,10 @@ contract('ExitHandler', (accounts) => { const parentBlockInterval = 0; const exitDuration = 5; - const exitStake = 0; + const exitStake = 0; + const piggybackStake = 0; + const challengeStake = 0; + const limboPeriod = 2; const seedTxs = async () => { await nativeToken.approve(exitHandler.address, 1000); @@ -110,7 +114,8 @@ contract('ExitHandler', (accounts) => { await proxy.applyProposal(data, {from: accounts[2]}); const vaultCont = await ExitHandler.new(); - data = await vaultCont.contract.methods.initializeWithExit(bridge.address, exitDuration, exitStake).encodeABI(); + data = await vaultCont.contract.methods.initializeWithExit(bridge.address, exitDuration, exitStake, piggybackStake, challengeStake, + limboPeriod).encodeABI(); proxy = await AdminableProxy.new(vaultCont.address, data, {from: accounts[2]}); exitHandler = await ExitHandler.at(proxy.address); @@ -157,10 +162,6 @@ contract('ExitHandler', (accounts) => { [new Output(50, charlie)] ).sign([bobPriv]); - const exitStake = exitHandler.exitStake(); - const piggybackStake = exitHandler.piggybackStake(); - const challengeStake = exitHandler.challengeStake(); - const inTxData = transferTx.hex(); const spendTxData = spendTx.hex(); @@ -195,10 +196,6 @@ contract('ExitHandler', (accounts) => { [new Output(50, charlie)] ).sign([alicePriv]); - const exitStake = exitHandler.exitStake(); - const piggybackStake = exitHandler.piggybackStake(); - const challengeStake = exitHandler.challengeStake(); - const inTxData = transferTx.hex(); const spendTxData = spendTx.hex(); @@ -232,10 +229,6 @@ contract('ExitHandler', (accounts) => { [new Output(50, charlie)] ).sign([bobPriv]); - const exitStake = exitHandler.exitStake(); - const piggybackStake = exitHandler.piggybackStake(); - const challengeStake = exitHandler.challengeStake(); - const inTxData = transferTx.hex(); const spendTxData = spendTx.hex(); From 37ef21d92599bbbd2884257ea07911206ca40056 Mon Sep 17 00:00:00 2001 From: fnatic Date: Sat, 16 Feb 2019 10:30:27 +0530 Subject: [PATCH 5/6] Code not compiling aaaaaaaaaaaa --- contracts/ExitHandler.sol | 413 ++++++++++++++++++++------------------ 1 file changed, 220 insertions(+), 193 deletions(-) diff --git a/contracts/ExitHandler.sol b/contracts/ExitHandler.sol index 82a60c2..f990247 100644 --- a/contracts/ExitHandler.sol +++ b/contracts/ExitHandler.sol @@ -29,51 +29,88 @@ 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); + // event LimboExitStarted(bytes32 indexed txHash, uint256 indexed color, bytes32 indexed exitId); + // 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; + uint32 priorityTimestamp; } struct LimboExit { - LimboIn[] input; - LimboOut[] output; + uint256 amount; + uint16 color; + address owner; bool finalized; + uint256 priority; + uint256 stake; address exitor; - bool isValid; // a tx is valid if it is canonical and exitable and comes from valid txs + uint8 _outputIndex; + bytes32 txHash; } - struct LimboIn { - uint256 amount; - address owner; - bool isPegged; - bool exitable; - LimboChallenge challenge; + struct LimboTx { + LimboOut[] outputs; + LimboIn[] inputs; + bool isCanonical; + // LimboIn[] inputs; + bytes txnData; + } + + // struct LimboExit { + // bool finalized; + // uint256 priority; + // uint256 stake; + // address exitor; + // uint256 amount; + // address owner; + // uint8 color; + // uint8 _outputIndex; + // bytes32 txHash; + // } + + // struct LimboTx { + // LimboOut[] outputs; + // LimboInputChallenge[] challenges; + // } + + struct LimboChallenge { + address challenger; + bool resolved; + bytes32[] proof; } struct LimboOut { uint256 amount; address owner; bool isPegged; - uint256 color; - bool exitable; + bool unspent; LimboChallenge challenge; - // bytes32 utxoId; } - struct LimboChallenge { + struct LimboIn { address owner; - bool resolved; - bytes32[] proof; + uint256 amount; + // OutputId[] to; + bool isPegged; + LimboChallenge challenge; } + // struct Outputidx { + // uint8 pos; + // } + + // struct LimboInputChallenge { + // address challenger; + // bool resolved; + // uint8 _inputIndex; + // bytes32[] proof; + // } + uint256 public exitDuration; uint256 public limboPeriod; uint256 public piggybackStake; @@ -81,14 +118,13 @@ contract ExitHandler is DepositHandler { 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; + mapping(bytes32 => LimboExit) limboExits; + mapping(bytes32 => LimboTx) limboTxns; + // mapping(uint8 => LimboOut) txOuts; function initializeWithExit( Bridge _bridge, @@ -122,158 +158,137 @@ contract ExitHandler is DepositHandler { exitDuration = _exitDuration; } - function startLimboExit(bytes memory inTxData, bytes32[] memory _youngestInputProof) public payable returns (bytes32 utxoId) { + function startLimboExit(bytes memory inTxData, bytes32[] memory _youngestInputProof, uint8 _outputIndex) public payable returns (bytes32 utxoId) { // exitor assumes tx to be canonical require(msg.value >= exitStake, "Not enough ether sent to pay for exit stake"); TxLib.Tx memory transferTx = TxLib.parseTx(inTxData); - uint8 _outputIndex = 0; - mapping(uint8 => LimboIn) storage inputs; - mapping(uint8 => LimboOut) storage outputs; + TxLib.Output memory out = transferTx.outs[_outputIndex]; + + LimboTx memory limboTx; + limboTx.txn= transferTx; for (uint8 i =0; i < transferTx.outs.length; i++){ TxLib.Output memory outs = transferTx.outs[i]; - outputs[i] = LimboOut({ - amount: outs.value, - owner: outs.owner, - isPegged: false, - color: outs.color, - exitable: true, - challenge: {} - // utxoId: bytes32(uint256(i) << 120 | uint120(uint256(inTxHash))); }); - }); - for (uint8 i =0; i < transferTx.ins.length; i++){ - TxLib.Input memory ins = transferTx.ins[i]; - TxLib.Output memory out = transferTx.outs[ins.outpoint.pos]; - inputs[i] = LimboIn({ - amount: out.value, - owner: out.owner, - isPegged: false, - exitable: true, - challenge: {} - }); + LimboOut memory output; + output.amount = outs.value; + output.owner = outs.owner; + output.isPegged = false; + output.exitable = false; + limboTx.outputs[i] = output; + // txOuts[i] = output; } - + // limboTx.outputs= txOuts; + + // for (uint8 i =0; i < transferTx.ins.length; i++){ + // TxLib.Input memory ins = transferTx.ins[i]; + // TxLib.Output memory out = transferTx.outs[ins.outpoint.pos]; + // inputs[i] = LimboIn({ + // amount: out.value, + // owner: out.owner, + // isPegged: false, + // exitable: true, + // challenge: {} + // }); + bytes32 inTxHash = keccak256(inTxData); - bytes32 utxoId = bytes32(uint256(_outputIndex) << 120 | uint120(uint256(inTxHash))); + utxoId = bytes32(uint256(_outputIndex) << 120 | uint120(uint256(inTxHash))); uint256 priority; uint32 height; uint32 timestamp; - // priority based on youngestInput - if (_youngestInputProof.length > 0) { - (height, timestamp) = bridge.periods(_youngestInputProof[0]); - require(timestamp > 0, "The referenced period was not submitted to bridge"); - } + // validate youngestinputproof bytes32 inputTxHash; uint64 txPos; (txPos, inputTxHash,) = TxLib.validateProof(96, _youngestInputProof); - if (isNft(outs.color)) { + // priority based on youngestInput + (height, timestamp) = bridge.periods(_youngestInputProof[0]); + require(timestamp > 0, "The referenced period was not submitted to bridge"); + + if (isNft(out.color)) { priority = (nftExitCounter << 128) | uint128(uint256(utxoId)); nftExitCounter++; } else { priority = getERC20ExitPriority(timestamp, utxoId, txPos); } - limboExits[utxoId] = LimboExit({ - input: inputs, - output: outputs, - finalized: false, - exitor: msg.sender, - isValid: true - }); - emit LimboExitStarted( - inTxHash, - outs.color - ); - tokens[outs.color].insert(priority); + LimboExit memory limboExit; + limboExit.finalized= false; + limboExit.priority= priority; + limboExit.exitor= msg.sender; + limboExit.amount= out.value; + limboExit.owner= out.owner; + limboExit.color= out.color; + limboExit._outputIndex= _outputIndex; + limboExit.txHash= inTxHash; + + limboExits[utxoId] = limboExit; + limboTxns[inTxHash] = limboTx; + // emit LimboExitStarted( + // inTxHash, + // out.color, + // utxoId + // ); + tokens[out.color].insert(priority); return utxoId; } - } - function joinLimboExit( - bytes32 exitId, uint8 _index) + bytes32 exitId, uint8 _outputIndex) public payable { + // honest owner of outputs will piggyback the exit if they also want to exit their funds 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 challengeLimboExitByInclusionProof( - bytes32 exitId, bytes memory inTxData, - uint8 InputNo, bytes32[] memory _youngestInputProof, bytes32[] memory _inclusionProof) - public payable { - require(msg.value >= challengeStake, "Not enough ether sent to challenge exit"); + // exitId is utxoId LimboExit memory limboExit = limboExits[exitId]; + bytes32 inTxHash = limboExit.txHash; + LimboTx memory limboTx = limboTxns[inTxHash]; - bytes32 inTxHash = keccak256(inTxData); - require(limboExit.isValid == true); - TxLib.Tx memory transferTx = TxLib.parseTx(inTxData); - - bytes32 txHash; - bytes memory txData; - uint64 txPos; - (txPos, txHash, txData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _inclusionProof); - - require(inTxHash == txHash, "Invalid inclusion proof"); + if (limboTx.outputs[_outputIndex].owner == owner) { + // output is piggybacking + require(limboTx.outputs[_outputIndex].isPegged = false, "Already joined the exit"); - limboExit.isValid = false; - limboExit.input[InputNo].exitable = false; - limboExit.input[InputNo].challenge = LimboChallenge({ - owner: msg.sender, - resolved: false, - proof: _inclusionProof - }); + limboTx.outputs[_outputIndex].isPegged = true; + limboTx.outputs[_outputIndex].exitable = true; + } } function challengeLimboExitByInputSpend( - bytes32 exitId, - bytes memory inTxData, uint8 InputNo, uint8 _spendInputNo, - bytes32[] memory _youngestInputProof, bytes32[] memory _spendProof) + bytes32 txHash, + uint8 InputNo, uint8 _spendInputNo, + bytes32[] memory _spendProof) public payable { + // challenging canonicity + require(msg.value >= challengeStake, "Not enough ether sent to challenge exit"); - LimboExit memory limboExit = limboExits[exitId]; - bytes32 inTxHash = keccak256(inTxData); + LimboTx memory limboTx = limboTxns[exitId]; bytes32 competingHash; bytes memory competingTxData; uint32 blockHeight; uint64 competingPos; - TxLib.Tx memory transferTx = TxLib.parseTx(inTxData); - (competingPos, competingHash, competingTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _spendProof); + TxLib.Tx memory inputTx = TxLib.parseTx(limboTx.txnData); + (competingPos, competingHash, competingTxData) = TxLib.validateProof(96, _spendProof); TxLib.Tx memory competingTx = TxLib.parseTx(competingTxData); blockHeight = bridge.periods[_spendProof[0]].height; // if canonical till now - - if (limboExit.isValid == true){ - TxLib.Input memory inInput = transferTx.ins[InputNo]; + if (limboTx.isCanonical == true){ + TxLib.Input memory inInput = inputTx.ins[InputNo]; TxLib.Input memory competingInput = competingTx.ins[_spendInputNo]; // proving non-canonical // if same inputs, add challenge require(inInput.r == competingInput.r); require(inInput.s == competingInput.s); require(inInput.v == competingInput.v); + limboTx.isCanonical = false; } else { @@ -281,107 +296,117 @@ contract ExitHandler is DepositHandler { bytes32 previousHash; bytes memory previousTxData; uint64 previousPos; - LimboChallenge memory prevChallenge = limboExit.challenge; + LimboChallenge memory prevChallenge = limboTx.inputs[InputNo].challenge; bytes memory _prevProof = prevChallenge.proof; - (previousPos, previousHash, previousTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _prevProof); + (previousPos, previousHash, previousTxData) = TxLib.validateProof(96, _prevProof); TxLib.Tx memory previousTx = TxLib.parseTx(previousTxData); uint32 prevHeight = bridge.periods[_prevProof[0]].height; require(blockHeight < prevHeight); // pay challengeStake to winner from exitHandler - tokens[transferTx.outs[0].color].addr.transferFrom(address(this), msg.sender, challengeStake); + // tokens[transferTx.outs[0].color].addr.transferFrom(address(this), msg.sender, challengeStake); } - limboExit.isValid = false; - limboExit.input[InputNo].exitable = false; - limboExit.challenge = LimboChallenge({ + limboTx.inputs[InputNo].challenge = LimboChallenge({ owner: msg.sender, resolved: false, proof: _spendProof }); } - function challengeLimboExitByOutputSpend( - bytes32 exitId, - bytes memory inTxData, uint8 OutputNo, - bytes memory _youngestInputProof, bytes memory _spendProof, uint8 InputNo) + function challengeLimboExitByInclusionProof( + bytes32 txHash, + bytes32[] memory _inclusionProof) public payable { require(msg.value >= challengeStake, "Not enough ether sent to challenge exit"); - LimboExit memory limboExit = limboExits[exitId]; - bytes32 inTxHash = keccak256(inTxData); + LimboTx memory limboTx = limboTxns[txHash]; - bytes32 competingHash; + // bytes32 competingHash; bytes memory competingTxData; - uint64 competingPos; - - TxLib.Tx memory transferTx = TxLib.parseTx(inTxData); - (competingPos, competingHash, competingTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _spendProof); + // uint32 blockHeight; + // uint64 competingPos; + + TxLib.Tx memory inputTx = TxLib.parseTx(limboTx.txnData); + (,, competingTxData) = TxLib.validateProof(96, _inclusionProof); TxLib.Tx memory competingTx = TxLib.parseTx(competingTxData); + bytes32 inTxHash = keccak256(competingTxData); + + require(inTxHash == txHash, "Invalid inclusion proof"); + + limboTx.isCanonical = true; + } + + + function challengeLimboExitByOutputSpend( + bytes32 txHash, uint8 outputNo, uint8 InputNo, + bytes memory _spendProof) + public payable { + require(msg.value >= challengeStake, "Not enough ether sent to challenge exit"); + LimboTx memory limboTx = limboTxns[inTxHash]; + LimboOut memory limboOut = limboTx.outputs[outputNo]; - uint32 blockHeight = bridge.periods[_spendProof[0]].height; + TxLib.Tx memory transferTx = TxLib.parseTx(limboTx.txnData); + (spendPos, spendHash, spendData) = TxLib.validateProof(96, _spendProof); - if (limboExit.isValid == true){ + if (limboOut.isPegged){ + if (limboOut.unspent) { uint256 offset = uint8(uint256(_spendProof[1] >> 248)); // getting spendTx input address address owner = TxLib.recoverTxSigner(offset, _spendProof); - LimboOut memory inOutput = limboExit.output[OutputNo]; - TxLib.Input memory competingInput = competingTx.input[InputNo]; + TxLib.Input memory competingInput = competingTx.ins[InputNo]; // check if same or not ? require(inOutput.owner == owner); require(inOutput.amount == competingTx.outs[competingInput.outpoint.pos]); - - } else { - // if someone challenged it before and became successful - LimboChallenge memory prevChallenge = limboExit.challenge; - bytes32[] memory _prevProof = prevChallenge.proof; - uint64 previousPos; + } + else{ + // if someone challenged it before and became successful bytes32 previousHash; bytes memory previousTxData; - - (previousPos, previousHash, previousTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _prevProof); - TxLib.Tx memory previousTx = TxLib.parseTx(previousTxData); + uint64 previousPos; + LimboChallenge memory prevChallenge = limboTx.outputs[outputNo].challenge; + bytes memory _prevProof = prevChallenge.proof; + (previousPos, previousHash, previousTxData) = TxLib.validateProof(96, _prevProof); + TxLib.Tx memory previousTx = TxLib.parseTx(previousTxData); uint32 prevHeight = bridge.periods[_prevProof[0]].height; - + require(blockHeight < prevHeight); - tokens[transferTx.outs[0].color].addr.transferFrom(address(this), msg.sender, challengeStake); - - } - limboExit.isValid = false; - limboExit.output[OutputNo].exitable = false; - limboExit.challenge = LimboChallenge({ + } + limboTxns[inTxHash].outputs[outputNo].unspent = false; + limboTxns[inTxHash].outputs[outputNo].challenge = LimboChallenge({ owner: msg.sender, resolved: false, proof: _spendProof }); + } } - function resolveInputSpendChallenge( - bytes32 exitId, bytes memory inTxData, uint8 InputNo, - bytes32[] memory _txProof, bytes32[] memory _youngestInputProof - ) public { - LimboExit memory limboExit = limboExits[exitId]; - LimboChallenge memory challenge = limboExit.input[InputNo].challenge; - bytes32[] memory _prevProof = challenge.proof; - TxLib.Tx memory transferTx = TxLib.parseTx(inTxData); - bytes32 inTxHash = keccak256(inTxData); - - bytes32 previousHash; - bytes memory previousTxData; - uint64 previousPos; - - (previousPos, previousHash, previousTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _prevProof); - TxLib.Tx memory previousTx = TxLib.parseTx(previousTxData); - uint32 prevHeight = bridge.periods[_prevProof[0]].height; - - uint32 blockHeight = bridge.periods[_txProof[0]].height; - require(blockHeight < prevHeight); - - limboExit.isValid = true; - limboExit.input[InputNo].exitable = true; - limboExit.input[InputNo].challenge.resolved = true; - //[TODO] pay resolver exitStake - } + // function resolveInputSpendChallenge( + // bytes32 exitId, bytes memory inTxData, uint8 InputNo, + // bytes32[] memory _txProof, bytes32[] memory _youngestInputProof + // ) public { + // LimboExit memory limboExit = limboExits[exitId]; + // LimboChallenge memory challenge = limboExit.input[InputNo].challenge; + // bytes32[] memory _prevProof = challenge.proof; + // TxLib.Tx memory transferTx = TxLib.parseTx(inTxData); + // bytes32 inTxHash = keccak256(inTxData); + + // bytes32 previousHash; + // bytes memory previousTxData; + // uint64 previousPos; + + // (previousPos, previousHash, previousTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _prevProof); + // TxLib.Tx memory previousTx = TxLib.parseTx(previousTxData); + // uint32 prevHeight = bridge.periods[_prevProof[0]].height; + + // uint32 blockHeight = bridge.periods[_txProof[0]].height; + // require(blockHeight < prevHeight); + + // limboExit.isValid = true; + // limboExit.input[InputNo].exitable = true; + // limboExit.input[InputNo].challenge.resolved = true; + // //[TODO] pay resolver exitStake + // } function finalizeTopLimboExit(uint16 _color) public { bytes32 utxoId; @@ -392,26 +417,28 @@ contract ExitHandler is DepositHandler { require(tokens[_color].currentSize > 0, "The exit queue for color is empty"); LimboExit memory currentExit = limboExits[utxoId]; - uint256 amount; - if (currentExit.isValid == true){ - // all piggybacked outputs are paid out - for(uint8 i=0; i < currentExit.output.length; i++){ - LimboOut memory out = currentExit.output[i]; - if (out.exitable){ - amount = piggybackStake + out.amount; - tokens[out.color].addr.transferFrom(address(this), out.owner, amount); + bytes32 txHash = currentExit.txHash; + LimboTx memory txn = limboTxns[txHash]; + if (txn.isCanonical){ + // unspent piggybacked outputs will exit + for (uint8 i=0; i < txn.outputs.length; i++){ + if (txn.outputs[i].isPegged){ + if(txn.outputs[i].unspent){ + // output will exit the chain + } } } } else { - // all piggybacked inputs are paid out - for(uint8 i=0; i < currentExit.input.length; i++){ - LimboIn memory input = currentExit.input[i]; - if (input.exitable){ - amount = piggybackStake + input.amount; - tokens[input.color].addr.transferFrom(address(this), input.owner, amount); + // unspent piggybacked inputs will exit + for (uint8 i=0; i < txn.inputs.length; i++){ + if (txn.inputs[i].isPegged){ + if(txn.inputs[i].challenge.length == 0){ + // unchallenged inputs will exit the chain + } } } } + delete limboExits[utxoId]; } From 55459660ecae78b594027224278138eb908b8940 Mon Sep 17 00:00:00 2001 From: fnatic Date: Thu, 28 Feb 2019 03:43:28 +0530 Subject: [PATCH 6/6] New thoughts --- contracts/ExitHandler.sol | 226 ++++++++++++++++---------------------- test/exitHandler.js | 147 +++++++++++++------------ 2 files changed, 171 insertions(+), 202 deletions(-) diff --git a/contracts/ExitHandler.sol b/contracts/ExitHandler.sol index f990247..c72f335 100644 --- a/contracts/ExitHandler.sol +++ b/contracts/ExitHandler.sol @@ -42,23 +42,15 @@ contract ExitHandler is DepositHandler { } struct LimboExit { - uint256 amount; - uint16 color; - address owner; + address exitor; + bool isCanonical; bool finalized; uint256 priority; uint256 stake; - address exitor; uint8 _outputIndex; bytes32 txHash; - } - - struct LimboTx { - LimboOut[] outputs; - LimboIn[] inputs; - bool isCanonical; - // LimboIn[] inputs; - bytes txnData; + LimboIn inputs; + LimboOut outputs; } // struct LimboExit { @@ -73,11 +65,6 @@ contract ExitHandler is DepositHandler { // bytes32 txHash; // } - // struct LimboTx { - // LimboOut[] outputs; - // LimboInputChallenge[] challenges; - // } - struct LimboChallenge { address challenger; bool resolved; @@ -85,25 +72,20 @@ contract ExitHandler is DepositHandler { } struct LimboOut { - uint256 amount; + uint256 value; address owner; bool isPegged; - bool unspent; - LimboChallenge challenge; + bool isExitable; } struct LimboIn { address owner; uint256 amount; - // OutputId[] to; bool isPegged; + bool isExitable; LimboChallenge challenge; } - // struct Outputidx { - // uint8 pos; - // } - // struct LimboInputChallenge { // address challenger; // bool resolved; @@ -129,16 +111,13 @@ contract ExitHandler is DepositHandler { function initializeWithExit( Bridge _bridge, uint256 _exitDuration, - uint256 _exitStake, - uint256 _piggybackStake, - uint256 _challengeStake, - uint256 _limboPeriod) public initializer { + uint256 _exitStake) public initializer { initialize(_bridge); exitDuration = _exitDuration; exitStake = _exitStake; - challengeStake = _challengeStake; - piggybackStake = _piggybackStake; - limboPeriod = _limboPeriod; + // challengeStake = _challengeStake; + // piggybackStake = _piggybackStake; + // limboPeriod = _limboPeriod; emit MinGasPrice(0); } @@ -146,13 +125,13 @@ contract ExitHandler is DepositHandler { exitStake = _exitStake; } - function setPiggybackStake(uint256 _piggybackStake) public ifAdmin { - piggybackStake = _piggybackStake; - } + // function setPiggybackStake(uint256 _piggybackStake) public ifAdmin { + // piggybackStake = _piggybackStake; + // } - function setChallengeStake(uint256 _challengeStake) public ifAdmin { - challengeStake = _challengeStake; - } + // function setChallengeStake(uint256 _challengeStake) public ifAdmin { + // challengeStake = _challengeStake; + // } function setExitDuration(uint256 _exitDuration) public ifAdmin { exitDuration = _exitDuration; @@ -164,22 +143,26 @@ contract ExitHandler is DepositHandler { require(msg.value >= exitStake, "Not enough ether sent to pay for exit stake"); TxLib.Tx memory transferTx = TxLib.parseTx(inTxData); - TxLib.Output memory out = transferTx.outs[_outputIndex]; - - LimboTx memory limboTx; - limboTx.txn= transferTx; + TxLib.Output memory _output = transferTx.outs[_outputIndex]; + LimboExit memory limboExit; + LimboOut memory output; + TxLib.Output memory out; + for (uint8 i =0; i < transferTx.outs.length; i++){ - TxLib.Output memory outs = transferTx.outs[i]; - LimboOut memory output; - output.amount = outs.value; - output.owner = outs.owner; + out = transferTx.outs[i]; + output.value = out.value; + output.owner = out.owner; output.isPegged = false; - output.exitable = false; - limboTx.outputs[i] = output; - // txOuts[i] = output; + output.isExitable = false; + + if (i == _outputIndex){ + output.isExitable = true; + } + limboExit.outputs[i] = output; } - // limboTx.outputs= txOuts; + + // for (uint8 i =0; i < transferTx.ins.length; i++){ // TxLib.Input memory ins = transferTx.ins[i]; @@ -192,6 +175,8 @@ contract ExitHandler is DepositHandler { // challenge: {} // }); + + bytes32 inTxHash = keccak256(inTxData); utxoId = bytes32(uint256(_outputIndex) << 120 | uint120(uint256(inTxHash))); uint256 priority; @@ -199,15 +184,15 @@ contract ExitHandler is DepositHandler { uint32 timestamp; // validate youngestinputproof - bytes32 inputTxHash; + bytes32 youngestTxHash; uint64 txPos; - (txPos, inputTxHash,) = TxLib.validateProof(96, _youngestInputProof); + (txPos, youngestTxHash,) = TxLib.validateProof(96, _youngestInputProof); // priority based on youngestInput (height, timestamp) = bridge.periods(_youngestInputProof[0]); require(timestamp > 0, "The referenced period was not submitted to bridge"); - if (isNft(out.color)) { + if (isNft(_output.color)) { priority = (nftExitCounter << 128) | uint128(uint256(utxoId)); nftExitCounter++; } else { @@ -218,77 +203,81 @@ contract ExitHandler is DepositHandler { limboExit.finalized= false; limboExit.priority= priority; limboExit.exitor= msg.sender; - limboExit.amount= out.value; - limboExit.owner= out.owner; - limboExit.color= out.color; limboExit._outputIndex= _outputIndex; limboExit.txHash= inTxHash; limboExits[utxoId] = limboExit; - limboTxns[inTxHash] = limboTx; // emit LimboExitStarted( // inTxHash, // out.color, // utxoId // ); - tokens[out.color].insert(priority); + tokens[_output.color].insert(priority); return utxoId; } function joinLimboExit( - bytes32 exitId, uint8 _outputIndex) + bytes32 exitId, uint8 _index) public payable { - // honest owner of outputs will piggyback the exit if they also want to exit their funds + // honest owner of inputs or outputs can piggyback the exit if they also want to exit their funds require(msg.value >= piggybackStake, "Not enough ether sent to join the exit"); address owner = msg.sender; - // exitId is utxoId LimboExit memory limboExit = limboExits[exitId]; bytes32 inTxHash = limboExit.txHash; - LimboTx memory limboTx = limboTxns[inTxHash]; - if (limboTx.outputs[_outputIndex].owner == owner) { - // output is piggybacking - require(limboTx.outputs[_outputIndex].isPegged = false, "Already joined the exit"); + // output is piggybacking + if (limboExit.outputs[_index].owner == owner) { + require(limboExit.outputs[_index].isPegged = false, "Output already joined the exit"); - limboTx.outputs[_outputIndex].isPegged = true; - limboTx.outputs[_outputIndex].exitable = true; + limboExit.outputs[_index].isPegged = true; + limboExit.outputs[_index].exitable = true; + } + + // input is piggybacking + if (limboExit.inputs[_index].owner == owner) { + require(limboExit.inputs[_index].isPegged = false, "Input already joined the exit"); + + limboExit.inputs[_index].isPegged = true; + limboExit.inputs[_index].exitable = true; } } function challengeLimboExitByInputSpend( - bytes32 txHash, + bytes32 exitId, uint8 InputNo, uint8 _spendInputNo, bytes32[] memory _spendProof) public payable { // challenging canonicity require(msg.value >= challengeStake, "Not enough ether sent to challenge exit"); - LimboTx memory limboTx = limboTxns[exitId]; + LimboExit memory limboExit = limboExits[exitId]; bytes32 competingHash; bytes memory competingTxData; uint32 blockHeight; uint64 competingPos; - TxLib.Tx memory inputTx = TxLib.parseTx(limboTx.txnData); (competingPos, competingHash, competingTxData) = TxLib.validateProof(96, _spendProof); TxLib.Tx memory competingTx = TxLib.parseTx(competingTxData); + TxLib.Input memory inputTx = limboExit.txn.inputs[InputNo]; + blockHeight = bridge.periods[_spendProof[0]].height; // if canonical till now - if (limboTx.isCanonical == true){ - TxLib.Input memory inInput = inputTx.ins[InputNo]; + if (limboExit.isCanonical == true){ + LimboIn memory inInput = inputTx.inputs[InputNo]; TxLib.Input memory competingInput = competingTx.ins[_spendInputNo]; // proving non-canonical // if same inputs, add challenge require(inInput.r == competingInput.r); require(inInput.s == competingInput.s); require(inInput.v == competingInput.v); - limboTx.isCanonical = false; + limboExit.isCanonical = false; + limboExit.inputs[InputNo].isExitable = false; } else { @@ -296,7 +285,7 @@ contract ExitHandler is DepositHandler { bytes32 previousHash; bytes memory previousTxData; uint64 previousPos; - LimboChallenge memory prevChallenge = limboTx.inputs[InputNo].challenge; + LimboChallenge memory prevChallenge = limboExit.inputs[InputNo].challenge; bytes memory _prevProof = prevChallenge.proof; (previousPos, previousHash, previousTxData) = TxLib.validateProof(96, _prevProof); TxLib.Tx memory previousTx = TxLib.parseTx(previousTxData); @@ -304,7 +293,7 @@ contract ExitHandler is DepositHandler { require(blockHeight < prevHeight); // pay challengeStake to winner from exitHandler - // tokens[transferTx.outs[0].color].addr.transferFrom(address(this), msg.sender, challengeStake); + tokens[limboExit.color].addr.transferFrom(address(this), msg.sender, challengeStake); } limboTx.inputs[InputNo].challenge = LimboChallenge({ @@ -314,42 +303,20 @@ contract ExitHandler is DepositHandler { }); } - function challengeLimboExitByInclusionProof( - bytes32 txHash, - bytes32[] memory _inclusionProof) - public payable { - require(msg.value >= challengeStake, "Not enough ether sent to challenge exit"); - LimboTx memory limboTx = limboTxns[txHash]; - - // bytes32 competingHash; - bytes memory competingTxData; - // uint32 blockHeight; - // uint64 competingPos; - - TxLib.Tx memory inputTx = TxLib.parseTx(limboTx.txnData); - (,, competingTxData) = TxLib.validateProof(96, _inclusionProof); - TxLib.Tx memory competingTx = TxLib.parseTx(competingTxData); - bytes32 inTxHash = keccak256(competingTxData); - - require(inTxHash == txHash, "Invalid inclusion proof"); - - limboTx.isCanonical = true; - } - function challengeLimboExitByOutputSpend( - bytes32 txHash, uint8 outputNo, uint8 InputNo, + bytes32 exitId, uint8 outputNo, uint8 InputNo, bytes memory _spendProof) public payable { require(msg.value >= challengeStake, "Not enough ether sent to challenge exit"); - LimboTx memory limboTx = limboTxns[inTxHash]; - LimboOut memory limboOut = limboTx.outputs[outputNo]; + LimboExit memory limboExit = limboExits[exitId]; + LimboOut memory limboOut = limboExit.outputs[outputNo]; TxLib.Tx memory transferTx = TxLib.parseTx(limboTx.txnData); (spendPos, spendHash, spendData) = TxLib.validateProof(96, _spendProof); if (limboOut.isPegged){ - if (limboOut.unspent) { + if (limboOut.isExitable) { uint256 offset = uint8(uint256(_spendProof[1] >> 248)); // getting spendTx input address address owner = TxLib.recoverTxSigner(offset, _spendProof); @@ -364,7 +331,7 @@ contract ExitHandler is DepositHandler { bytes32 previousHash; bytes memory previousTxData; uint64 previousPos; - LimboChallenge memory prevChallenge = limboTx.outputs[outputNo].challenge; + LimboChallenge memory prevChallenge = limboExit.outputs[outputNo].challenge; bytes memory _prevProof = prevChallenge.proof; (previousPos, previousHash, previousTxData) = TxLib.validateProof(96, _prevProof); TxLib.Tx memory previousTx = TxLib.parseTx(previousTxData); @@ -372,8 +339,8 @@ contract ExitHandler is DepositHandler { require(blockHeight < prevHeight); } - limboTxns[inTxHash].outputs[outputNo].unspent = false; - limboTxns[inTxHash].outputs[outputNo].challenge = LimboChallenge({ + limboExits[inTxHash].outputs[outputNo].isExitable = false; + limboExits[inTxHash].outputs[outputNo].challenge = LimboChallenge({ owner: msg.sender, resolved: false, proof: _spendProof @@ -381,32 +348,31 @@ contract ExitHandler is DepositHandler { } } - // function resolveInputSpendChallenge( - // bytes32 exitId, bytes memory inTxData, uint8 InputNo, - // bytes32[] memory _txProof, bytes32[] memory _youngestInputProof - // ) public { - // LimboExit memory limboExit = limboExits[exitId]; - // LimboChallenge memory challenge = limboExit.input[InputNo].challenge; - // bytes32[] memory _prevProof = challenge.proof; - // TxLib.Tx memory transferTx = TxLib.parseTx(inTxData); - // bytes32 inTxHash = keccak256(inTxData); - - // bytes32 previousHash; - // bytes memory previousTxData; - // uint64 previousPos; - - // (previousPos, previousHash, previousTxData) = TxLib.validateProof(32 * (_youngestInputProof.length + 2) + 64, _prevProof); - // TxLib.Tx memory previousTx = TxLib.parseTx(previousTxData); - // uint32 prevHeight = bridge.periods[_prevProof[0]].height; - - // uint32 blockHeight = bridge.periods[_txProof[0]].height; - // require(blockHeight < prevHeight); - - // limboExit.isValid = true; - // limboExit.input[InputNo].exitable = true; - // limboExit.input[InputNo].challenge.resolved = true; - // //[TODO] pay resolver exitStake - // } + function resolveInputSpendChallenge( + bytes32 exitId, bytes memory inTxData, uint8 InputNo, + bytes32[] memory _txProof) public { + LimboExit memory limboExit = limboExits[exitId]; + LimboChallenge memory challenge = limboExit.inputs[InputNo].challenge; + bytes32[] memory _prevProof = challenge.proof; + TxLib.Tx memory transferTx = TxLib.parseTx(inTxData); + bytes32 inTxHash = keccak256(inTxData); + + bytes32 previousHash; + bytes memory previousTxData; + uint64 previousPos; + + (previousPos, previousHash, previousTxData) = TxLib.validateProof(96, _prevProof); + TxLib.Tx memory previousTx = TxLib.parseTx(previousTxData); + uint32 prevHeight = bridge.periods[_prevProof[0]].height; + + uint32 blockHeight = bridge.periods[_txProof[0]].height; + require(blockHeight < prevHeight); + + limboExit.isValid = true; + limboExit.input[InputNo].exitable = true; + limboExit.input[InputNo].challenge.resolved = true; + //[TODO] pay resolver exitStake + } function finalizeTopLimboExit(uint16 _color) public { bytes32 utxoId; diff --git a/test/exitHandler.js b/test/exitHandler.js index 09e23ca..179062a 100644 --- a/test/exitHandler.js +++ b/test/exitHandler.js @@ -114,8 +114,11 @@ contract('ExitHandler', (accounts) => { await proxy.applyProposal(data, {from: accounts[2]}); const vaultCont = await ExitHandler.new(); - data = await vaultCont.contract.methods.initializeWithExit(bridge.address, exitDuration, exitStake, piggybackStake, challengeStake, - limboPeriod).encodeABI(); + data = await vaultCont.contract.methods.initializeWithExit(bridge.address, exitDuration, exitStake).encodeABI(); + // data = await vaultCont.contract.methods.initializeWithExit(bridge.address, exitDuration, exitStake, piggybackStake, challengeStake, + // limboPeriod).encodeABI(); + + proxy = await AdminableProxy.new(vaultCont.address, data, {from: accounts[2]}); exitHandler = await ExitHandler.at(proxy.address); @@ -152,105 +155,105 @@ contract('ExitHandler', (accounts) => { assert(aliceBalanceBefore.add(new BN(50)).eq(aliceBalanceAfter)); }); - it('Should allow to challenge tx by output spend and prevent inflight exit', async () => { - const period = await submitNewPeriod([depositTx]); - const depositProof = period.proof(depositTx); + // it('Should allow to challenge tx by output spend and prevent inflight exit', async () => { + // const period = await submitNewPeriod([depositTx]); + // const depositProof = period.proof(depositTx); - //bob spends utxo1 by sending it to charlie - const spendTx = Tx.transfer( - [new Input(new Outpoint(transferTx.hash(), 0))], - [new Output(50, charlie)] - ).sign([bobPriv]); + // //bob spends utxo1 by sending it to charlie + // const spendTx = Tx.transfer( + // [new Input(new Outpoint(transferTx.hash(), 0))], + // [new Output(50, charlie)] + // ).sign([bobPriv]); - const inTxData = transferTx.hex(); - const spendTxData = spendTx.hex(); + // const inTxData = transferTx.hex(); + // const spendTxData = spendTx.hex(); - // any user can start the exit for this transaction - exitId = await exitHandler.startLimboExit(inTxData, {from: bob, value: exitStake}); + // // any user can start the exit for this transaction + // exitId = await exitHandler.startLimboExit(inTxData, {from: bob, value: exitStake}); - // Bob piggybacks and joins a Limo exit by its id - await exitHandler.joinLimboExit(exitId, 0, {from: bob, value: piggybackStake}); + // // Bob piggybacks and joins a Limo exit by its id + // await exitHandler.joinLimboExit(exitId, 0, {from: bob, value: piggybackStake}); - const bobBalanceBefore = await nativeToken.balanceOf(bob); - await exitHandler.challengeLimboExit(exitId, inTxData, 0, depositProof, spendProof, 0, {from: pete, value: challengeStake}); + // const bobBalanceBefore = await nativeToken.balanceOf(bob); + // await exitHandler.challengeLimboExit(exitId, inTxData, 0, depositProof, spendProof, 0, {from: pete, value: challengeStake}); - const challengeTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); - await time.increaseTo(challengeTime); + // const challengeTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); + // await time.increaseTo(challengeTime); - // const responseTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); - // await time.increaseTo(responseTime); + // // const responseTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); + // // await time.increaseTo(responseTime); - await exitHandler.finalizeTopLimboExit(nativeTokenColor); + // await exitHandler.finalizeTopLimboExit(nativeTokenColor); - const bobBalanceAfter = await nativeToken.balanceOf(bob); - assert(bobBalanceAfter.eq(bobBalanceBefore)); - }); + // const bobBalanceAfter = await nativeToken.balanceOf(bob); + // assert(bobBalanceAfter.eq(bobBalanceBefore)); + // }); - it('Should allow user to challenge input spent and prevent inflight tx', async () => { - const period = await submitNewPeriod([depositTx); - const depositProof = period.proof(depositTx); + // it('Should allow user to challenge input spent and prevent inflight tx', async () => { + // const period = await submitNewPeriod([depositTx); + // const depositProof = period.proof(depositTx); - //alice double spends utxo1 by sending it to charlie - const spendTx = Tx.transfer( - [new Input(new Outpoint(depositTx.hash(), 0))], - [new Output(50, charlie)] - ).sign([alicePriv]); + // //alice double spends utxo1 by sending it to charlie + // const spendTx = Tx.transfer( + // [new Input(new Outpoint(depositTx.hash(), 0))], + // [new Output(50, charlie)] + // ).sign([alicePriv]); - const inTxData = transferTx.hex(); - const spendTxData = spendTx.hex(); + // const inTxData = transferTx.hex(); + // const spendTxData = spendTx.hex(); - exitId = await exitHandler.startLimboExit(inTxData, 0, {from: bob, value: exitStake}); + // exitId = await exitHandler.startLimboExit(inTxData, 0, {from: bob, value: exitStake}); - // Bob piggybacks and joins a Limo exit by its id - await exitHandler.joinLimboExit(exitId, 0, {from: bob, value: piggybackStake}); + // // Bob piggybacks and joins a Limo exit by its id + // await exitHandler.joinLimboExit(exitId, 0, {from: bob, value: piggybackStake}); - const bobBalanceBefore = await nativeToken.balanceOf(bob); - // challenge to tx's canonicity - await exitHandler.challengeLimboExit(exitId, inTxData, 0, depositProof, spendProof, 0, {from: pete, value: challengeStake}); + // const bobBalanceBefore = await nativeToken.balanceOf(bob); + // // challenge to tx's canonicity + // await exitHandler.challengeLimboExit(exitId, inTxData, 0, depositProof, spendProof, 0, {from: pete, value: challengeStake}); - const challengeTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); - await time.increaseTo(challengeTime); + // const challengeTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); + // await time.increaseTo(challengeTime); - await exitHandler.finalizeTopLimboExit(nativeTokenColor); + // await exitHandler.finalizeTopLimboExit(nativeTokenColor); - // const responseTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); - // await time.increaseTo(responseTime); + // // const responseTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); + // // await time.increaseTo(responseTime); - const bobBalanceAfter = await nativeToken.balanceOf(bob); - assert(bobBalanceAfter.eq(bobBalanceBefore)); - }); + // const bobBalanceAfter = await nativeToken.balanceOf(bob); + // assert(bobBalanceAfter.eq(bobBalanceBefore)); + // }); - it('Should resolve a output challenge and exit inflight tx', async () => { - const period = await submitNewPeriod([depositTx); - const depositProof = period.proof(depositTx); + // it('Should resolve a output challenge and exit inflight tx', async () => { + // const period = await submitNewPeriod([depositTx); + // const depositProof = period.proof(depositTx); - const spendTx = Tx.transfer( - [new Input(new Outpoint(transferTx.hash(), 0))], - [new Output(50, charlie)] - ).sign([bobPriv]); + // const spendTx = Tx.transfer( + // [new Input(new Outpoint(transferTx.hash(), 0))], + // [new Output(50, charlie)] + // ).sign([bobPriv]); - const inTxData = transferTx.hex(); - const spendTxData = spendTx.hex(); + // const inTxData = transferTx.hex(); + // const spendTxData = spendTx.hex(); - exitId = await exitHandler.startLimboExit(inTxData, 0, {from: bob, value: exitStake}); + // exitId = await exitHandler.startLimboExit(inTxData, 0, {from: bob, value: exitStake}); - // Alice piggybacks and joins a Limo exit by its id - await exitHandler.joinLimboExit(exitId, 0, {from: alice, value: piggybackStake}); + // // Alice piggybacks and joins a Limo exit by its id + // await exitHandler.joinLimboExit(exitId, 0, {from: alice, value: piggybackStake}); - const aliceBalanceBefore = await nativeToken.balanceOf(alice); - await exitHandler.challengeLimboExit(exitId, inTxData, 0, depositProof, spendProof, 0, {from: pete, value: challengeStake}); + // const aliceBalanceBefore = await nativeToken.balanceOf(alice); + // await exitHandler.challengeLimboExit(exitId, inTxData, 0, depositProof, spendProof, 0, {from: pete, value: challengeStake}); - const challengeTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); - await time.increaseTo(challengeTime); + // const challengeTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); + // await time.increaseTo(challengeTime); - await exitHandler.resolveInputSpendChallenge(exitId, inTxData, 0, depositData, txProof, depositProof); + // await exitHandler.resolveInputSpendChallenge(exitId, inTxData, 0, depositData, txProof, depositProof); - const responseTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); - await time.increaseTo(responseTime); + // const responseTime = (await time.latest()) + (2 * time.duration.seconds(limboPeriod/2)); + // await time.increaseTo(responseTime); - const aliceBalanceAfter = await nativeToken.balanceOf(alice); - assert(aliceBalanceAfter.eq(aliceBalanceBefore.add(new BN(50)))); - }); + // const aliceBalanceAfter = await nativeToken.balanceOf(alice); + // assert(aliceBalanceAfter.eq(aliceBalanceBefore.add(new BN(50)))); + // }); it('Should allow to exit deposit utxo', async () => { const period = await submitNewPeriod([depositTx]);