Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement minimal batch checkpoints and exits #29 #40

Open
wants to merge 2 commits into
base: master
from
Open
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -53,9 +53,10 @@ contract DepositERC20 is DepositStandard {

function finalizeExit(
types.Checkpoint memory _exit,
bytes memory _exitInclusionProof,
uint256 depositedRangeId
) public {
bytes32 checkpointId = getCheckpointId(_exit);
bytes32 checkpointId = calculateCheckpointRootWithProof(_exit, _exitInclusionProof);
// Check that we are authorized to finalize this exit
require(_exit.stateUpdate.stateObject.predicateAddress == msg.sender, "Exit must be finalized by its predicate");
require(checkpointFinalized(checkpointId), "Checkpoint must be finalized to finalize an exit");
@@ -56,4 +56,9 @@ contract DataTypes {
uint8 v;
}

struct BatchCheckpoint {
bytes32 rangeAndBlockNumber;
bytes32 hashOfStateObject;
}

}
@@ -3,7 +3,6 @@
* Created by Plasma Group https://github.com/plasma-group/pigi/blob/master/LICENSE.txt
* Modified by Cryptoeconomics Lab on Jul 03 2019
*/

pragma solidity >0.5.6;
pragma experimental ABIEncoderV2;

@@ -14,7 +13,6 @@ import "../CommitmentChain.sol";
// This contract is following pigi spec
// https://docs.plasma.group/en/latest/
contract DepositStandard {

/*
* Structs
*/
@@ -28,7 +26,7 @@ contract DepositStandard {
types.Checkpoint challengingCheckpoint;
}

/*** Events ***/
// Events
event CheckpointStarted(
types.Checkpoint checkpoint,
uint256 challengeableUntil
@@ -51,31 +49,26 @@ contract DepositStandard {
bytes32 exit
);

event CheckpointChallenged(
Challenge challenge
);

uint256 public totalDeposited;
mapping (uint256 => types.Range) public depositedRanges;
mapping (bytes32 => CheckpointStatus) public checkpoints;
mapping (bytes32 => uint256) public exitRedeemableAfter;
mapping (bytes32 => bool) public challenges;

mapping (bytes32 => bool) public challengeInclusions;
/*
* Public Constants
*/
CommitmentChain public commitmentChain;
uint256 public constant CHALLENGE_PERIOD = 10;
uint256 public constant EXIT_PERIOD = 20;

/**
* @dev Constructs a deposit contract
* @param _commitmentChain TODO
*/
constructor(address _commitmentChain) public {
commitmentChain = CommitmentChain(_commitmentChain);
}

/**
* Original code is https://github.com/plasma-group/pigi/blob/3fa77c71c0a198a8e410b75740e1a0406f9b723a/packages/contracts/contracts/Deposit.sol#L110.
*/
function extendDepositedRanges(uint256 _amount) public {
uint256 oldStart = depositedRanges[totalDeposited].start;
uint256 oldEnd = depositedRanges[totalDeposited].end;
@@ -97,8 +90,9 @@ contract DepositStandard {
totalDeposited += _amount;
}

// This function is called when an exit is finalized to "burn" it--so that checkpoints and exits
// on the range cannot be made. It is equivalent to the range having never been deposited.
/**
* @dev Precondition: range is subrange of depositedRanges[depositedRangeId]
*/
function removeDepositedRange(types.Range memory range, uint256 depositedRangeId) public {
types.Range memory encompasingRange = depositedRanges[depositedRangeId];

@@ -126,6 +120,55 @@ contract DepositStandard {
// 3) ##### -> $$$$$ -- without right-side leftovers & not the rightmost deposit, we can simply delete the value
delete depositedRanges[encompasingRange.end];
}

/**
* @dev Start Batch checkpointing procedure. Checkpoint aggregator sends checkpoints(range and block number) as call data.
* Now checkpointId is merkle root of checkpoints tree.
*/
function startBatchCheckpoints(
types.BatchCheckpoint[] memory _checkpoints
) public {
bytes32 checkpointRoot = calculateCheckpointRoot(_checkpoints);
require(!checkpointExists(checkpointRoot), "Checkpoint must not already exist");
checkpoints[checkpointRoot] = CheckpointStatus(block.number + CHALLENGE_PERIOD, 0);
}

/**
* @dev Anyone can challengeInclusion when any checkpoint in merkle tree isn't really exists.
* This act require challenge bond.(not implemented)
*/
function challengeInclusion(
types.Checkpoint memory _checkpoint,
bytes memory _checkpointInclusionProof
) public {
bytes32 checkpointId = getCheckpointId(_checkpoint);
bytes32 checkpointRoot = calculateCheckpointRootWithProof(_checkpoint, _checkpointInclusionProof);
require(checkpointExists(checkpointRoot), "Checkpoint must exist");
challengeInclusions[checkpointId] = true;
checkpoints[checkpointRoot].outstandingChallenges += 1;
}

/**
* @dev Checkpoint aggregator can respond challengeInclusion showing the inclusion of checkpoint,
* otherwize checkpoints will be canceled.
*/
function respondChallengeInclusion(
types.Checkpoint memory _checkpoint,
bytes memory _inclusionProof,
bytes memory _checkpointInclusionProof,
uint256 _depositedRangeId
) public {
bytes32 checkpointId = getCheckpointId(_checkpoint);
bytes32 checkpointRoot = calculateCheckpointRootWithProof(_checkpoint, _checkpointInclusionProof);
require(challengeInclusions[checkpointId], "challenge must exist");
require(commitmentChain.verifyInclusion(_checkpoint.stateUpdate, _inclusionProof), "Checkpoint must be included");
require(isSubrange(_checkpoint.subrange, _checkpoint.stateUpdate.range), "Checkpoint must be on a subrange of the StateUpdate");
require(isSubrange(_checkpoint.subrange, depositedRanges[_depositedRangeId]), "Checkpoint subrange must be on a depositedRange");
require(
exitExists(checkpointRoot) && _checkpoint.stateUpdate.stateObject.predicateAddress == msg.sender,
"Exit must be started by its predicate");
checkpoints[checkpointRoot].outstandingChallenges -= 1;
}

function startCheckpoint(
types.Checkpoint memory _checkpoint,
@@ -142,50 +185,78 @@ contract DepositStandard {
emit CheckpointStarted(_checkpoint, checkpoints[checkpointId].challengeableUntil);
}

function startExit(types.Checkpoint memory _checkpoint) public {
bytes32 checkpointId = getCheckpointId(_checkpoint);
/**
* @dev Start batch exit from batch checkpoint
*/
function startExit(
bytes32 _checkpointId
) public {
// Verify this exit may be started
require(checkpointExists(checkpointId), "Checkpoint must exist in order to begin exit");
require(exitRedeemableAfter[checkpointId] == 0, "There must not exist an exit on this checkpoint already");
require(_checkpoint.stateUpdate.stateObject.predicateAddress == msg.sender, "Exit must be started by its predicate");
exitRedeemableAfter[checkpointId] = block.number + EXIT_PERIOD;
emit ExitStarted(checkpointId, exitRedeemableAfter[checkpointId]);
require(checkpointExists(_checkpointId), "Checkpoint must exist in order to begin exit");
require(exitRedeemableAfter[_checkpointId] == 0, "There must not exist an exit on this checkpoint already");
exitRedeemableAfter[_checkpointId] = block.number + EXIT_PERIOD;
emit ExitStarted(_checkpointId, exitRedeemableAfter[_checkpointId]);
}

function deprecateExit(types.Checkpoint memory _exit) public {
bytes32 checkpointId = getCheckpointId(_exit);
/**
* @dev Predicate can deprecate one exit of batch exit. New checkpoint root should be caluculated.
*/
function deprecateExit(
types.Checkpoint memory _exit,
bytes memory _checkpointInclusionProof
) public {
bytes32 checkpointId = calculateCheckpointRootWithProof(_exit, _checkpointInclusionProof);
require(_exit.stateUpdate.stateObject.predicateAddress == msg.sender, "Exit must be deprecated by its predicate");
bytes32 newCheckpointId = calculateNewMerkleRoot(_checkpointInclusionProof);
checkpoints[newCheckpointId] = checkpoints[checkpointId];
exitRedeemableAfter[newCheckpointId] = exitRedeemableAfter[checkpointId];
delete exitRedeemableAfter[checkpointId];
}

function deleteOutdatedExit(types.Checkpoint memory _exit, types.Checkpoint memory _newerCheckpoint) public {
bytes32 outdatedExitId = getCheckpointId(_exit);
bytes32 newerCheckpointId = getCheckpointId(_newerCheckpoint);
function deleteOutdatedExit(
types.Checkpoint memory _exit,
bytes memory _exitInclusionProof,
types.Checkpoint memory _newerCheckpoint,
bytes memory _checkpointInclusionProof
) public {
bytes32 outdatedExitId = calculateCheckpointRootWithProof(_exit, _exitInclusionProof);
bytes32 newerCheckpointId = calculateCheckpointRootWithProof(_newerCheckpoint, _checkpointInclusionProof);
require(intersects(_exit.subrange, _newerCheckpoint.subrange), "Exit and newer checkpoint must overlap");
require(_exit.stateUpdate.plasmaBlockNumber < _newerCheckpoint.stateUpdate.plasmaBlockNumber, "Exit must be before a checkpoint");
require(checkpointFinalized(newerCheckpointId), "Newer checkpoint must be finalized to delete an earlier exit");
delete exitRedeemableAfter[outdatedExitId];
}

function challengeCheckpoint(Challenge memory _challenge) public {
bytes32 challengedCheckpointId = getCheckpointId(_challenge.challengedCheckpoint);
bytes32 challengingCheckpointId = getCheckpointId(_challenge.challengingCheckpoint);
/**
* @dev Anyone can challenge checkpoint. All checkpoints in one batch will be removed when challenge succeed.
*/
function challengeCheckpoint(
Challenge memory _challenge,
bytes memory _challengedCheckpointInclusionProof,
bytes memory _challengingCheckpointInclusionProof
) public {
bytes32 challengedCheckpointId = calculateCheckpointRootWithProof(_challenge.challengedCheckpoint, _challengedCheckpointInclusionProof);
bytes32 challengingCheckpointId = calculateCheckpointRootWithProof(_challenge.challengingCheckpoint, _challengingCheckpointInclusionProof);
bytes32 challengeId = getChallengeId(_challenge);
// Verify that the challenge may be added
require(exitExists(challengingCheckpointId), "Challenging exit must exist");
require(checkpointExists(challengedCheckpointId), "Challenged checkpoint must exist");
require(intersects(_challenge.challengedCheckpoint.subrange, _challenge.challengingCheckpoint.subrange), "Challenge ranges must intersect");
require(_challenge.challengingCheckpoint.stateUpdate.plasmaBlockNumber < _challenge.challengedCheckpoint.stateUpdate.plasmaBlockNumber, "Challenging cp after challenged cp");
require(!challenges[challengeId], "Challenge must not already exist");
require(checkpoints[challengedCheckpointId].challengeableUntil > block.number, "Checkpoint must still be challengable");
require(checkpoints[challengedCheckpointId].challengeableUntil > block.number, "Checkpoint must still be challengeable");
// Add the challenge
checkpoints[challengedCheckpointId].outstandingChallenges += 1;
challenges[challengeId] = true;
}

function removeChallenge(Challenge memory _challenge) public {
bytes32 challengedCheckpointId = getCheckpointId(_challenge.challengedCheckpoint);
bytes32 challengingCheckpointId = getCheckpointId(_challenge.challengingCheckpoint);
function removeChallenge(
Challenge memory _challenge,
bytes memory _challengedCheckpointInclusionProof,
bytes memory _challengingCheckpointInclusionProof
) public {
bytes32 challengedCheckpointId = calculateCheckpointRootWithProof(_challenge.challengedCheckpoint, _challengedCheckpointInclusionProof);
bytes32 challengingCheckpointId = calculateCheckpointRootWithProof(_challenge.challengingCheckpoint, _challengingCheckpointInclusionProof);
bytes32 challengeId = getChallengeId(_challenge);
// Verify that the challenge may be added
require(challenges[challengeId], "Challenge must exist");
@@ -196,17 +267,17 @@ contract DepositStandard {
}

/*
* Helpers
*/
* Helpers
*/
function getCheckpointId(types.Checkpoint memory _checkpoint) internal pure returns (bytes32) {
return keccak256(abi.encode(_checkpoint.stateUpdate, _checkpoint.subrange));
}

function getChallengeId(Challenge memory _challenge) internal pure returns (bytes32) {
function getChallengeId(Challenge memory _challenge) private pure returns (bytes32) {
return keccak256(abi.encode(_challenge));
}

function getLatestPlasmaBlockNumber() internal returns (uint256) {
function getLatestPlasmaBlockNumber() private returns (uint256) {
return 0;
}

@@ -225,11 +296,69 @@ contract DepositStandard {
function checkpointFinalized(bytes32 checkpointId) public view returns (bool) {
// To be considered finalized, a checkpoint:
// - MUST have no outstanding challenges
// - MUST no longer be challengable
return checkpoints[checkpointId].outstandingChallenges == 0 && checkpoints[checkpointId].challengeableUntil < block.number;
// - MUST no longer be challengeable
return checkpoints[checkpointId].outstandingChallenges == 0
&& checkpoints[checkpointId].challengeableUntil < block.number;
}

function exitExists(bytes32 checkpointId) public view returns (bool) {
return exitRedeemableAfter[checkpointId] != 0;
}

/**
* @dev Now supports 2 checkpoints
* One checkpoint has 8 bytes start, 8 bytes end, 16 bytes block number and 32 bytes hash of left data.
*/
function calculateCheckpointRoot(
types.BatchCheckpoint[] memory _batchCheckpoints
) public returns (bytes32) {
// TODO: calculate merkle root
return keccak256(abi.encodePacked(
keccak256(abi.encode(_batchCheckpoints[0])),
keccak256(abi.encode(_batchCheckpoints[1]))
));
}

function calculateCheckpointRootWithProof(
types.Checkpoint memory _checkpoint,
bytes memory _checkpointInclusionProof
) public returns (bytes32) {
return calculateMerkleRoot(
keccak256(abi.encode(_checkpoint)),
_checkpointInclusionProof);
}

/**
* @dev Removes checkpoint from merkle tree
*/
function calculateNewMerkleRoot(
bytes memory _checkpointInclusionProof
) public returns (bytes32) {
return calculateMerkleRoot(
keccak256(abi.encode(0)),
_checkpointInclusionProof);
}


/**
* from https://github.com/ameensol/merkle-tree-solidity/blob/master/src/MerkleProof.sol
*/
function calculateMerkleRoot(bytes32 hash, bytes memory proof) internal pure returns (bytes32) {
bytes32 el;
bytes32 h = hash;

for (uint256 i = 32; i <= proof.length; i += 32) {
assembly {
el := mload(add(proof, i))
}

if (h < el) {
h = keccak256(abi.encodePacked(h, el));
} else {
h = keccak256(abi.encodePacked(el, h));
}
}
return h;
}

}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.