Skip to content

Instantly share code, notes, and snippets.

@syuhei176
Created April 27, 2019 07:53
Show Gist options
  • Save syuhei176/79c9c414a95d2da2f5a8b22bc843d1d1 to your computer and use it in GitHub Desktop.
Save syuhei176/79c9c414a95d2da2f5a8b22bc843d1d1 to your computer and use it in GitHub Desktop.
fast finality with predicate interface
# Plasma Fast Finality Contract with predicate interface
# see https://github.com/cryptoeconomicslab/plasma-chamber/wiki/Plasma-Fast-Finality for more description.
struct Merchant:
tokenAddress: address
amount: uint256
expiredAt: uint256
struct Dispute:
recipient: address
withdrawableAt: timestamp
tokenId: uint256
amount: uint256
status: uint256
stateHash: bytes32
contract ERC20:
def transferFrom(_from: address, _to: address, _value: uint256) -> bool: modifying
def transfer(_to: address, _value: uint256) -> bool: modifying
contract ERC721:
def setup(): modifying
def mint(_to: address, _tokenId: uint256): modifying
def ownerOf(_tokenId: uint256) -> address: constant
def burn(_tokenId: uint256): modifying
contract CommitmentChain():
def verifyInclusion(
_requestingSegment: uint256,
_txBytes: bytes[496],
_blkNum: uint256,
_proofs: bytes[2352]
) -> bytes[256]: constant
contract PredicateInterface():
def canInitiateExit(
_txHash: bytes32,
_stateUpdate: bytes[256],
_owner: address,
_segment: uint256
) -> bool: constant
def verifyDeprecation(
_txHash: bytes32,
_stateBytes: bytes[256],
_nextStateUpdate: bytes[256],
_transactionWitness: bytes[130]
) -> bool: constant
FFTokenMinted: event({_merchantId: uint256, _amount: uint256, _expiredAt: uint256})
FFTokenBurned: event({_merchantId: uint256})
BOND: constant(wei_value) = as_wei_value(1, "finney")
MASK8BYTES: constant(uint256) = 2**64 - 1
STATE_FIRST_DISPUTED: constant(uint256) = 1
STATE_CHALLENGED: constant(uint256) = 2
STATE_SECOND_DISPUTED: constant(uint256) = 3
STATE_FINALIZED: constant(uint256) = 4
ffToken: address
merchants: map(uint256, Merchant)
merchantNonce: uint256
operator: address
disputes: map(bytes32, Dispute)
commitmentChain: address
ownershipPredicate: address
@private
@constant
def parseSegment(
segment: uint256
) -> (uint256, uint256, uint256):
tokenId: uint256 = bitwise_and(shift(segment, -16 * 8), MASK8BYTES)
start: uint256 = bitwise_and(shift(segment, -8 * 8), MASK8BYTES)
end: uint256 = bitwise_and(segment, MASK8BYTES)
return (tokenId, start, end)
@private
def processDepositCollateral(
tokenAddress: address,
amount: uint256,
expireSpan: uint256
) -> uint256:
assert expireSpan > 0 and expireSpan < 50 * 7 * 24 * 60 * 60
expiredAt: uint256 = as_unitless_number(block.timestamp) + expireSpan
merchantId: uint256 = self.merchantNonce
self.merchantNonce += 1
self.merchants[merchantId] = Merchant({
tokenAddress: tokenAddress,
amount: amount,
expiredAt: expiredAt
})
ERC721(self.ffToken).mint(self.operator, merchantId)
log.FFTokenMinted(merchantId, amount, expiredAt)
return merchantId
# @dev Constructor
@public
def __init__(
_commitmentChain: address,
_ownershipPredicate: address,
_erc721: address
):
self.operator = msg.sender
self.commitmentChain = _commitmentChain
self.ownershipPredicate = _ownershipPredicate
self.merchantNonce = 0
self.ffToken = create_forwarder_to(_erc721)
ERC721(self.ffToken).setup()
@public
def getTokenAddress() -> address:
return self.ffToken
# @dev depositAndMintToken
# Operator deposit collateral and mint FF NFT.
@public
@payable
def depositAndMintToken(
expireSpan: uint256
) -> uint256:
assert msg.sender == self.operator
return self.processDepositCollateral(
ZERO_ADDRESS,
as_unitless_number(msg.value),
expireSpan
)
# @dev depositERC20AndMintToken
# Operator deposit ERC20 token as collateral and mint FF NFT.
@public
def depositERC20AndMintToken(
token: address,
amount: uint256,
expireSpan: uint256
) -> uint256:
assert msg.sender == self.operator
assert ERC20(token).transferFrom(self.operator, self, amount)
return self.processDepositCollateral(
token,
amount,
expireSpan
)
# @dev withdrawAndBurnToken
@public
def withdrawAndBurnToken(
_merchantId: uint256
):
merchant: Merchant = self.merchants[_merchantId]
assert merchant.amount > 0
assert merchant.expiredAt < as_unitless_number(block.timestamp)
if merchant.tokenAddress == ZERO_ADDRESS:
send(self.operator, as_wei_value(merchant.amount, 'wei'))
else:
ERC20(merchant.tokenAddress).transfer(self.operator, merchant.amount)
pass
ERC721(self.ffToken).burn(_merchantId)
clear(self.merchants[_merchantId])
log.FFTokenBurned(_merchantId)
# @dev dispute
# Merchant send "FF tx" with operator's signature
# Check operator's signature of inflight "FF tx" is valid
@public
@payable
def dispute(
_exitStateBytes: bytes[256],
_txBytes: bytes[256],
_sigs: bytes[130],
_operatorSigs: bytes[65],
_segment: uint256
):
assert msg.value == BOND
# check operator's signatures
txHash: bytes32 = sha3(_txBytes)
assert self.disputes[txHash].status == 0 and self.disputes[txHash].withdrawableAt == 0
assert self.operator == self.ecrecoverSig(txHash, _operatorSigs)
tokenId: uint256
start: uint256
end: uint256
(tokenId, start, end) = self.parseSegment(_segment)
assert PredicateInterface(self.txverifier).canInitiateExit(
txHash,
_txBytes,
msg.sender,
_segment)
self.disputes[txHash] = Dispute({
recipient: msg.sender,
withdrawableAt: block.timestamp + 1 * 7 * 24 * 60 * 60,
tokenId: tokenId,
amount: (end - start),
status: STATE_FIRST_DISPUTED,
stateHash: sha3(_exitStateBytes)
})
# @dev challenge
# Operator challenge showing inclusion of "FF tx"
@public
def challenge(
_txBytes: bytes[256],
_proof: bytes[2352],
_blkNum: uint256,
_segment: uint256,
):
txHash: bytes32 = sha3(_txBytes)
assert self.disputes[txHash].status == STATE_FIRST_DISPUTED
CommitmentChain(self.commitmentChain).verifyInclusion(
_segment,
_txBytes,
_blkNum,
_proof)
self.disputes[txHash].status = STATE_CHALLENGED
# @dev secondDispute
# Merchant show double spending of "FF tx"
@public
def secondDispute(
_stateBytes: bytes[256],
_disputeTxBytes: bytes[256],
_txBytes: bytes[256],
_proof: bytes[2352],
_sigs: bytes[130],
_blkNum: uint256,
_segment: uint256
):
txHash: bytes32 = sha3(_txBytes)
CommitmentChain(self.commitmentChain).checkTransaction(
_segment,
_txBytes,
_blkNum,
_proof)
disputeId: bytes32 = sha3(_disputeTxBytes)
assert self.disputes[disputeId].stateHash == sha3(_stateBytes)
assert PredicateInterface(self.txverifier).verifyDeprecation(
txHash,
_stateBytes,
_txBytes,
_sigs)
self.disputes[disputeId].status = STATE_SECOND_DISPUTED
# @dev finalizeDispute
# Withdraw the amount of "FF tx" from operator's collateral
@public
def finalizeDispute(
_merchantId: uint256,
_txHash: bytes32
):
# finalize dispute after a period
dispute: Dispute = self.disputes[_txHash]
assert dispute.withdrawableAt < block.timestamp
assert dispute.status == STATE_FIRST_DISPUTED or dispute.status == STATE_SECOND_DISPUTED
assert ERC721(self.ffToken).ownerOf(_merchantId) == msg.sender
amount: uint256
tokenAddress: address
decimalOffset: uint256
(tokenAddress, decimalOffset) = RootChain(self.rootchain).getTokenFromId(dispute.tokenId)
if dispute.tokenId == 0:
amount = dispute.amount * (10 ** 9)
assert self.merchants[_merchantId].amount >= amount
send(dispute.recipient, as_wei_value(amount, "wei") + BOND)
else:
amount = dispute.amount * decimalOffset
assert self.merchants[_merchantId].amount >= amount
ERC20(tokenAddress).transfer(dispute.recipient, amount)
send(dispute.recipient, BOND)
self.merchants[_merchantId].amount -= amount
self.disputes[_txHash].status = STATE_FINALIZED
# @dev getDispute
@public
@constant
def getDispute(
_txHash: bytes32
) -> (address, uint256, uint256, uint256):
dispute: Dispute = self.disputes[_txHash]
return (
dispute.recipient,
as_unitless_number(dispute.withdrawableAt),
dispute.amount,
dispute.status
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment