Skip to content

Commit

Permalink
feat: add ife claim
Browse files Browse the repository at this point in the history
  • Loading branch information
souradeep-das committed Dec 8, 2020
1 parent 6a30e0f commit 5e793c3
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 58 deletions.
160 changes: 118 additions & 42 deletions plasma_framework/contracts/poc/fast_exits/Quasar.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ pragma solidity 0.5.11;
pragma experimental ABIEncoderV2;

import "../../src/framework/PlasmaFramework.sol";
import "../../src/exits/payment/PaymentExitGame.sol";
import "../../src/utils/PosLib.sol";
import "../../src/utils/Merkle.sol";
import "../../src/exits/utils/ExitId.sol";
import "../../src/exits/payment/routers/PaymentInFlightExitRouter.sol";
import "../../src/transactions/PaymentTransactionModel.sol";
import "../../src/transactions/GenericTransaction.sol";
import "../../src/exits/utils/MoreVpFinalization.sol";
import "../../src/exits/interfaces/ISpendingCondition.sol";
import "../../src/exits/registries/SpendingConditionRegistry.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol";
Expand All @@ -17,9 +23,15 @@ import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol";
contract Quasar {
using SafeERC20 for IERC20;
using SafeMath for uint256;
using SafeMath for uint64;
using PosLib for PosLib.Position;

PlasmaFramework public plasmaFramework;
// the contract works with the current exit game
// any changes to the exit game would require modifications to this contract
// verify the exitGame before interacting
PaymentExitGame public paymentExitGame;
SpendingConditionRegistry public spendingConditionRegistry;

address public quasarOwner;
address public quasarMaintainer;
Expand All @@ -28,13 +40,15 @@ contract Quasar {
uint256 public bondValue;
// bond is added to the reserve only when tickets are flushed, bond is returned every other time
uint256 private bondReserve;
bool public isPaused;

struct Ticket {
address payable outputOwner;
uint256 validityTimestamp;
uint256 reservedAmount;
address token;
uint256 bondValue;
bytes rlpOutputCreationTx;
bool isClaimed;
}

Expand All @@ -55,6 +69,11 @@ contract Quasar {
_;
}

modifier onlyWhenNotPaused() {
require(!isPaused, "The Quasar contract is paused");
_;
}

/**
* @dev Constructor, takes params to set up quasar contract
* @param plasmaFrameworkContract Plasma Framework contract address
Expand All @@ -63,8 +82,10 @@ contract Quasar {
* @param _waitingPeriod Waiting period from submission to processing claim
* @param _bondValue bond to obtain tickets
*/
constructor (address plasmaFrameworkContract, address _quasarOwner, uint256 _safePlasmaBlockNum, uint256 _waitingPeriod, uint256 _bondValue) public {
constructor (address plasmaFrameworkContract, address spendingConditionRegistryContract, address _quasarOwner, uint256 _safePlasmaBlockNum, uint256 _waitingPeriod, uint256 _bondValue) public {
plasmaFramework = PlasmaFramework(plasmaFrameworkContract);
paymentExitGame = PaymentExitGame(plasmaFramework.exitGames(1));
spendingConditionRegistry = SpendingConditionRegistry(spendingConditionRegistryContract);
quasarOwner = _quasarOwner;
quasarMaintainer = msg.sender;
safePlasmaBlockNum = _safePlasmaBlockNum;
Expand All @@ -73,6 +94,9 @@ contract Quasar {
bondReserve = 0;
}

////////////////////////////////////////////
// Maintenance methods
////////////////////////////////////////////
/**
* @dev Update the safe blocknum limit
* @param newSafePlasmaBlockNum new blocknum limit, has to be higher than previous blocknum limit
Expand All @@ -99,6 +123,14 @@ contract Quasar {
bondReserve = bondReserve.add(ticketData[utxoPos].bondValue);
}
function pauseQuasar() public onlyQuasarMaintainer() {
isPaused = true;
}
function resumeQuasar() public onlyQuasarMaintainer() {
isPaused = false;
}
/**
* @dev Add Eth Liquid funds to the quasar
*/
Expand Down Expand Up @@ -127,14 +159,17 @@ contract Quasar {
msg.sender.transfer(amount);
}
////////////////////////////////////////////
// Exit procedure
////////////////////////////////////////////
/**
* @dev Obtain a ticket from the Quasar
* @notice Ticket is valid for four hours, pay bond here for obtaining ticket
* @param utxoPos Output that will be spent to the quasar later, is the ticket identifier
* @param rlpOutputCreationTx RLP-encoded transaction that created the output
* @param outputCreationTxInclusionProof Transaction inclusion proof
*/
function obtainTicket(uint256 utxoPos, bytes memory rlpOutputCreationTx, bytes memory outputCreationTxInclusionProof) public payable {
function obtainTicket(uint256 utxoPos, bytes memory rlpOutputCreationTx, bytes memory outputCreationTxInclusionProof) public payable onlyWhenNotPaused() {
PosLib.Position memory utxoPosDecoded = PosLib.decode(utxoPos);
require(utxoPosDecoded.blockNum <= safePlasmaBlockNum, "UTXO is from a block over the safe limit");
Expand Down Expand Up @@ -162,7 +197,7 @@ contract Quasar {
require(msg.value == bondValue, "Bond Value incorrect");

tokenUsableCapacity[outputData.token] = tokenUsableCapacity[outputData.token].sub(outputData.amount);
ticketData[utxoPos] = Ticket(msg.sender, block.timestamp.add(14400), outputData.amount, outputData.token, msg.value, false);
ticketData[utxoPos] = Ticket(msg.sender, block.timestamp.add(14400), outputData.amount, outputData.token, msg.value, rlpOutputCreationTx, false);
}

// for simplicity fee has to be from a seperate input in the tx to quasar
Expand All @@ -179,24 +214,9 @@ contract Quasar {
bytes memory rlpTxToQuasarOwner,
bytes memory txToQuasarOwnerInclusionProof
) public {
require(!ticketData[utxoPos].isClaimed, "Already Claimed");
require(ticketData[utxoPos].outputOwner == msg.sender, "Not called by the ticket owner");
uint256 expiryTimestamp = ticketData[utxoPos].validityTimestamp;
require(expiryTimestamp != 0 && block.timestamp <= expiryTimestamp, "Ticket is not valid");

PaymentTransactionModel.Transaction memory decodedTx
= PaymentTransactionModel.decode(rlpTxToQuasarOwner);

// first input should be the Utxo with which ticket was obtained
require(decodedTx.inputs[0] == bytes32(utxoPos), "Wrong Tx provided");

// first output should be the utxo for Quasar owner
FungibleTokenOutputModel.Output memory outputData
= PaymentTransactionModel.getOutput(decodedTx, 0);

// verify output to Quasar Owner
verifyOwnership(outputData, quasarOwner);

verifyTicketValidityForClaim(utxoPos);

verifyClaimTxCorrectlyFormed(utxoPos, rlpTxToQuasarOwner);
PosLib.Position memory utxoQuasarOwnerDecoded = PosLib.decode(utxoPosQuasarOwner);
require(MoreVpFinalization.isStandardFinalized(
plasmaFramework,
Expand All @@ -205,30 +225,44 @@ contract Quasar {
txToQuasarOwnerInclusionProof
), "Provided Tx doesn't exist");
ticketData[utxoPos].isClaimed = true;
claimData[utxoPos] = Claim(rlpTxToQuasarOwner, block.timestamp.add(waitingPeriod), true);
}
// considering fee as a separate input
require(ticketData[utxoPos].reservedAmount == outputData.amount, "Wrong Amount Sent to Quasar Owner");
require(ticketData[utxoPos].token == outputData.token, "Wrong Token Sent to Quasar Owner");
function ifeClaim(uint256 utxoPos, bytes memory inFlightClaimTx) public {
verifyTicketValidityForClaim(utxoPos);
verifyClaimTxCorrectlyFormed(utxoPos, inFlightClaimTx);
//verify IFE started
uint168 exitId = ExitId.getInFlightExitId(inFlightClaimTx);
uint168[] memory exitIdArr = new uint168[](1);
exitIdArr[0] = exitId;
PaymentExitDataModel.InFlightExit[] memory ifeData = paymentExitGame.inFlightExits(exitIdArr);
require(ifeData[0].exitStartTimestamp != 0, "IFE has not been started");
// this might be overriden by the ticket expiry check usually, but if the ticket is obtained later
// this check would always make sure there is enought time to piggyback
// ifeClaims should start within 8hrs from starting IFE to enable sufficient time to piggyback
require(block.timestamp <= ifeData[0].exitStartTimestamp.add(28800), "IFE Claim period has passed");
ticketData[utxoPos].isClaimed = true;
claimData[utxoPos] = Claim(rlpTxToQuasarOwner, block.timestamp.add(waitingPeriod), true);
// 7+1 days waiting period for IFE Claims
claimData[utxoPos] = Claim(inFlightClaimTx, block.timestamp.add(691200), true);
}
/**
* @dev Challenge an active claim
* @dev Challenge an active claim, can be used to challenge IFEClaims as well
* @notice A challenge is required only when a tx that spends the same utxo was included previously
* @param utxoPos pos of the output, which is the ticket identifier
* @param rlpChallengeTx RLP-encoded challenge transaction
* @param challengeTxInputIndex index pos of the same utxo in the challenge transaction
* @param challengeTxInclusionProof Challenge transaction inclusion proof
* @param challengeTxPos Tx pos of the challenge transaction
* @param challengingTxWitness Witness for challenging transaction
*/
function challengeClaim(
uint256 utxoPos,
bytes memory rlpChallengeTx,
uint16 challengeTxInputIndex,
bytes memory challengeTxInclusionProof,
uint256 challengeTxPos
bytes memory challengingTxWitness
) public {
require(ticketData[utxoPos].isClaimed && claimData[utxoPos].isValid, "Not Challengeable");
require(block.timestamp <= claimData[utxoPos].finalizationTimestamp, "Challenge Period Over");
Expand All @@ -237,20 +271,13 @@ contract Quasar {
"The challenging transaction is the same as the claim transaction"
);
PaymentTransactionModel.Transaction memory decodedChallengeTx
= PaymentTransactionModel.decode(rlpChallengeTx);
require(MoreVpFinalization.isProtocolFinalized(
plasmaFramework,
rlpChallengeTx
), "Provided Challenge Tx is not protocol finalized");
require(decodedChallengeTx.inputs[challengeTxInputIndex] == bytes32(utxoPos), "Wrong Tx provided");
PosLib.Position memory challengeTxPosDecoded = PosLib.decode(challengeTxPos);
verifySpendingCondition(utxoPos, rlpChallengeTx, challengeTxInputIndex, challengingTxWitness);
require(MoreVpFinalization.isStandardFinalized(
plasmaFramework,
rlpChallengeTx,
challengeTxPosDecoded,
challengeTxInclusionProof
), "Provided Challenge Tx doesn't exist");

claimData[utxoPos].isValid = false;
Ticket memory ticket = ticketData[utxoPos];
address token = ticket.token;
Expand All @@ -273,6 +300,9 @@ contract Quasar {
outputOwner.transfer(totalAmount);
}
////////////////////////////////////////////
// Helper methods
////////////////////////////////////////////
/**
* @dev Verify the owner of the output
* @param output Output Data
Expand All @@ -282,5 +312,51 @@ contract Quasar {
address outputOwner = PaymentTransactionModel.getOutputOwner(output);
require(outputOwner == expectedOutputOwner, "Was not called by the Output owner");
}
function verifySpendingCondition(uint256 utxoPos, bytes memory rlpChallengeTx, uint16 challengeTxInputIndex, bytes memory challengingTxWitness) private {
GenericTransaction.Transaction memory challengingTx = GenericTransaction.decode(rlpChallengeTx);
GenericTransaction.Transaction memory inputTx = GenericTransaction.decode(ticketData[utxoPos].rlpOutputCreationTx);
PosLib.Position memory utxoPosDecoded = PosLib.decode(utxoPos);
GenericTransaction.Output memory output = GenericTransaction.getOutput(inputTx, utxoPosDecoded.outputIndex);
ISpendingCondition condition = spendingConditionRegistry.spendingConditions(
output.outputType, challengingTx.txType
);
require(address(condition) != address(0), "Spending condition contract not found");
bool isSpent = condition.verify(
ticketData[utxoPos].rlpOutputCreationTx,
utxoPos,
rlpChallengeTx,
challengeTxInputIndex,
challengingTxWitness
);
require(isSpent, "Spending condition failed");
}

function verifyTicketValidityForClaim(uint256 utxoPos) private {
require(!ticketData[utxoPos].isClaimed, "Already Claimed");
require(ticketData[utxoPos].outputOwner == msg.sender, "Not called by the ticket owner");
uint256 expiryTimestamp = ticketData[utxoPos].validityTimestamp;
require(expiryTimestamp != 0 && block.timestamp <= expiryTimestamp, "Ticket is not valid");
}

function verifyClaimTxCorrectlyFormed(uint256 utxoPos, bytes memory claimTx) private {
PaymentTransactionModel.Transaction memory decodedTx
= PaymentTransactionModel.decode(claimTx);

// first input should be the Utxo with which ticket was obtained
require(decodedTx.inputs[0] == bytes32(utxoPos), "Wrong Tx provided");

// first output should be the utxo for Quasar owner
FungibleTokenOutputModel.Output memory outputData
= PaymentTransactionModel.getOutput(decodedTx, 0);

// verify output to Quasar Owner
verifyOwnership(outputData, quasarOwner);
// considering fee as a separate input
require(ticketData[utxoPos].reservedAmount == outputData.amount, "Wrong Amount Sent to Quasar Owner");
require(ticketData[utxoPos].token == outputData.token, "Wrong Token Sent to Quasar Owner");
}

}
40 changes: 24 additions & 16 deletions plasma_framework/test/endToEndTests/QuasarExit.e2e.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const PaymentExitGame = artifacts.require('PaymentExitGame');
const PlasmaFramework = artifacts.require('PlasmaFramework');
const SpendingConditionRegistry = artifacts.require('SpendingConditionRegistry');
const Quasar = artifacts.require('../Quasar');
const EthVault = artifacts.require('EthVault');
const Erc20Vault = artifacts.require('Erc20Vault');
Expand All @@ -14,6 +15,8 @@ const { MerkleTree } = require('../helpers/merkle.js');
const { PaymentTransactionOutput, PaymentTransaction } = require('../helpers/transaction.js');
const { spentOnGas } = require('../helpers/utils.js');
const { buildUtxoPos } = require('../helpers/positions.js');
const { sign } = require('../helpers/sign.js');
const { hashTx } = require('../helpers/paymentEip712.js');
const Testlang = require('../helpers/testlang.js');
const config = require('../../config.js');

Expand Down Expand Up @@ -47,12 +50,25 @@ contract(
await Promise.all([setupAccount(), deployStableContracts()]);
});

const setupContracts = async () => {
this.framework = await PlasmaFramework.deployed();
this.spendingConditionRegistry = await SpendingConditionRegistry.deployed();
this.ethVault = await EthVault.at(await this.framework.vaults(config.registerKeys.vaultId.eth));
this.erc20Vault = await Erc20Vault.at(await this.framework.vaults(config.registerKeys.vaultId.erc20));
this.exitGame = await PaymentExitGame.at(
await this.framework.exitGames(
config.registerKeys.txTypes.payment,
),
);
};

const setupQuasar = async () => {
this.waitingPeriod = 14400;
this.dummyQuasarBondValue = 500;

this.quasar = await Quasar.new(
this.framework.address,
this.spendingConditionRegistry.address,
quasarOwner,
0,
this.waitingPeriod,
Expand All @@ -61,18 +77,6 @@ contract(
);
};

const setupContracts = async () => {
this.framework = await PlasmaFramework.deployed();

this.ethVault = await EthVault.at(await this.framework.vaults(config.registerKeys.vaultId.eth));
this.erc20Vault = await Erc20Vault.at(await this.framework.vaults(config.registerKeys.vaultId.erc20));
this.exitGame = await PaymentExitGame.at(
await this.framework.exitGames(
config.registerKeys.txTypes.payment,
),
);
};

const aliceDepositsETH = async () => {
const depositBlockNum = (await this.framework.nextDepositBlock()).toNumber();
this.depositUtxoPos = buildUtxoPos(depositBlockNum, 0, 0);
Expand Down Expand Up @@ -497,6 +501,7 @@ contract(
await aliceTransferEth(bob, DEPOSIT_VALUE);
this.bobTransferTxUtxoPos = this.transferUtxoPos;
this.bobTransferTx = this.transferTx;
this.bobTransferTxObject = this.transferTxObject;
this.bobTransferTxMerkleProof = this.merkleProofForTransferTx;
// spends same output in a tx to quasarowner
await aliceTransferEth(quasarOwner, DEPOSIT_VALUE);
Expand Down Expand Up @@ -528,8 +533,12 @@ contract(
const utxoPos = this.depositUtxoPos;
const rlpChallengeTx = this.bobTransferTx;
const challengeTxInputIndex = 0;
const challengeTxInclusionProof = this.bobTransferTxMerkleProof;
const challengeTxPos = this.bobTransferTxUtxoPos;

this.inFlightTxRaw = web3.utils.bytesToHex(this.bobTransferTxObject.rlpEncoded());

const txHash = hashTx(this.bobTransferTxObject, this.framework.address);
const signature = sign(txHash, alicePrivateKey);

this.quasarMaintainerBalanceBeforeChallenge = new BN(
await web3.eth.getBalance(quasarMaintainer),
);
Expand All @@ -540,8 +549,7 @@ contract(
utxoPos,
rlpChallengeTx,
challengeTxInputIndex,
challengeTxInclusionProof,
challengeTxPos,
signature,
{
from: quasarMaintainer,
},
Expand Down

0 comments on commit 5e793c3

Please sign in to comment.