Skip to content

Commit

Permalink
Quasar Fast Exit: Add IFE Claims for Byzantine conditions (#724)
Browse files Browse the repository at this point in the history
* feat: add ife claims

* feat: separate pool contract

* feat: erc20 support

* feat: update to use safe block margin
  • Loading branch information
souradeep-das authored Jan 20, 2021
1 parent 34e693f commit 743e1e2
Show file tree
Hide file tree
Showing 4 changed files with 736 additions and 50 deletions.
106 changes: 75 additions & 31 deletions plasma_framework/contracts/poc/fast_exits/Quasar.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
pragma solidity 0.5.11;
pragma experimental ABIEncoderV2;

import "./QuasarPool.sol";
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/utils/SafeEthTransfer.sol";
import "../../src/transactions/PaymentTransactionModel.sol";
import "../../src/transactions/GenericTransaction.sol";
Expand All @@ -18,23 +22,30 @@ import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol";
* @title Quasar Contract
* Implementation Doc - https://github.com/omgnetwork/research-workshop/blob/master/Incognito_fast_withdrawals.md
*/
contract Quasar {
contract Quasar is QuasarPool {
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;
uint256 public safeBlockMargin;
uint256 public waitingPeriod;
uint256 constant public TICKET_VALIDITY_PERIOD = 14400;
uint256 constant internal SAFE_GAS_STIPEND = 2300;
uint256 constant public IFE_CLAIM_MARGIN = 28800;
// 7+1 days waiting period for IFE Claims
uint256 constant public IFE_CLAIM_WAITING_PERIOD = 691200;
uint256 public bondValue;
// bond is added to this reserve only when tickets are flushed, bond is returned every other time
uint256 private unclaimedBonds;
uint256 public unclaimedBonds;
bool public isPaused;

struct Ticket {
address payable outputOwner;
Expand All @@ -52,18 +63,22 @@ contract Quasar {
bool isValid;
}

mapping (address => uint256) public tokenUsableCapacity;
mapping (uint256 => Ticket) public ticketData;
mapping (uint256 => Claim) private claimData;

event QuasarTotalEthCapacityUpdated(uint256 balance);
event NewTicketObtained(uint256 utxoPos);
event IFEClaimSubmitted(uint256 utxoPos, uint168 exitId);

modifier onlyQuasarMaintainer() {
require(msg.sender == quasarMaintainer, "Only the Quasar Maintainer can invoke this method");
_;
}

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 @@ -81,6 +96,7 @@ contract Quasar {
uint256 _bondValue
) public {
plasmaFramework = PlasmaFramework(plasmaFrameworkContract);
paymentExitGame = PaymentExitGame(plasmaFramework.exitGames(1));
spendingConditionRegistry = SpendingConditionRegistry(spendingConditionRegistryContract);
quasarOwner = _quasarOwner;
quasarMaintainer = msg.sender;
Expand Down Expand Up @@ -128,32 +144,25 @@ contract Quasar {
}

/**
* @dev Add Eth Liquid funds to the quasar
* @dev Pause contract in a byzantine state
*/
function addEthCapacity() public payable onlyQuasarMaintainer() {
tokenUsableCapacity[address(0x0)] = tokenUsableCapacity[address(0x0)].add(msg.value);
emit QuasarTotalEthCapacityUpdated(tokenUsableCapacity[address(0x0)]);
function pauseQuasar() public onlyQuasarMaintainer() {
isPaused = true;
}

/**
* @dev Withdraw Unblocked Eth funds from the contract
* @param amount amount of Eth(in wei) to withdraw
* @dev Unpause contract and allow tickets
*/
function withdrawLiquidEthFunds(uint256 amount) public onlyQuasarMaintainer() {
address token = address(0x0);
uint256 withdrawableFunds = unclaimedBonds.add(tokenUsableCapacity[token]);
require(amount <= withdrawableFunds, "Amount should be lower than claimable funds");

// attempt to consume the unclaimed bonds first,
// and then withdraw the residual funds from the pool
if (amount <= unclaimedBonds) {
unclaimedBonds = unclaimedBonds.sub(amount);
} else {
uint256 residualAlmount = amount.sub(unclaimedBonds);
unclaimedBonds = 0;
tokenUsableCapacity[token] = tokenUsableCapacity[token].sub(residualAlmount);
emit QuasarTotalEthCapacityUpdated(tokenUsableCapacity[token]);
}
function resumeQuasar() public onlyQuasarMaintainer() {
isPaused = false;
}

/**
* @dev Withdraw Unclaimed bonds from the contract
*/
function withdrawUnclaimedBonds() public onlyQuasarMaintainer() {
uint256 amount = unclaimedBonds;
unclaimedBonds = 0;
SafeEthTransfer.transferRevertOnError(msg.sender, amount, SAFE_GAS_STIPEND);
}

Expand All @@ -167,7 +176,7 @@ contract Quasar {
* @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() {
require(msg.value == bondValue, "Bond Value incorrect");
require(!ticketData[utxoPos].isClaimed, "The UTXO has already been claimed");
require(ticketData[utxoPos].validityTimestamp == 0, "This UTXO already has a ticket");
Expand Down Expand Up @@ -230,20 +239,49 @@ contract Quasar {
claimData[utxoPos] = Claim(rlpTxToQuasarOwner, block.timestamp.add(waitingPeriod), true);
}

/**
* @dev Submit and IFEclaim for claims without inclusion proof
* @param utxoPos pos of the output, which is the ticket identifier
* @param inFlightClaimTx in-flight tx that spends the output 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");

// ifeClaims should start within IFE_CLAIM_MARGIN from starting IFE to enable sufficient time to piggyback
// this might be overriden by the ticket expiry check usually, except if the ticket is obtained later
require(block.timestamp <= ifeData[0].exitStartTimestamp.add(IFE_CLAIM_MARGIN), "IFE Claim period has passed");

ticketData[utxoPos].isClaimed = true;
claimData[utxoPos] = Claim(inFlightClaimTx, block.timestamp.add(IFE_CLAIM_WAITING_PERIOD), true);
emit IFEClaimSubmitted(utxoPos, exitId);
}

/**
* @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 challengeTxWitness Witness for challenging transaction
* @param senderData A keccak256 hash of the sender's address
*/
function challengeClaim(
uint256 utxoPos,
bytes memory rlpChallengeTx,
uint16 challengeTxInputIndex,
bytes memory challengeTxWitness
bytes memory challengeTxWitness,
bytes32 senderData
) public {
require(senderData == keccak256(abi.encodePacked(msg.sender)), "Incorrect SenderData");
require(ticketData[utxoPos].isClaimed && claimData[utxoPos].isValid, "The claim is not challengeable");
require(block.timestamp <= claimData[utxoPos].finalizationTimestamp, "The challenge period is over");
require(
Expand Down Expand Up @@ -272,9 +310,15 @@ contract Quasar {
require(block.timestamp > claimData[utxoPos].finalizationTimestamp, "The claim is not finalized yet");
require(claimData[utxoPos].isValid, "The claim has already been claimed or challenged");
address payable outputOwner = ticketData[utxoPos].outputOwner;
uint256 totalAmount = ticketData[utxoPos].reservedAmount.add(ticketData[utxoPos].bondValue);
claimData[utxoPos].isValid = false;
SafeEthTransfer.transferRevertOnError(outputOwner, totalAmount, SAFE_GAS_STIPEND);
address token = ticketData[utxoPos].token;
if (token == address(0)) {
uint256 totalAmount = ticketData[utxoPos].reservedAmount.add(ticketData[utxoPos].bondValue);
SafeEthTransfer.transferRevertOnError(outputOwner, totalAmount, SAFE_GAS_STIPEND);
} else {
IERC20(token).safeTransfer(outputOwner, ticketData[utxoPos].reservedAmount);
SafeEthTransfer.transferRevertOnError(outputOwner, ticketData[utxoPos].bondValue, SAFE_GAS_STIPEND);
}
}

////////////////////////////////////////////
Expand Down
65 changes: 65 additions & 0 deletions plasma_framework/contracts/poc/fast_exits/QuasarPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
pragma solidity 0.5.11;
pragma experimental ABIEncoderV2;

import "../../src/utils/SafeEthTransfer.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol";

contract QuasarPool {
using SafeMath for uint256;
using SafeERC20 for IERC20;

address public quasarMaintainer;
uint256 constant internal SAFE_GAS_STIPEND = 2300;

modifier onlyQuasarMaintainer() {
require(msg.sender == quasarMaintainer, "Only the Quasar Maintainer can invoke this method");
_;
}

mapping (address => uint256) public tokenUsableCapacity;
event QuasarTotalCapacityUpdated(address token, uint256 balance);

/**
* @dev Add Eth Liquid funds to the quasar
*/
function addEthCapacity() public payable {
address token = address(0);
tokenUsableCapacity[token] = tokenUsableCapacity[token].add(msg.value);
emit QuasarTotalCapacityUpdated(token, tokenUsableCapacity[token]);
}

/**
* @dev Add ERC20 Liquid funds to the quasar
*/
function addTokenCapacity(address token, uint256 amount) public {
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
tokenUsableCapacity[token] = tokenUsableCapacity[token].add(amount);
emit QuasarTotalCapacityUpdated(token, tokenUsableCapacity[token]);
}

/**
* @dev Withdraw Eth funds from the contract
* @param amount amount of Eth(in wei) to withdraw
*/
function withdrawEth(uint256 amount) public onlyQuasarMaintainer() {
address token = address(0);
require(amount <= tokenUsableCapacity[token], "Amount should be lower than claimable funds");
tokenUsableCapacity[token] = tokenUsableCapacity[token].sub(amount);
SafeEthTransfer.transferRevertOnError(msg.sender, amount, SAFE_GAS_STIPEND);
emit QuasarTotalCapacityUpdated(token, tokenUsableCapacity[token]);
}

/**
* @dev Withdraw Erc20 funds from the contract
* @param token the erc20 token
* @param amount amount of the token to withdraw
*/
function withdrawErc20(address token, uint256 amount) public onlyQuasarMaintainer() {
require(amount <= tokenUsableCapacity[token], "Amount should be lower than claimable funds");
tokenUsableCapacity[token] = tokenUsableCapacity[token].sub(amount);
IERC20(token).safeTransfer(msg.sender, amount);
emit QuasarTotalCapacityUpdated(token, tokenUsableCapacity[token]);
}
}
Loading

0 comments on commit 743e1e2

Please sign in to comment.