Feature Tip: Add private address tag to any address under My Name Tag !
Overview
ETH Balance
0 ETH
Eth Value
$0.00Latest 5 from a total of 5 transactions
Latest 1 internal transaction
Advanced mode:
| Parent Transaction Hash | Method | Block |
From
|
|
To
|
||
|---|---|---|---|---|---|---|---|
| 0x6101ad3d | 23329320 | 202 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Minimal Proxy Contract for 0x09114973ae4bf3af3896e4e541082c73f224f8aa
Contract Name:
TopTournament
Compiler Version
v0.8.27+commit.40a35a09
Optimization Enabled:
Yes with 200 runs
Other Settings:
cancun EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
import {Clones} from "@openzeppelin-contracts-5.2.0/proxy/Clones.sol";
import "prt-contracts/tournament/abstracts/NonLeafTournament.sol";
import "prt-contracts/tournament/abstracts/RootTournament.sol";
import "prt-contracts/tournament/factories/IMultiLevelTournamentFactory.sol";
/// @notice Top tournament of a multi-level instance
contract TopTournament is NonLeafTournament, RootTournament {
using Clones for address;
struct Args {
TournamentArgs tournamentArgs;
IMultiLevelTournamentFactory tournamentFactory;
}
function _args() internal view returns (Args memory) {
return abi.decode(address(this).fetchCloneArgs(), (Args));
}
function _tournamentArgs()
internal
view
override
returns (TournamentArgs memory)
{
return _args().tournamentArgs;
}
function _tournamentFactory()
internal
view
override
returns (IMultiLevelTournamentFactory)
{
return _args().tournamentFactory;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/Clones.sol)
pragma solidity ^0.8.20;
import {Create2} from "../utils/Create2.sol";
import {Errors} from "../utils/Errors.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for
* deploying minimal proxy contracts, also known as "clones".
*
* > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
* > a minimal bytecode implementation that delegates all calls to a known, fixed address.
*
* The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
* (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
* deterministic method.
*/
library Clones {
error CloneArgumentsTooLong();
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create opcode, which should never revert.
*/
function clone(address implementation) internal returns (address instance) {
return clone(implementation, 0);
}
/**
* @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
* to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function clone(address implementation, uint256 value) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create(value, 0x09, 0x37)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `implementation` and `salt` multiple times will revert, since
* the clones cannot be deployed twice at the same address.
*/
function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
return cloneDeterministic(implementation, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
* a `value` parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministic(
address implementation,
bytes32 salt,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
assembly ("memory-safe") {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create2(value, 0x09, 0x37, salt)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(add(ptr, 0x38), deployer)
mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
mstore(add(ptr, 0x14), implementation)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
mstore(add(ptr, 0x58), salt)
mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
*/
function predictDeterministicAddress(
address implementation,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddress(implementation, salt, address(this));
}
/**
* @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create opcode, which should never revert.
*/
function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) {
return cloneWithImmutableArgs(implementation, args, 0);
}
/**
* @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value`
* parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneWithImmutableArgs(
address implementation,
bytes memory args,
uint256 value
) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
assembly ("memory-safe") {
instance := create(value, add(bytecode, 0x20), mload(bytecode))
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation` with custom
* immutable arguments. These are provided through `args` and cannot be changed after deployment. To
* access the arguments within the implementation, use {fetchCloneArgs}.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
* `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
* at the same address.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal returns (address instance) {
return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0);
}
/**
* @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs],
* but with a `value` parameter to send native currency to the new contract.
*
* NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
* to always have enough balance for new deployments. Consider exposing this function under a payable method.
*/
function cloneDeterministicWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
uint256 value
) internal returns (address instance) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.deploy(value, salt, bytecode);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt,
address deployer
) internal pure returns (address predicted) {
bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
return Create2.computeAddress(salt, keccak256(bytecode), deployer);
}
/**
* @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
*/
function predictDeterministicAddressWithImmutableArgs(
address implementation,
bytes memory args,
bytes32 salt
) internal view returns (address predicted) {
return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this));
}
/**
* @dev Get the immutable args attached to a clone.
*
* - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this
* function will return an empty array.
* - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or
* `cloneDeterministicWithImmutableArgs`, this function will return the args array used at
* creation.
* - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This
* function should only be used to check addresses that are known to be clones.
*/
function fetchCloneArgs(address instance) internal view returns (bytes memory) {
bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short
assembly ("memory-safe") {
extcodecopy(instance, add(result, 32), 45, mload(result))
}
return result;
}
/**
* @dev Helper that prepares the initcode of the proxy with immutable args.
*
* An assembly variant of this function requires copying the `args` array, which can be efficiently done using
* `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using
* abi.encodePacked is more expensive but also more portable and easier to review.
*
* NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes.
* With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes.
*/
function _cloneCodeWithImmutableArgs(
address implementation,
bytes memory args
) private pure returns (bytes memory) {
if (args.length > 24531) revert CloneArgumentsTooLong();
return
abi.encodePacked(
hex"61",
uint16(args.length + 45),
hex"3d81600a3d39f3363d3d373d3d3d363d73",
implementation,
hex"5af43d82803e903d91602b57fd5bf3",
args
);
}
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
import "prt-contracts/tournament/abstracts/Tournament.sol";
import "prt-contracts/tournament/abstracts/NonRootTournament.sol";
import "prt-contracts/tournament/factories/IMultiLevelTournamentFactory.sol";
/// @notice Non-leaf tournament can create inner tournaments and matches
abstract contract NonLeafTournament is Tournament {
using Clock for Clock.State;
using Commitment for Tree.Node;
using Machine for Machine.Hash;
using Tree for Tree.Node;
using Time for Time.Instant;
using Match for Match.State;
using Match for Match.Id;
using Match for Match.IdHash;
//
// Storage
//
mapping(NonRootTournament => Match.IdHash) matchIdFromInnerTournaments;
//
// Events
//
event newInnerTournament(Match.IdHash indexed, NonRootTournament);
function sealInnerMatchAndCreateInnerTournament(
Match.Id calldata _matchId,
Tree.Node _leftLeaf,
Tree.Node _rightLeaf,
Machine.Hash _agreeHash,
bytes32[] calldata _agreeHashProof
) external tournamentNotFinished {
Match.State storage _matchState = matches[_matchId.hashFromId()];
_matchState.requireCanBeFinalized();
// Pause clocks
Time.Duration _maxDuration;
{
Clock.State storage _clock1 = clocks[_matchId.commitmentOne];
Clock.State storage _clock2 = clocks[_matchId.commitmentTwo];
_clock1.setPaused();
_clock2.setPaused();
_maxDuration = Clock.max(_clock1, _clock2);
}
TournamentArgs memory args = _tournamentArgs();
(Machine.Hash _finalStateOne, Machine.Hash _finalStateTwo) = _matchState
.sealMatch(
_matchId,
args.initialHash,
_leftLeaf,
_rightLeaf,
_agreeHash,
_agreeHashProof
);
NonRootTournament _inner = instantiateInner(
_agreeHash,
_matchId.commitmentOne,
_finalStateOne,
_matchId.commitmentTwo,
_finalStateTwo,
_maxDuration,
_matchState.toCycle(args.startCycle),
args.level + 1
);
matchIdFromInnerTournaments[_inner] = _matchId.hashFromId();
emit newInnerTournament(_matchId.hashFromId(), _inner);
}
error ChildTournamentNotFinished();
error ChildTournamentCannotBeEliminated();
error ChildTournamentMustBeEliminated();
error WrongTournamentWinner(Tree.Node commitmentRoot, Tree.Node winner);
function winInnerTournament(
NonRootTournament _childTournament,
Tree.Node _leftNode,
Tree.Node _rightNode
) external tournamentNotFinished {
Match.IdHash _matchIdHash =
matchIdFromInnerTournaments[_childTournament];
_matchIdHash.requireExist();
Match.State storage _matchState = matches[_matchIdHash];
_matchState.requireExist();
_matchState.requireIsFinished();
require(
!_childTournament.canBeEliminated(),
ChildTournamentMustBeEliminated()
);
(bool finished, Tree.Node _winner,, Clock.State memory _innerClock) =
_childTournament.innerTournamentWinner();
require(finished, ChildTournamentNotFinished());
_winner.requireExist();
Tree.Node _commitmentRoot = _leftNode.join(_rightNode);
require(
_commitmentRoot.eq(_winner),
WrongTournamentWinner(_commitmentRoot, _winner)
);
Clock.State storage _clock = clocks[_commitmentRoot];
_clock.requireInitialized();
_clock.reInitialized(_innerClock);
pairCommitment(_commitmentRoot, _clock, _leftNode, _rightNode);
// delete storage
deleteMatch(_matchIdHash);
matchIdFromInnerTournaments[_childTournament] = Match.ZERO_ID;
}
function eliminateInnerTournament(NonRootTournament _childTournament)
external
tournamentNotFinished
{
Match.IdHash _matchIdHash =
matchIdFromInnerTournaments[_childTournament];
_matchIdHash.requireExist();
Match.State storage _matchState = matches[_matchIdHash];
_matchState.requireExist();
_matchState.requireIsFinished();
require(
_childTournament.canBeEliminated(),
ChildTournamentCannotBeEliminated()
);
// delete storage
deleteMatch(_matchIdHash);
matchIdFromInnerTournaments[_childTournament] = Match.ZERO_ID;
}
function instantiateInner(
Machine.Hash _initialHash,
Tree.Node _contestedCommitmentOne,
Machine.Hash _contestedFinalStateOne,
Tree.Node _contestedCommitmentTwo,
Machine.Hash _contestedFinalStateTwo,
Time.Duration _allowance,
uint256 _startCycle,
uint64 _level
) private returns (NonRootTournament) {
// the inner tournament is bottom tournament at last level
// else instantiate middle tournament
TournamentArgs memory args = _tournamentArgs();
Tournament _tournament;
IMultiLevelTournamentFactory tournamentFactory = _tournamentFactory();
if (_level == args.levels - 1) {
_tournament = tournamentFactory.instantiateBottom(
_initialHash,
_contestedCommitmentOne,
_contestedFinalStateOne,
_contestedCommitmentTwo,
_contestedFinalStateTwo,
_allowance,
_startCycle,
_level,
args.provider
);
} else {
_tournament = tournamentFactory.instantiateMiddle(
_initialHash,
_contestedCommitmentOne,
_contestedFinalStateOne,
_contestedCommitmentTwo,
_contestedFinalStateTwo,
_allowance,
_startCycle,
_level,
args.provider
);
}
return NonRootTournament(address(_tournament));
}
function _tournamentFactory()
internal
view
virtual
returns (IMultiLevelTournamentFactory);
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
import "prt-contracts/ITournament.sol";
import "prt-contracts/tournament/abstracts/Tournament.sol";
import "prt-contracts/types/TournamentParameters.sol";
/// @notice Root tournament has no parent
abstract contract RootTournament is Tournament, ITournament {
function validContestedFinalState(Machine.Hash)
internal
pure
override
returns (bool, Machine.Hash, Machine.Hash)
{
// always returns true in root tournament
return (true, Machine.ZERO_STATE, Machine.ZERO_STATE);
}
function arbitrationResult()
external
view
override
returns (bool, Tree.Node, Machine.Hash)
{
if (!isFinished()) {
return (false, Tree.ZERO_NODE, Machine.ZERO_STATE);
}
(bool _hasDanglingCommitment, Tree.Node _danglingCommitment) =
hasDanglingCommitment();
assert(_hasDanglingCommitment);
Machine.Hash _finalState = finalStates[_danglingCommitment];
return (true, _danglingCommitment, _finalState);
}
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
import "prt-contracts/ITournamentFactory.sol";
interface IMultiLevelTournamentFactory is ITournamentFactory {
function instantiateTop(Machine.Hash _initialHash, IDataProvider _provider)
external
returns (Tournament);
function instantiateMiddle(
Machine.Hash _initialHash,
Tree.Node _contestedCommitmentOne,
Machine.Hash _contestedFinalStateOne,
Tree.Node _contestedCommitmentTwo,
Machine.Hash _contestedFinalStateTwo,
Time.Duration _allowance,
uint256 _startCycle,
uint64 _level,
IDataProvider _provider
) external returns (Tournament);
function instantiateBottom(
Machine.Hash _initialHash,
Tree.Node _contestedCommitmentOne,
Machine.Hash _contestedFinalStateOne,
Tree.Node _contestedCommitmentTwo,
Machine.Hash _contestedFinalStateTwo,
Time.Duration _allowance,
uint256 _startCycle,
uint64 _level,
IDataProvider _provider
) external returns (Tournament);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol)
pragma solidity ^0.8.20;
import {Errors} from "./Errors.sol";
/**
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
* `CREATE2` can be used to compute in advance the address where a smart
* contract will be deployed, which allows for interesting new mechanisms known
* as 'counterfactual interactions'.
*
* See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
* information.
*/
library Create2 {
/**
* @dev There's no code to deploy.
*/
error Create2EmptyBytecode();
/**
* @dev Deploys a contract using `CREATE2`. The address where the contract
* will be deployed can be known in advance via {computeAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `amount`.
* - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
*/
function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
if (address(this).balance < amount) {
revert Errors.InsufficientBalance(address(this).balance, amount);
}
if (bytecode.length == 0) {
revert Create2EmptyBytecode();
}
assembly ("memory-safe") {
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
// if no address was created, and returndata is not empty, bubble revert
if and(iszero(addr), not(iszero(returndatasize()))) {
let p := mload(0x40)
returndatacopy(p, 0, returndatasize())
revert(p, returndatasize())
}
}
if (addr == address(0)) {
revert Errors.FailedDeployment();
}
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
* `bytecodeHash` or `salt` will result in a new destination address.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
return computeAddress(salt, bytecodeHash, address(this));
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
* `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
assembly ("memory-safe") {
let ptr := mload(0x40) // Get free memory pointer
// | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... |
// |-------------------|---------------------------------------------------------------------------|
// | bytecodeHash | CCCCCCCCCCCCC...CC |
// | salt | BBBBBBBBBBBBB...BB |
// | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA |
// | 0xFF | FF |
// |-------------------|---------------------------------------------------------------------------|
// | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
// | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |
mstore(add(ptr, 0x40), bytecodeHash)
mstore(add(ptr, 0x20), salt)
mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
mstore8(start, 0xff)
addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of common custom errors used in multiple contracts
*
* IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
* It is recommended to avoid relying on the error API for critical functionality.
*
* _Available since v5.1._
*/
library Errors {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedCall();
/**
* @dev The deployment failed.
*/
error FailedDeployment();
/**
* @dev A necessary precompile is missing.
*/
error MissingPrecompile(address);
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
import "prt-contracts/arbitration-config/ArbitrationConstants.sol";
import "prt-contracts/IDataProvider.sol";
import "prt-contracts/types/TournamentParameters.sol";
import "prt-contracts/types/Machine.sol";
import "prt-contracts/types/Tree.sol";
import "prt-contracts/tournament/libs/Commitment.sol";
import "prt-contracts/tournament/libs/Time.sol";
import "prt-contracts/tournament/libs/Clock.sol";
import "prt-contracts/tournament/libs/Match.sol";
struct TournamentArgs {
Machine.Hash initialHash;
uint256 startCycle;
uint64 level;
uint64 levels;
uint64 log2step;
uint64 height;
Time.Instant startInstant;
Time.Duration allowance;
Time.Duration maxAllowance;
Time.Duration matchEffort;
IDataProvider provider;
}
/// @notice Implements the core functionalities of a permissionless tournament that resolves
/// disputes of n parties in O(log(n))
/// @dev tournaments and matches are nested alternately. Anyone can join a tournament
/// while the tournament is still open, and two of the participants with unique commitments
/// will form a match. A match located in the last level is called `leafMatch`,
/// meaning the one-step disagreement is found and can be resolved by solidity-step.
/// Non-leaf (inner) matches would normally create inner tournaments with height = height + 1,
/// to find the divergence with improved precision.
abstract contract Tournament {
using Machine for Machine.Hash;
using Tree for Tree.Node;
using Commitment for Tree.Node;
using Time for Time.Instant;
using Time for Time.Duration;
using Clock for Clock.State;
using Match for Match.Id;
using Match for Match.IdHash;
using Match for Match.State;
//
// Storage
//
Tree.Node danglingCommitment;
uint256 matchCount;
Time.Instant lastMatchDeleted;
mapping(Tree.Node => Clock.State) clocks;
mapping(Tree.Node => Machine.Hash) finalStates;
// matches existing in current tournament
mapping(Match.IdHash => Match.State) matches;
//
// Events
//
event matchCreated(
Tree.Node indexed one, Tree.Node indexed two, Tree.Node leftOfTwo
);
event matchDeleted(Match.IdHash);
event commitmentJoined(Tree.Node root);
//
// Modifiers
//
error TournamentIsFinished();
error TournamentIsClosed();
modifier tournamentNotFinished() {
require(!isFinished(), TournamentIsFinished());
_;
}
modifier tournamentOpen() {
require(!isClosed(), TournamentIsClosed());
_;
}
//
// Virtual Methods
//
/// @return bool if commitment with _finalState is allowed to join the tournament
function validContestedFinalState(Machine.Hash _finalState)
internal
view
virtual
returns (bool, Machine.Hash, Machine.Hash);
//
// Methods
//
/// @dev root tournaments are open to everyone,
/// while non-root tournaments are open to anyone
/// who's final state hash matches the one of the two in the tournament
function joinTournament(
Machine.Hash _finalState,
bytes32[] calldata _proof,
Tree.Node _leftNode,
Tree.Node _rightNode
) external tournamentOpen {
Tree.Node _commitmentRoot = _leftNode.join(_rightNode);
TournamentArgs memory args = _tournamentArgs();
// Prove final state is in commitmentRoot
_commitmentRoot.requireFinalState(args.height, _finalState, _proof);
// Verify whether finalState is one of the two allowed of tournament if nested
requireValidContestedFinalState(_finalState);
finalStates[_commitmentRoot] = _finalState;
Clock.State storage _clock = clocks[_commitmentRoot];
_clock.requireNotInitialized(); // reverts if commitment is duplicate
_clock.setNewPaused(args.startInstant, args.allowance);
pairCommitment(_commitmentRoot, _clock, _leftNode, _rightNode);
emit commitmentJoined(_commitmentRoot);
}
/// @notice Advance the match until the smallest divergence is found at current level
/// @dev this function is being called repeatedly in turns by the two parties that disagree on the commitment.
function advanceMatch(
Match.Id calldata _matchId,
Tree.Node _leftNode,
Tree.Node _rightNode,
Tree.Node _newLeftNode,
Tree.Node _newRightNode
) external tournamentNotFinished {
Match.State storage _matchState = matches[_matchId.hashFromId()];
_matchState.requireExist();
_matchState.requireCanBeAdvanced();
_matchState.advanceMatch(
_matchId, _leftNode, _rightNode, _newLeftNode, _newRightNode
);
// advance clocks
clocks[_matchId.commitmentOne].advanceClock();
clocks[_matchId.commitmentTwo].advanceClock();
}
error WrongChildren(
uint256 commitment, Tree.Node parent, Tree.Node left, Tree.Node right
);
error WinByTimeout();
function winMatchByTimeout(
Match.Id calldata _matchId,
Tree.Node _leftNode,
Tree.Node _rightNode
) external tournamentNotFinished {
matches[_matchId.hashFromId()].requireExist();
Clock.State storage _clockOne = clocks[_matchId.commitmentOne];
Clock.State storage _clockTwo = clocks[_matchId.commitmentTwo];
_clockOne.requireInitialized();
_clockTwo.requireInitialized();
if (_clockOne.hasTimeLeft() && !_clockTwo.hasTimeLeft()) {
require(
_matchId.commitmentOne.verify(_leftNode, _rightNode),
WrongChildren(1, _matchId.commitmentOne, _leftNode, _rightNode)
);
_clockOne.deducted(_clockTwo.timeSinceTimeout());
pairCommitment(
_matchId.commitmentOne, _clockOne, _leftNode, _rightNode
);
} else if (!_clockOne.hasTimeLeft() && _clockTwo.hasTimeLeft()) {
require(
_matchId.commitmentTwo.verify(_leftNode, _rightNode),
WrongChildren(2, _matchId.commitmentTwo, _leftNode, _rightNode)
);
_clockTwo.deducted(_clockOne.timeSinceTimeout());
pairCommitment(
_matchId.commitmentTwo, _clockTwo, _leftNode, _rightNode
);
} else {
revert WinByTimeout();
}
// delete storage
deleteMatch(_matchId.hashFromId());
}
error EliminateByTimeout();
function eliminateMatchByTimeout(Match.Id calldata _matchId)
external
tournamentNotFinished
{
matches[_matchId.hashFromId()].requireExist();
Clock.State storage _clockOne = clocks[_matchId.commitmentOne];
Clock.State storage _clockTwo = clocks[_matchId.commitmentTwo];
_clockOne.requireInitialized();
_clockTwo.requireInitialized();
// check if both clocks are out of time
if (
(
!_clockOne.hasTimeLeft()
&& !_clockTwo.timeLeft().gt(_clockOne.timeSinceTimeout())
)
|| (
!_clockTwo.hasTimeLeft()
&& !_clockOne.timeLeft().gt(_clockTwo.timeSinceTimeout())
)
) {
// delete storage
deleteMatch(_matchId.hashFromId());
} else {
revert EliminateByTimeout();
}
}
//
// View methods
//
function canWinMatchByTimeout(Match.Id calldata _matchId)
external
view
returns (bool)
{
Clock.State memory _clockOne = clocks[_matchId.commitmentOne];
Clock.State memory _clockTwo = clocks[_matchId.commitmentTwo];
return !_clockOne.hasTimeLeft() || !_clockTwo.hasTimeLeft();
}
function getCommitment(Tree.Node _commitmentRoot)
public
view
returns (Clock.State memory, Machine.Hash)
{
return (clocks[_commitmentRoot], finalStates[_commitmentRoot]);
}
function getMatch(Match.IdHash _matchIdHash)
public
view
returns (Match.State memory)
{
return matches[_matchIdHash];
}
function getMatchCycle(Match.IdHash _matchIdHash)
external
view
returns (uint256)
{
Match.State memory _m = getMatch(_matchIdHash);
uint256 startCycle = _tournamentArgs().startCycle;
return _m.toCycle(startCycle);
}
function tournamentLevelConstants()
external
view
returns (
uint64 _maxLevel,
uint64 _level,
uint64 _log2step,
uint64 _height
)
{
TournamentArgs memory args;
args = _tournamentArgs();
_maxLevel = args.levels;
_level = args.level;
_log2step = args.log2step;
_height = args.height;
}
//
// Helper functions
//
error InvalidContestedFinalState(
Machine.Hash contestedFinalStateOne,
Machine.Hash contestedFinalStateTwo,
Machine.Hash finalState
);
function requireValidContestedFinalState(Machine.Hash _finalState)
internal
view
{
(
bool valid,
Machine.Hash contestedFinalStateOne,
Machine.Hash contestedFinalStateTwo
) = validContestedFinalState(_finalState);
require(
valid,
InvalidContestedFinalState(
contestedFinalStateOne, contestedFinalStateTwo, _finalState
)
);
}
function hasDanglingCommitment()
internal
view
returns (bool _h, Tree.Node _node)
{
_node = danglingCommitment;
if (!_node.isZero()) {
_h = true;
}
}
function setDanglingCommitment(Tree.Node _node) internal {
danglingCommitment = _node;
}
function clearDanglingCommitment() internal {
danglingCommitment = Tree.ZERO_NODE;
}
function pairCommitment(
Tree.Node _rootHash,
Clock.State storage _newClock,
Tree.Node _leftNode,
Tree.Node _rightNode
) internal {
assert(_leftNode.join(_rightNode).eq(_rootHash));
(bool _hasDanglingCommitment, Tree.Node _danglingCommitment) =
hasDanglingCommitment();
if (_hasDanglingCommitment) {
TournamentArgs memory args = _tournamentArgs();
(Match.IdHash _matchId, Match.State memory _matchState) = Match
.createMatch(
_danglingCommitment,
_rootHash,
_leftNode,
_rightNode,
args.log2step,
args.height
);
matches[_matchId] = _matchState;
Clock.State storage _firstClock = clocks[_danglingCommitment];
// grant extra match effort for both clocks
_firstClock.addMatchEffort(args.matchEffort, args.maxAllowance);
_newClock.addMatchEffort(args.matchEffort, args.maxAllowance);
_firstClock.advanceClock();
clearDanglingCommitment();
matchCount++;
emit matchCreated(_danglingCommitment, _rootHash, _leftNode);
} else {
setDanglingCommitment(_rootHash);
}
}
function deleteMatch(Match.IdHash _matchIdHash) internal {
matchCount--;
lastMatchDeleted = Time.currentTime();
delete matches[_matchIdHash];
emit matchDeleted(_matchIdHash);
}
//
// Clock methods
//
/// @return bool if the tournament is still open to join
function isClosed() public view returns (bool) {
Time.Instant startInstant;
Time.Duration allowance;
{
TournamentArgs memory args;
args = _tournamentArgs();
startInstant = args.startInstant;
allowance = args.allowance;
}
return startInstant.timeoutElapsed(allowance);
}
/// @return bool if the tournament is over
function isFinished() public view returns (bool) {
return isClosed() && matchCount == 0;
}
/// @notice returns if and when tournament was finished.
/// @return (bool, Time.Instant)
/// - if the tournament can be eliminated
/// - the time when the tournament was finished
function timeFinished() public view returns (bool, Time.Instant) {
if (!isFinished()) {
return (false, Time.ZERO_INSTANT);
}
TournamentArgs memory args = _tournamentArgs();
// Here, we know that `lastMatchDeleted` holds the Instant when `matchCount` became zero.
// However, we still must consider when the tournament was closed, in case it
// happens after `lastMatchDeleted`.
// Note that `lastMatchDeleted` could be zero if there are no matches eliminated.
// In this case, we'd only care about `tournamentClosed`.
Time.Instant tournamentClosed = args.startInstant.add(args.allowance);
Time.Instant winnerCouldWin = tournamentClosed.max(lastMatchDeleted);
return (true, winnerCouldWin);
}
//
// Internal functions
//
function _tournamentArgs()
internal
view
virtual
returns (TournamentArgs memory);
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
import "prt-contracts/tournament/abstracts/Tournament.sol";
import "prt-contracts/types/TournamentParameters.sol";
struct NonRootTournamentArgs {
Tree.Node contestedCommitmentOne;
Machine.Hash contestedFinalStateOne;
Tree.Node contestedCommitmentTwo;
Machine.Hash contestedFinalStateTwo;
}
/// @notice Non-root tournament needs to propagate side-effects to its parent
abstract contract NonRootTournament is Tournament {
using Machine for Machine.Hash;
using Tree for Tree.Node;
using Time for Time.Instant;
using Time for Time.Duration;
using Clock for Clock.State;
/// @notice get the dangling commitment at current level and then retrieve the winner commitment
/// @return (bool, Tree.Node, Tree.Node)
/// - if the tournament is finished
/// - the contested parent commitment
/// - the winning inner commitment
/// - the paused clock of the winning inner commitment
function innerTournamentWinner()
external
view
returns (bool, Tree.Node, Tree.Node, Clock.State memory)
{
if (!isFinished() || canBeEliminated()) {
Clock.State memory zeroClock;
return (false, Tree.ZERO_NODE, Tree.ZERO_NODE, zeroClock);
}
(bool _hasDanglingCommitment, Tree.Node _winner) =
hasDanglingCommitment();
assert(_hasDanglingCommitment);
(bool finished, Time.Instant timeFinished) = timeFinished();
assert(finished);
Clock.State memory _clock = clocks[_winner];
_clock = _clock.deduct(Time.currentTime().timeSpan(timeFinished));
NonRootTournamentArgs memory args = _nonRootTournamentArgs();
Machine.Hash _finalState = finalStates[_winner];
if (_finalState.eq(args.contestedFinalStateOne)) {
return (true, args.contestedCommitmentOne, _winner, _clock);
} else {
assert(_finalState.eq(args.contestedFinalStateTwo));
return (true, args.contestedCommitmentTwo, _winner, _clock);
}
}
/// @notice returns whether this inner tournament can be safely eliminated.
/// @return (bool)
/// - if the tournament can be eliminated
function canBeEliminated() public view returns (bool) {
(bool finished, Time.Instant winnerCouldHaveWon) = timeFinished();
if (!finished) {
return false;
}
(bool _hasDanglingCommitment, Tree.Node _danglingCommitment) =
hasDanglingCommitment();
// If the tournament is finished but has no winners,
// inner tournament can be eliminated
if (!_hasDanglingCommitment) {
return true;
}
// We know that, after `winnerCouldHaveWon` plus winner's clock.allowance has elapsed,
// it is safe to elminate the tournament.
(Clock.State memory clock,) = getCommitment(_danglingCommitment);
return winnerCouldHaveWon.timeoutElapsed(clock.allowance);
}
/// @notice a final state is valid if it's equal to ContestedFinalStateOne or ContestedFinalStateTwo
function validContestedFinalState(Machine.Hash _finalState)
internal
view
override
returns (bool, Machine.Hash, Machine.Hash)
{
NonRootTournamentArgs memory args = _nonRootTournamentArgs();
return (
args.contestedFinalStateOne.eq(_finalState)
|| args.contestedFinalStateTwo.eq(_finalState),
args.contestedFinalStateOne,
args.contestedFinalStateTwo
);
}
function _nonRootTournamentArgs()
internal
view
virtual
returns (NonRootTournamentArgs memory);
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
import "prt-contracts/types/Machine.sol";
import "prt-contracts/types/Tree.sol";
interface ITournament {
function arbitrationResult()
external
view
returns (
bool finished,
Tree.Node winnerCommitment,
Machine.Hash finalState
);
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
import "prt-contracts/tournament/libs/Time.sol";
struct TournamentParameters {
uint64 levels;
uint64 log2step;
uint64 height;
Time.Duration matchEffort;
Time.Duration maxAllowance;
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
import "prt-contracts/ITournament.sol";
import "prt-contracts/tournament/abstracts/Tournament.sol";
import "prt-contracts/IDataProvider.sol";
interface ITournamentFactory {
event tournamentCreated(ITournament tournament);
function instantiate(Machine.Hash initialState, IDataProvider provider)
external
returns (ITournament);
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
import "prt-contracts/tournament/libs/Time.sol";
library ArbitrationConstants {
// 3-level tournament
uint64 constant LEVELS = 3;
/// @return log2step gap of each leaf in the tournament[level]
function log2step(uint64 level) internal pure returns (uint64) {
uint64[LEVELS] memory arr = [uint64(44), uint64(27), uint64(0)];
return arr[level];
}
/// @return height of the tournament[level] tree which is calculated by subtracting the log2step[level] from the log2step[level - 1]
function height(uint64 level) internal pure returns (uint64) {
uint64[LEVELS] memory arr = [uint64(48), uint64(17), uint64(27)];
return arr[level];
}
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
interface IDataProvider {
/// @notice Provides the Merkle root of an input
/// @param inputIndexWithinEpoch The index of the input within the epoch
/// @param input The input blob (to hash and check against the input box)
/// @return The root of smallest Merkle tree that fits the input
function provideMerkleRootOfInput(
uint256 inputIndexWithinEpoch,
bytes calldata input
) external view returns (bytes32);
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
library Machine {
type Hash is bytes32;
Hash constant ZERO_STATE = Hash.wrap(0x0);
function notInitialized(Hash hash) internal pure returns (bool) {
bytes32 h = Hash.unwrap(hash);
return h == 0x0;
}
function eq(Hash left, Hash right) internal pure returns (bool) {
bytes32 l = Hash.unwrap(left);
bytes32 r = Hash.unwrap(right);
return l == r;
}
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
import "prt-contracts/types/Machine.sol";
library Tree {
using Tree for Node;
type Node is bytes32;
Node constant ZERO_NODE = Node.wrap(bytes32(0x0));
function eq(Node left, Node right) internal pure returns (bool) {
bytes32 l = Node.unwrap(left);
bytes32 r = Node.unwrap(right);
return l == r;
}
function join(Node left, Node right) internal pure returns (Node) {
bytes32 l = Node.unwrap(left);
bytes32 r = Node.unwrap(right);
bytes32 p = keccak256(abi.encode(l, r));
return Node.wrap(p);
}
function verify(Node parent, Node left, Node right)
internal
pure
returns (bool)
{
return parent.eq(left.join(right));
}
function requireChildren(Node parent, Node left, Node right)
internal
pure
{
require(parent.verify(left, right), "child nodes don't match parent");
}
function isZero(Node node) internal pure returns (bool) {
bytes32 n = Node.unwrap(node);
return n == 0x0;
}
function requireExist(Node node) internal pure {
require(!node.isZero(), "tree node doesn't exist");
}
function toMachineHash(Node node) internal pure returns (Machine.Hash) {
return Machine.Hash.wrap(Node.unwrap(node));
}
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
import "prt-contracts/arbitration-config/ArbitrationConstants.sol";
import "prt-contracts/types/Tree.sol";
import "prt-contracts/types/Machine.sol";
library Commitment {
using Tree for Tree.Node;
using Commitment for Tree.Node;
error CommitmentMismatch(Tree.Node received, Tree.Node expected);
function requireState(
Tree.Node commitment,
uint64 treeHeight,
uint256 position,
Machine.Hash state,
bytes32[] calldata hashProof
) internal pure {
Tree.Node expectedCommitment =
getRoot(Machine.Hash.unwrap(state), treeHeight, position, hashProof);
require(
commitment.eq(expectedCommitment),
CommitmentMismatch(commitment, expectedCommitment)
);
}
function isEven(uint256 x) private pure returns (bool) {
return x % 2 == 0;
}
error LengthMismatch(uint64 treeHeight, uint64 siblingsLength);
function getRoot(
bytes32 leaf,
uint64 treeHeight,
uint256 position,
bytes32[] calldata siblings
) internal pure returns (Tree.Node) {
uint64 siblingsLength = uint64(siblings.length);
require(
treeHeight == siblingsLength,
LengthMismatch(treeHeight, siblingsLength)
);
for (uint256 i = 0; i < treeHeight; i++) {
if (isEven(position >> i)) {
leaf = keccak256(abi.encodePacked(leaf, siblings[i]));
} else {
leaf = keccak256(abi.encodePacked(siblings[i], leaf));
}
}
return Tree.Node.wrap(leaf);
}
function requireFinalState(
Tree.Node commitment,
uint64 treeHeight,
Machine.Hash finalState,
bytes32[] calldata hashProof
) internal pure {
Tree.Node expectedCommitment = getRootForLastLeaf(
treeHeight, Machine.Hash.unwrap(finalState), hashProof
);
require(
commitment.eq(expectedCommitment),
"commitment last state doesn't match"
);
}
function getRootForLastLeaf(
uint64 treeHeight,
bytes32 leaf,
bytes32[] calldata siblings
) internal pure returns (Tree.Node) {
assert(treeHeight == siblings.length);
for (uint256 i = 0; i < treeHeight; i++) {
leaf = keccak256(abi.encodePacked(siblings[i], leaf));
}
return Tree.Node.wrap(leaf);
}
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
library Time {
type Instant is uint64;
type Duration is uint64;
using Time for Instant;
using Time for Duration;
Instant constant ZERO_INSTANT = Instant.wrap(0);
Duration constant ZERO_DURATION = Duration.wrap(0);
function currentTime() internal view returns (Instant) {
return Instant.wrap(uint64(block.number));
}
function add(Instant timestamp, Duration duration)
internal
pure
returns (Instant)
{
uint64 t = Instant.unwrap(timestamp);
uint64 d = Duration.unwrap(duration);
return Instant.wrap(t + d);
}
function gt(Instant left, Instant right) internal pure returns (bool) {
uint64 l = Instant.unwrap(left);
uint64 r = Instant.unwrap(right);
return l > r;
}
function gt(Duration left, Duration right) internal pure returns (bool) {
uint64 l = Duration.unwrap(left);
uint64 r = Duration.unwrap(right);
return l > r;
}
function isZero(Instant timestamp) internal pure returns (bool) {
uint64 t = Instant.unwrap(timestamp);
return t == 0;
}
function isZero(Duration duration) internal pure returns (bool) {
uint64 d = Duration.unwrap(duration);
return d == 0;
}
function add(Duration left, Duration right)
internal
pure
returns (Duration)
{
uint64 l = Duration.unwrap(left);
uint64 r = Duration.unwrap(right);
return Duration.wrap(l + r);
}
function sub(Duration left, Duration right)
internal
pure
returns (Duration)
{
uint64 l = Duration.unwrap(left);
uint64 r = Duration.unwrap(right);
return Duration.wrap(l - r);
}
function monus(Duration left, Duration right)
internal
pure
returns (Duration)
{
uint64 l = Duration.unwrap(left);
uint64 r = Duration.unwrap(right);
return Duration.wrap(l < r ? 0 : l - r);
}
function timeSpan(Instant left, Instant right)
internal
pure
returns (Duration)
{
uint64 l = Instant.unwrap(left);
uint64 r = Instant.unwrap(right);
return Duration.wrap(l - r);
}
function timeoutElapsedSince(
Instant timestamp,
Duration duration,
Instant current
) internal pure returns (bool) {
return !timestamp.add(duration).gt(current);
}
function timeoutElapsed(Instant timestamp, Duration duration)
internal
view
returns (bool)
{
return timestamp.timeoutElapsedSince(duration, currentTime());
}
function min(Duration left, Duration right)
internal
pure
returns (Duration)
{
return left.gt(right) ? right : left;
}
function max(Instant left, Instant right) internal pure returns (Instant) {
return left.gt(right) ? left : right;
}
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
import "prt-contracts/tournament/libs/Time.sol";
import "prt-contracts/arbitration-config/ArbitrationConstants.sol";
library Clock {
using Time for Time.Instant;
using Time for Time.Duration;
using Clock for State;
struct State {
Time.Duration allowance;
Time.Instant startInstant; // the block number when the clock started ticking, zero means clock is paused
}
//
// View/Pure methods
//
function notInitialized(State memory state) internal pure returns (bool) {
return state.allowance.isZero();
}
function requireInitialized(State memory state) internal pure {
require(!state.notInitialized(), "clock is not initialized");
}
function requireNotInitialized(State memory state) internal pure {
require(state.notInitialized(), "clock is initialized");
}
function hasTimeLeft(State memory state) internal view returns (bool) {
if (state.startInstant.isZero()) {
// a paused clock is always considered having time left
return true;
} else {
// otherwise the allowance must be greater than the timespan from current time to start instant
return state.allowance.gt(
Time.timeSpan(Time.currentTime(), state.startInstant)
);
}
}
/// @return max allowance of two paused clocks
function max(State memory pausedState1, State memory pausedState2)
internal
pure
returns (Time.Duration)
{
if (pausedState1.allowance.gt(pausedState2.allowance)) {
return pausedState1.allowance;
} else {
return pausedState2.allowance;
}
}
/// @return duration of time has elapsed since the clock timeout
function timeSinceTimeout(State memory state)
internal
view
returns (Time.Duration)
{
if (state.startInstant.isZero()) {
revert("a paused clock can't timeout");
}
return Time.timeSpan(Time.currentTime(), state.startInstant).monus(
state.allowance
);
}
function timeLeft(State memory state)
internal
view
returns (Time.Duration)
{
if (state.startInstant.isZero()) {
return state.allowance;
} else {
return state.allowance.monus(
Time.timeSpan(Time.currentTime(), state.startInstant)
);
}
}
//
// Storage methods
//
/// @notice re-initialize a clock with new state
function reInitialized(State storage state, State memory newState)
internal
{
Time.Duration _allowance = timeLeft(newState);
_setNewPaused(state, _allowance);
}
function setNewPaused(
State storage state,
Time.Instant checkinInstant,
Time.Duration initialAllowance
) internal {
Time.Duration _allowance =
initialAllowance.monus(Time.currentTime().timeSpan(checkinInstant));
_setNewPaused(state, _allowance);
}
/// @notice Resume the clock from pause state, or pause a clock and update the allowance
function advanceClock(State storage state) internal {
Time.Duration _timeLeft = timeLeft(state);
if (_timeLeft.isZero()) {
revert("can't advance clock with no time left");
}
toggleClock(state);
state.allowance = _timeLeft;
}
/// @notice Deduct duration from a clock and set it to paused.
/// The clock must have time left after deduction.
function deducted(State storage state, Time.Duration deduction) internal {
Time.Duration _timeLeft = state.allowance.monus(deduction);
_setNewPaused(state, _timeLeft);
}
/// @notice Deduct duration from a clock and set it to paused.
/// The clock must have time left after deduction.
function deduct(State memory state, Time.Duration deduction)
internal
pure
returns (State memory)
{
assert(state.startInstant.isZero());
Time.Duration _timeLeft = state.allowance.monus(deduction);
return State({startInstant: Time.ZERO_INSTANT, allowance: _timeLeft});
}
/// @notice Add matchEffort to a clock and set it to paused.
/// The new clock allowance is capped by maxAllowance.
function addMatchEffort(
State storage state,
Time.Duration matchEffort,
Time.Duration maxAllowance
) internal {
Time.Duration _timeLeft = timeLeft(state);
Time.Duration _allowance = _timeLeft.add(matchEffort).min(maxAllowance);
_setNewPaused(state, _allowance);
}
function setPaused(State storage state) internal {
if (!state.startInstant.isZero()) {
state.advanceClock();
}
}
//
// Private
//
function toggleClock(State storage state) private {
if (state.startInstant.isZero()) {
state.startInstant = Time.currentTime();
} else {
state.startInstant = Time.ZERO_INSTANT;
}
}
function _setNewPaused(State storage state, Time.Duration allowance)
private
{
if (allowance.isZero()) {
revert("can't create clock with zero time");
}
state.allowance = allowance;
state.startInstant = Time.ZERO_INSTANT;
}
}// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)
pragma solidity ^0.8.17;
import "prt-contracts/arbitration-config/ArbitrationConstants.sol";
import "prt-contracts/types/Tree.sol";
import "prt-contracts/types/Machine.sol";
import "./Commitment.sol";
/// @notice Implements functionalities to advance a match, until the point where divergence is found.
library Match {
using Tree for Tree.Node;
using Match for Id;
using Match for IdHash;
using Match for State;
using Machine for Machine.Hash;
using Commitment for Tree.Node;
//
// Events
//
event matchAdvanced(Match.IdHash indexed, Tree.Node parent, Tree.Node left);
//
// Id
//
struct Id {
Tree.Node commitmentOne;
Tree.Node commitmentTwo;
}
//
// IdHash
//
type IdHash is bytes32;
IdHash constant ZERO_ID = IdHash.wrap(bytes32(0x0));
function hashFromId(Id memory id) internal pure returns (IdHash) {
return IdHash.wrap(keccak256(abi.encode(id)));
}
function isZero(IdHash idHash) internal pure returns (bool) {
return IdHash.unwrap(idHash) == 0x0;
}
function eq(IdHash left, IdHash right) internal pure returns (bool) {
bytes32 l = IdHash.unwrap(left);
bytes32 r = IdHash.unwrap(right);
return l == r;
}
function requireEq(IdHash left, IdHash right) internal pure {
require(left.eq(right), "matches are not equal");
}
function requireExist(IdHash idHash) internal pure {
require(!idHash.isZero(), "match doesn't exist");
}
//
// State
//
struct State {
Tree.Node otherParent;
Tree.Node leftNode;
Tree.Node rightNode;
// Once match is done, leftNode and rightNode change meaning
// and contains contested final states.
uint256 runningLeafPosition;
uint64 currentHeight;
uint64 log2step; // constant
uint64 height; // constant
}
function createMatch(
Tree.Node one,
Tree.Node two,
Tree.Node leftNodeOfTwo,
Tree.Node rightNodeOfTwo,
uint64 log2step,
uint64 height
) internal pure returns (IdHash, State memory) {
assert(two.verify(leftNodeOfTwo, rightNodeOfTwo));
Id memory matchId = Id(one, two);
State memory state = State({
otherParent: one,
leftNode: leftNodeOfTwo,
rightNode: rightNodeOfTwo,
runningLeafPosition: 0,
currentHeight: height,
log2step: log2step,
height: height
});
return (matchId.hashFromId(), state);
}
function advanceMatch(
State storage state,
Id calldata id,
Tree.Node leftNode,
Tree.Node rightNode,
Tree.Node newLeftNode,
Tree.Node newRightNode
) internal {
state.requireParentHasChildren(leftNode, rightNode);
if (!state.agreesOnLeftNode(leftNode)) {
// go down left in Commitment tree
leftNode.requireChildren(newLeftNode, newRightNode);
state._goDownLeftTree(newLeftNode, newRightNode);
} else {
// go down right in Commitment tree
rightNode.requireChildren(newLeftNode, newRightNode);
state._goDownRightTree(newLeftNode, newRightNode);
}
emit matchAdvanced(id.hashFromId(), state.otherParent, state.leftNode);
}
error IncorrectAgreeState(
Machine.Hash initialState, Machine.Hash agreeState
);
function sealMatch(
State storage state,
Id calldata id,
Machine.Hash initialState,
Tree.Node leftLeaf,
Tree.Node rightLeaf,
Machine.Hash agreeState,
bytes32[] calldata agreeStateProof
)
internal
returns (Machine.Hash divergentStateOne, Machine.Hash divergentStateTwo)
{
state.requireParentHasChildren(leftLeaf, rightLeaf);
if (!state.agreesOnLeftNode(leftLeaf)) {
// Divergence is in the left leaf!
(divergentStateOne, divergentStateTwo) =
state._setDivergenceOnLeftLeaf(leftLeaf);
} else {
// Divergence is in the right leaf!
(divergentStateOne, divergentStateTwo) =
state._setDivergenceOnRightLeaf(rightLeaf);
}
// Prove agree hash is in commitment
if (state.runningLeafPosition == 0) {
require(
agreeState.eq(initialState),
IncorrectAgreeState(initialState, agreeState)
);
} else {
Tree.Node commitment;
if (state.height % 2 == 1) {
commitment = id.commitmentOne;
} else {
commitment = id.commitmentTwo;
}
commitment.requireState(
state.height,
state.runningLeafPosition - 1,
agreeState,
agreeStateProof
);
}
state._setAgreeState(agreeState);
}
//
// View methods
//
function exists(State memory state) internal pure returns (bool) {
return !state.otherParent.isZero();
}
function isFinished(State memory state) internal pure returns (bool) {
return state.currentHeight == 0;
}
function canBeFinalized(State memory state) internal pure returns (bool) {
return state.currentHeight == 1;
}
function canBeAdvanced(State memory state) internal pure returns (bool) {
return state.currentHeight > 1;
}
function agreesOnLeftNode(State memory state, Tree.Node newLeftNode)
internal
pure
returns (bool)
{
return newLeftNode.eq(state.leftNode);
}
function toCycle(State memory state, uint256 startCycle)
internal
pure
returns (uint256)
{
uint256 step = 1 << state.log2step;
uint256 leafPosition = state.runningLeafPosition;
return startCycle + (leafPosition * step);
}
function getDivergence(State memory state, uint256 startCycle)
internal
pure
returns (
Machine.Hash agreeHash,
uint256 agreeCycle,
Machine.Hash finalStateOne,
Machine.Hash finalStateTwo
)
{
assert(state.currentHeight == 0);
agreeHash = Machine.Hash.wrap(Tree.Node.unwrap(state.otherParent));
agreeCycle = state.toCycle(startCycle);
if (state.runningLeafPosition % 2 == 0) {
// divergence was set on left leaf
(finalStateOne, finalStateTwo) = _getDivergenceOnLeftLeaf(state);
} else {
// divergence was set on right leaf
(finalStateOne, finalStateTwo) = _getDivergenceOnRightLeaf(state);
}
}
//
// Requires
//
function requireExist(State memory state) internal pure {
require(state.exists(), "match does not exist");
}
function requireIsFinished(State memory state) internal pure {
require(state.isFinished(), "match is not finished");
}
function requireCanBeFinalized(State memory state) internal pure {
require(state.canBeFinalized(), "match is not ready to be finalized");
}
function requireCanBeAdvanced(State memory state) internal pure {
require(state.canBeAdvanced(), "match can't be advanced");
}
function requireParentHasChildren(
State memory state,
Tree.Node leftNode,
Tree.Node rightNode
) internal pure {
state.otherParent.requireChildren(leftNode, rightNode);
}
//
// Private
//
function _goDownLeftTree(
State storage state,
Tree.Node newLeftNode,
Tree.Node newRightNode
) internal {
assert(state.currentHeight > 1);
state.otherParent = state.leftNode;
state.leftNode = newLeftNode;
state.rightNode = newRightNode;
state.currentHeight--;
}
function _goDownRightTree(
State storage state,
Tree.Node newLeftNode,
Tree.Node newRightNode
) internal {
assert(state.currentHeight > 1);
state.otherParent = state.rightNode;
state.leftNode = newLeftNode;
state.rightNode = newRightNode;
state.currentHeight--;
state.runningLeafPosition += 1 << state.currentHeight;
}
function _setDivergenceOnLeftLeaf(State storage state, Tree.Node leftLeaf)
internal
returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo)
{
assert(state.currentHeight == 1);
state.rightNode = leftLeaf;
state.currentHeight = 0;
(finalStateOne, finalStateTwo) = _getDivergenceOnLeftLeaf(state);
}
function _setDivergenceOnRightLeaf(State storage state, Tree.Node rightLeaf)
internal
returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo)
{
assert(state.currentHeight == 1);
state.leftNode = rightLeaf;
state.currentHeight = 0;
state.runningLeafPosition += 1;
(finalStateOne, finalStateTwo) = _getDivergenceOnRightLeaf(state);
}
function _getDivergenceOnLeftLeaf(State memory state)
internal
pure
returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo)
{
if (state.height % 2 == 0) {
finalStateOne = state.leftNode.toMachineHash();
finalStateTwo = state.rightNode.toMachineHash();
} else {
finalStateOne = state.rightNode.toMachineHash();
finalStateTwo = state.leftNode.toMachineHash();
}
}
function _getDivergenceOnRightLeaf(State memory state)
internal
pure
returns (Machine.Hash finalStateOne, Machine.Hash finalStateTwo)
{
if (state.height % 2 == 0) {
finalStateOne = state.rightNode.toMachineHash();
finalStateTwo = state.leftNode.toMachineHash();
} else {
finalStateOne = state.leftNode.toMachineHash();
finalStateTwo = state.rightNode.toMachineHash();
}
}
function _setAgreeState(State storage state, Machine.Hash initialState)
internal
{
assert(state.currentHeight == 0);
state.otherParent = Tree.Node.wrap(Machine.Hash.unwrap(initialState));
}
}{
"remappings": [
"@openzeppelin-contracts-5.2.0/=dependencies/@openzeppelin-contracts-5.2.0/",
"forge-std-1.9.6/=dependencies/forge-std-1.9.6/",
"prt-contracts/=src/",
"step/=../../machine/step/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "cancun",
"viaIR": true,
"libraries": {}
}Contract ABI
API[{"inputs":[],"name":"ChildTournamentCannotBeEliminated","type":"error"},{"inputs":[],"name":"ChildTournamentMustBeEliminated","type":"error"},{"inputs":[],"name":"ChildTournamentNotFinished","type":"error"},{"inputs":[{"internalType":"Tree.Node","name":"received","type":"bytes32"},{"internalType":"Tree.Node","name":"expected","type":"bytes32"}],"name":"CommitmentMismatch","type":"error"},{"inputs":[],"name":"EliminateByTimeout","type":"error"},{"inputs":[{"internalType":"Machine.Hash","name":"initialState","type":"bytes32"},{"internalType":"Machine.Hash","name":"agreeState","type":"bytes32"}],"name":"IncorrectAgreeState","type":"error"},{"inputs":[{"internalType":"Machine.Hash","name":"contestedFinalStateOne","type":"bytes32"},{"internalType":"Machine.Hash","name":"contestedFinalStateTwo","type":"bytes32"},{"internalType":"Machine.Hash","name":"finalState","type":"bytes32"}],"name":"InvalidContestedFinalState","type":"error"},{"inputs":[{"internalType":"uint64","name":"treeHeight","type":"uint64"},{"internalType":"uint64","name":"siblingsLength","type":"uint64"}],"name":"LengthMismatch","type":"error"},{"inputs":[],"name":"TournamentIsClosed","type":"error"},{"inputs":[],"name":"TournamentIsFinished","type":"error"},{"inputs":[],"name":"WinByTimeout","type":"error"},{"inputs":[{"internalType":"uint256","name":"commitment","type":"uint256"},{"internalType":"Tree.Node","name":"parent","type":"bytes32"},{"internalType":"Tree.Node","name":"left","type":"bytes32"},{"internalType":"Tree.Node","name":"right","type":"bytes32"}],"name":"WrongChildren","type":"error"},{"inputs":[{"internalType":"Tree.Node","name":"commitmentRoot","type":"bytes32"},{"internalType":"Tree.Node","name":"winner","type":"bytes32"}],"name":"WrongTournamentWinner","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"Tree.Node","name":"root","type":"bytes32"}],"name":"commitmentJoined","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"Match.IdHash","name":"","type":"bytes32"},{"indexed":false,"internalType":"Tree.Node","name":"parent","type":"bytes32"},{"indexed":false,"internalType":"Tree.Node","name":"left","type":"bytes32"}],"name":"matchAdvanced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"Tree.Node","name":"one","type":"bytes32"},{"indexed":true,"internalType":"Tree.Node","name":"two","type":"bytes32"},{"indexed":false,"internalType":"Tree.Node","name":"leftOfTwo","type":"bytes32"}],"name":"matchCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"Match.IdHash","name":"","type":"bytes32"}],"name":"matchDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"Match.IdHash","name":"","type":"bytes32"},{"indexed":false,"internalType":"contract NonRootTournament","name":"","type":"address"}],"name":"newInnerTournament","type":"event"},{"inputs":[{"components":[{"internalType":"Tree.Node","name":"commitmentOne","type":"bytes32"},{"internalType":"Tree.Node","name":"commitmentTwo","type":"bytes32"}],"internalType":"struct Match.Id","name":"_matchId","type":"tuple"},{"internalType":"Tree.Node","name":"_leftNode","type":"bytes32"},{"internalType":"Tree.Node","name":"_rightNode","type":"bytes32"},{"internalType":"Tree.Node","name":"_newLeftNode","type":"bytes32"},{"internalType":"Tree.Node","name":"_newRightNode","type":"bytes32"}],"name":"advanceMatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"arbitrationResult","outputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"Tree.Node","name":"","type":"bytes32"},{"internalType":"Machine.Hash","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"Tree.Node","name":"commitmentOne","type":"bytes32"},{"internalType":"Tree.Node","name":"commitmentTwo","type":"bytes32"}],"internalType":"struct Match.Id","name":"_matchId","type":"tuple"}],"name":"canWinMatchByTimeout","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract NonRootTournament","name":"_childTournament","type":"address"}],"name":"eliminateInnerTournament","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"Tree.Node","name":"commitmentOne","type":"bytes32"},{"internalType":"Tree.Node","name":"commitmentTwo","type":"bytes32"}],"internalType":"struct Match.Id","name":"_matchId","type":"tuple"}],"name":"eliminateMatchByTimeout","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Tree.Node","name":"_commitmentRoot","type":"bytes32"}],"name":"getCommitment","outputs":[{"components":[{"internalType":"Time.Duration","name":"allowance","type":"uint64"},{"internalType":"Time.Instant","name":"startInstant","type":"uint64"}],"internalType":"struct Clock.State","name":"","type":"tuple"},{"internalType":"Machine.Hash","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"Match.IdHash","name":"_matchIdHash","type":"bytes32"}],"name":"getMatch","outputs":[{"components":[{"internalType":"Tree.Node","name":"otherParent","type":"bytes32"},{"internalType":"Tree.Node","name":"leftNode","type":"bytes32"},{"internalType":"Tree.Node","name":"rightNode","type":"bytes32"},{"internalType":"uint256","name":"runningLeafPosition","type":"uint256"},{"internalType":"uint64","name":"currentHeight","type":"uint64"},{"internalType":"uint64","name":"log2step","type":"uint64"},{"internalType":"uint64","name":"height","type":"uint64"}],"internalType":"struct Match.State","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"Match.IdHash","name":"_matchIdHash","type":"bytes32"}],"name":"getMatchCycle","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isClosed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isFinished","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"Machine.Hash","name":"_finalState","type":"bytes32"},{"internalType":"bytes32[]","name":"_proof","type":"bytes32[]"},{"internalType":"Tree.Node","name":"_leftNode","type":"bytes32"},{"internalType":"Tree.Node","name":"_rightNode","type":"bytes32"}],"name":"joinTournament","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"Tree.Node","name":"commitmentOne","type":"bytes32"},{"internalType":"Tree.Node","name":"commitmentTwo","type":"bytes32"}],"internalType":"struct Match.Id","name":"_matchId","type":"tuple"},{"internalType":"Tree.Node","name":"_leftLeaf","type":"bytes32"},{"internalType":"Tree.Node","name":"_rightLeaf","type":"bytes32"},{"internalType":"Machine.Hash","name":"_agreeHash","type":"bytes32"},{"internalType":"bytes32[]","name":"_agreeHashProof","type":"bytes32[]"}],"name":"sealInnerMatchAndCreateInnerTournament","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"timeFinished","outputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"Time.Instant","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tournamentLevelConstants","outputs":[{"internalType":"uint64","name":"_maxLevel","type":"uint64"},{"internalType":"uint64","name":"_level","type":"uint64"},{"internalType":"uint64","name":"_log2step","type":"uint64"},{"internalType":"uint64","name":"_height","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract NonRootTournament","name":"_childTournament","type":"address"},{"internalType":"Tree.Node","name":"_leftNode","type":"bytes32"},{"internalType":"Tree.Node","name":"_rightNode","type":"bytes32"}],"name":"winInnerTournament","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"Tree.Node","name":"commitmentOne","type":"bytes32"},{"internalType":"Tree.Node","name":"commitmentTwo","type":"bytes32"}],"internalType":"struct Match.Id","name":"_matchId","type":"tuple"},{"internalType":"Tree.Node","name":"_leftNode","type":"bytes32"},{"internalType":"Tree.Node","name":"_rightNode","type":"bytes32"}],"name":"winMatchByTimeout","outputs":[],"stateMutability":"nonpayable","type":"function"}]Loading...
Loading
Loading...
Loading
Net Worth in USD
$0.00
Net Worth in ETH
0
Multichain Portfolio | 33 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.