Skip to content

Commit

Permalink
Adding basic exit and challenge functions
Browse files Browse the repository at this point in the history
  • Loading branch information
nanspro committed Jan 28, 2019
1 parent 9eb350d commit 3ae5bc7
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 0 deletions.
90 changes: 90 additions & 0 deletions contracts/ExitHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,64 @@ contract ExitHandler is DepositHandler {
exitDuration = _exitDuration;
}

function startLimboExit(
bytes memory limboTxData, bytes32 signature, uint8 _outputIndex,
bytes memory depositTxData, bytes memory depositProof
) public payable {
// Transaction is from A to B
// B will have to pay exitStake
require(msg.value >= exitStake, "Not enough ether sent to pay for exit stake");
TxLib.Tx memory transferTx = TxLib.parseTx(limboTxData);
TxLib.Output memory out = transferTx.outs[_outputIndex];

// check if B has the right to do the exit for A or not
bytes32 sigHash = TxLib.getSigHash(limboTxData);
require(sigHash == signature);


// isWellDecodedTransaction();
bytes32 limboTxHash = keccak256(limboTxData);

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

require(depositProof.length > 0, "Not a limbo exit");

// check youngest input tx inclusion in the root chain block
bytes32 depositTxHash;
(txPos, depositTxHash,) = TxLib.validateProof(96, depositProof);
require(
depositTxHash == transferTx.ins[*].outpoint.hash,
"Input from the proof is not referenced in transfer tx"
);

if (isNft(out.color)) {
priority = (nftExitCounter << 128) | uint128(uint256(utxoId));
nftExitCounter++;
} else {
priority = getERC20ExitPriority(*, utxoId, txPos);
}

exits[utxoId] = Exit({
owner: out.owner,
color: out.color,
amount: out.value,
finalized: false,
stake: exitStake,
priorityTimestamp: timestamp
});
emit ExitStarted(
limboTxHash,
_outputIndex,
out.color,
out.owner,
out.value
);

tokens[out.color].insert(priority);

}

function startExit(
bytes32[] memory _youngestInputProof, bytes32[] memory _proof,
uint8 _outputIndex, uint8 _inputIndex
Expand Down Expand Up @@ -320,6 +378,38 @@ contract ExitHandler is DepositHandler {
exits[utxoId].finalized = true;
}

function challengeLimboExit(
bytes32 utxoId,
bytes32[] spendProof
) public payable {
require(msg.value >= exitStake, "Not enough ether sent to pay for exit stake");

bytes32 spendTxHash;
(, spendTxHash, spendTxData) = TxLib.validateProof(96, spendProof);
TxLib.Tx memory spendTx = TxLib.parseTx(spendTxData);
TxLib.Output memory out = spendTx.outs[0];

Exit memory limboExit = exits[utxoId]
require(limboExit.isLimbo == true);

// // check if A is trying to use the coin again
// // check if B is sending that coin to someone else

// // make sure one is spending the other one
// require(txHash0 == txn.ins[_inputIndex].outpoint.hash);
// require(0 == spendTx.ins[0].outpoint.pos);

// //If transfer then make sure signature is correct
// bytes32 sigHash = TxLib.getSigHash(txData);
// address signer = ecrecover(
// sigHash,
// txn.ins[_inputIndex].v,
// txn.ins[_inputIndex].r,
// txn.ins[_inputIndex].s
// );
// require(exits[utxoId].owner == signer);
}

function challengeExit(
bytes32[] memory _proof,
bytes32[] memory _prevProof,
Expand Down
80 changes: 80 additions & 0 deletions test/exitHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,33 @@ contract('ExitHandler', (accounts) => {
assert(aliceBalanceBefore.add(new BN(50)).eq(aliceBalanceAfter));
});

it('Should allow to limbo exit', async () => {
const period = await submitNewPeriod(depositTx);
const depositProof = period.proof(depositTx);
const outputIndex = 1;
const exitStake = exitHandler.exitStake();

// transaction hash + constant signed by alice
const signature = (transferTx + c).sign(alicePriv);

const inTxData = Tx.parseToParams(transferTx);

// Bob will start the exit for Alice
await exitHandler.startLimboExit(inTxData, signature, outputIndex,
depositTx, depositProof, {from: bob, value: exitStake});

const aliceBalanceBefore = await nativeToken.balanceOf(alice);

const exitTime = (await time.latest()) + (2 * time.duration.seconds(exitDuration));
await time.increaseTo(exitTime);

await exitHandler.finalizeTopExit(nativeTokenColor);

const aliceBalanceAfter = await nativeToken.balanceOf(alice);

assert(aliceBalanceBefore.add(new BN(50)).eq(aliceBalanceAfter));
});

it('Should allow to exit deposit utxo', async () => {
const period = await submitNewPeriod([depositTx]);

Expand Down Expand Up @@ -303,6 +330,59 @@ contract('ExitHandler', (accounts) => {

});

it('Should allow to challenge limbo exit and prevent it', async () => {
// utxo that will have spend exit utxo
const period = await submitNewPeriod(depositTx);
const spendTx = Tx.transfer(
[new Input(new Outpoint(transferTx.hash(), 0))],
[new Output(50, charlie)]
).sign([bobPriv]);

const depositProof = period.proof(depositTx);
const outputIndex = 1;
const exitStake = exitHandler.exitStake();
const signature = (transferTx + c).sign(alicePriv);

const txs = [spendTx];
const block = txs.reduce(
(b, tx) => b.addTx(tx),
new Block(33)
);
prevRoot = period.merkleRoot();
const newPeriod = new Period(prevRoot, [block]);
const spendProof = newPeriod.proof(spendTx);

const inTxData = Tx.parseToParams(transferTx);

// withdraw output
const event = await exitHandler.startLimboExit(inTxData, signature, outputIndex,
depositTx, depositProof, {from: bob, value: exitStake});

const utxoId = exitUtxoId(event);

// challenge exit and make sure exit is removed
assert.equal((await exitHandler.exits(utxoId))[2], bob);
await exitHandler.challengeLimboExit(utxoId, spendProof);

const bal1 = await nativeToken.balanceOf(bob);
const bal3 = await nativeToken.balanceOf(alice);

const exitTime = (await time.latest()) + (2 * time.duration.seconds(exitDuration));
await time.increaseTo(exitTime);

await exitHandler.finalizeTopExit(0);

const bal2 = await nativeToken.balanceOf(bob);
const bal4 = await nativeToken.balanceOf(alice);
// check transfer didn't happen
assert.equal(bal1.toNumber(), bal2.toNumber());
assert.equal(bal3.toNumber(), bal4.toNumber());

// check exit was evicted from PriorityQueue
assert.equal((await exitHandler.tokens(0))[1], 0);

});

it('Should allow to challenge exit by verified tx', async () => {
transferTx = Tx.transfer(
[new Input(new Outpoint(depositTx.hash(), 0))],
Expand Down

0 comments on commit 3ae5bc7

Please sign in to comment.