├── plasma_framework ├── .solhintignore ├── python_tests │ ├── plasma_core │ │ ├── __init__.py │ │ ├── utils │ │ │ ├── __init__.py │ │ │ ├── merkle │ │ │ │ ├── __init__.py │ │ │ │ ├── exceptions.py │ │ │ │ └── fixed_merkle.py │ │ │ ├── exit_priority.py │ │ │ ├── transactions.py │ │ │ └── utils.py │ │ ├── account.py │ │ ├── constants.py │ │ ├── exceptions.py │ │ └── block.py │ ├── testlang │ │ └── __init__.py │ ├── tests │ │ ├── tests_utils │ │ │ ├── __init__.py │ │ │ ├── constants.py │ │ │ ├── assertions.py │ │ │ └── deployer.py │ │ ├── contracts │ │ │ ├── root_chain │ │ │ │ ├── test_tokens.py │ │ │ │ ├── test_init.py │ │ │ │ └── test_submit_block.py │ │ │ └── rlp │ │ │ │ └── test_rlp.py │ │ └── utils │ │ │ ├── test_ganache.py │ │ │ ├── test_eip712_vectors.py │ │ │ └── test_fixed_merkle.py │ ├── setup.cfg │ ├── pytest.ini │ ├── Makefile │ ├── requirements.txt │ └── setup.py ├── codechecks.yml ├── .solcover.js ├── docs │ ├── audits │ │ ├── docs │ │ │ ├── Diligence_Morevp_Report.pdf │ │ │ ├── TOB_Rapid_Code_Review_Report.pdf │ │ │ └── Quantstamp_Plasma_Framework_Report.pdf │ │ └── README.md │ ├── design │ │ ├── images │ │ │ ├── transaction_dependencies.jpg │ │ │ └── spending-condition-registry-graph.jpg │ │ ├── fee-game.md │ │ └── add-new-tx-type-check-lists.md │ └── deploying │ │ └── deploying.md ├── contracts │ ├── mocks │ │ ├── attackers │ │ │ ├── OutOfGasFallbackAttacker.sol │ │ │ ├── FallbackFunctionFailAttacker.sol │ │ │ └── FailFastReentrancyGuardAttacker.sol │ │ ├── Imports.sol │ │ ├── utils │ │ │ ├── OnlyWithValueMock.sol │ │ │ ├── MerkleWrapper.sol │ │ │ ├── BitsWrapper.sol │ │ │ ├── QuarantineMock.sol │ │ │ ├── SafeEthTransferMock.sol │ │ │ ├── RLPMockSecurity.sol │ │ │ ├── BondSizeMock.sol │ │ │ └── PosLibWrapper.sol │ │ ├── framework │ │ │ ├── ProtocolWrapper.sol │ │ │ ├── utils │ │ │ │ ├── PriorityQueueLoadTest.sol │ │ │ │ └── ExitPriorityWrapper.sol │ │ │ ├── ExitGameControllerMock.sol │ │ │ ├── registries │ │ │ │ ├── VaultRegistryMock.sol │ │ │ │ └── ExitGameRegistryMock.sol │ │ │ ├── DummyVault.sol │ │ │ ├── BlockControllerMock.sol │ │ │ ├── ReentrancyExitGame.sol │ │ │ └── DummyExitGame.sol │ │ ├── transactions │ │ │ ├── eip712Libs │ │ │ │ └── PaymentEip712LibMock.sol │ │ │ ├── GenericTransactionWrapper.sol │ │ │ ├── FungibleTokenOutputWrapper.sol │ │ │ └── PaymentTransactionModelMock.sol │ │ ├── exits │ │ │ ├── dummyVaults │ │ │ │ ├── SpyErc20VaultForExitGame.sol │ │ │ │ └── SpyEthVaultForExitGame.sol │ │ │ ├── utils │ │ │ │ ├── OutputIdWrapper.sol │ │ │ │ ├── ExitIdWrapper.sol │ │ │ │ ├── ExitableTimestampWrapper.sol │ │ │ │ └── MoreVpFinalizationWrapper.sol │ │ │ ├── SpendingConditionMock.sol │ │ │ ├── SpyPlasmaFrameworkForExitGame.sol │ │ │ ├── StateTransitionVerifierMock.sol │ │ │ └── payment │ │ │ │ └── routers │ │ │ │ └── PaymentStandardExitRouterMock.sol │ │ ├── python_tests_wrappers │ │ │ ├── RLPTest.sol │ │ │ └── PriorityQueueTest.sol │ │ └── vaults │ │ │ └── NonCompliantERC20.sol │ ├── src │ │ ├── utils │ │ │ ├── OnlyWithValue.sol │ │ │ ├── OnlyFromAddress.sol │ │ │ ├── FailFastReentrancyGuard.sol │ │ │ ├── Bits.sol │ │ │ ├── SafeEthTransfer.sol │ │ │ ├── Merkle.sol │ │ │ └── PosLib.sol │ │ ├── exits │ │ │ ├── fee │ │ │ │ └── FeeExitGame.sol │ │ │ ├── interfaces │ │ │ │ ├── IStateTransitionVerifier.sol │ │ │ │ └── ISpendingCondition.sol │ │ │ ├── payment │ │ │ │ ├── PaymentExitGameArgs.sol │ │ │ │ ├── routers │ │ │ │ │ └── PaymentStandardExitRouterArgs.sol │ │ │ │ ├── controllers │ │ │ │ │ └── PaymentDeleteInFlightExit.sol │ │ │ │ └── spendingConditions │ │ │ │ │ └── PaymentOutputToPaymentTxCondition.sol │ │ │ ├── utils │ │ │ │ ├── OutputId.sol │ │ │ │ ├── ExitableTimestamp.sol │ │ │ │ ├── ExitId.sol │ │ │ │ └── MoreVpFinalization.sol │ │ │ └── registries │ │ │ │ └── SpendingConditionRegistry.sol │ │ ├── framework │ │ │ ├── models │ │ │ │ └── BlockModel.sol │ │ │ ├── Protocol.sol │ │ │ ├── interfaces │ │ │ │ └── IExitProcessor.sol │ │ │ ├── utils │ │ │ │ ├── Quarantine.sol │ │ │ │ └── ExitPriority.sol │ │ │ └── PlasmaFramework.sol │ │ ├── vaults │ │ │ ├── verifiers │ │ │ │ ├── IEthDepositVerifier.sol │ │ │ │ ├── IErc20DepositVerifier.sol │ │ │ │ ├── EthDepositVerifier.sol │ │ │ │ └── Erc20DepositVerifier.sol │ │ │ ├── Erc20Vault.sol │ │ │ └── EthVault.sol │ │ └── transactions │ │ │ └── FungibleTokenOutputModel.sol │ ├── Migrations.sol │ └── poc │ │ └── fast_exits │ │ └── QuasarPool.sol ├── scripts │ └── docgen.sh ├── migrations │ ├── 200_fee_exit_game.js │ ├── 50_deploy_spending_condition_registry.js │ ├── 120_payment_state_transition_verifier.js │ ├── 99_renounce_spending_condition_registry.js │ ├── 1020_deploy_dev_contracts.js │ ├── 40_deploy_erc20_vault.js │ ├── 30_deploy_eth_vault.js │ ├── 20_deploy_plasma_framework.js │ ├── 140_payment_exit_game.js │ ├── 100_payment_standard_controllers.js │ ├── 61_spending_conditions_fee.js │ ├── 1010_tenderly_setup.js │ ├── 60_spending_conditions_payment.js │ ├── 110_payment_ife_controllers.js │ ├── 1050_output_results.js │ ├── 9_create_authority.js │ └── 1040_setup.js ├── test │ ├── helpers │ │ ├── exitable.js │ │ ├── testlang.js │ │ ├── sign.js │ │ ├── constants.js │ │ ├── positions.js │ │ ├── utils.js │ │ ├── paymentEip712.js │ │ └── merkle.js │ ├── src │ │ ├── utils │ │ │ ├── OnlyWithValue.test.js │ │ │ ├── FailFastReentrancyGuard.test.js │ │ │ └── RLPOfficial.test.js │ │ ├── framework │ │ │ ├── Protocol.test.js │ │ │ └── PlasmaFramework.test.js │ │ ├── exits │ │ │ └── utils │ │ │ │ └── ExitableTimestamp.test.js │ │ └── transactions │ │ │ └── eip712Libs │ │ │ └── PaymentEip712Lib.test.js │ └── loadTests │ │ └── PriorityQueue.load.test.js ├── .solhint.json ├── Makefile ├── .eslintrc.js ├── config.js ├── package.json └── truffle-config.js ├── .gitmodules ├── .dockerignore ├── MultiSigWalletOverride ├── migrations │ ├── 1_initial_migration.js │ └── 2_deploy_contracts.js ├── Makefile ├── docker │ └── immutability │ │ └── config │ │ └── vault.hcl ├── docker-compose.yml ├── truffle.js └── package.json ├── Dockerfile ├── .github ├── workflows │ ├── dispatch-sync-to-private.yml │ ├── auto-pr-for-branch-syncing.yml │ └── sync-from-public-to-private.yml └── ISSUE_TEMPLATE.md ├── .gitignore └── .circleci └── scripts └── check_faucet_balance_and_warn.py /plasma_framework/.solhintignore: -------------------------------------------------------------------------------- 1 | Migrations.sol 2 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/plasma_core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/testlang/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/plasma_core/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/tests/tests_utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/plasma_core/utils/merkle/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plasma_framework/codechecks.yml: -------------------------------------------------------------------------------- 1 | checks: 2 | - name: eth-gas-reporter/codechecks 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "MultiSigWallet"] 2 | path = MultiSigWallet 3 | url = https://github.com/gnosis/MultiSigWallet.git 4 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_file = LICENSE.txt 3 | 4 | [flake8] 5 | ignore = 6 | E501, W503 7 | -------------------------------------------------------------------------------- /plasma_framework/.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: [ 3 | 'mocks', 4 | 'poc/fast_exits' 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = -n0 3 | markers = 4 | slow: marks tests as slow (deselect with '-m "not slow"') 5 | serial -------------------------------------------------------------------------------- /plasma_framework/python_tests/plasma_core/utils/merkle/exceptions.py: -------------------------------------------------------------------------------- 1 | class MemberNotExistException(Exception): 2 | """raise when a leaf is not in the merkle tree""" 3 | -------------------------------------------------------------------------------- /plasma_framework/docs/audits/docs/Diligence_Morevp_Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omgnetwork/plasma-contracts/HEAD/plasma_framework/docs/audits/docs/Diligence_Morevp_Report.pdf -------------------------------------------------------------------------------- /plasma_framework/docs/design/images/transaction_dependencies.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omgnetwork/plasma-contracts/HEAD/plasma_framework/docs/design/images/transaction_dependencies.jpg -------------------------------------------------------------------------------- /plasma_framework/docs/audits/docs/TOB_Rapid_Code_Review_Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omgnetwork/plasma-contracts/HEAD/plasma_framework/docs/audits/docs/TOB_Rapid_Code_Review_Report.pdf -------------------------------------------------------------------------------- /plasma_framework/python_tests/plasma_core/account.py: -------------------------------------------------------------------------------- 1 | class EthereumAccount(object): 2 | 3 | def __init__(self, address, key): 4 | self.address = address 5 | self.key = key 6 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | MultiSigWalletOverride/node_modules 2 | MultiSigWallet/node_modules 3 | plasma_framework/node_modules 4 | MultiSigWalletOverride/build 5 | MultiSigWallet/build 6 | plasma_framework/build 7 | -------------------------------------------------------------------------------- /plasma_framework/docs/audits/docs/Quantstamp_Plasma_Framework_Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omgnetwork/plasma-contracts/HEAD/plasma_framework/docs/audits/docs/Quantstamp_Plasma_Framework_Report.pdf -------------------------------------------------------------------------------- /plasma_framework/docs/design/images/spending-condition-registry-graph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omgnetwork/plasma-contracts/HEAD/plasma_framework/docs/design/images/spending-condition-registry-graph.jpg -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/attackers/OutOfGasFallbackAttacker.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | contract OutOfGasFallbackAttacker { 4 | function () external payable { 5 | while (true) {} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /MultiSigWalletOverride/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("./Migrations.sol") 2 | 3 | module.exports = async (deployer, _, [deployerAddress]) => { 4 | // Deploy migrations 5 | await deployer.deploy(Migrations); 6 | }; 7 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/utils/OnlyWithValue.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | contract OnlyWithValue { 4 | modifier onlyWithValue(uint256 _value) { 5 | require(msg.value == _value, "Input value must match msg.value"); 6 | _; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/utils/OnlyFromAddress.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | contract OnlyFromAddress { 4 | 5 | modifier onlyFrom(address caller) { 6 | require(msg.sender == caller, "Caller address is unauthorized"); 7 | _; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/attackers/FallbackFunctionFailAttacker.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | contract FallbackFunctionFailAttacker { 5 | function () external payable { 6 | revert("fail on fallback function"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /plasma_framework/scripts/docgen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | [[ -z $(git status --porcelain) ]] && rm -rf contracts/mocks && rm -rf contracts/poc && solidoc ./ ./docs/contracts && git checkout -- contracts/mocks && git checkout -- contracts/poc || echo 'Please make sure all files are committed and the git status is clean' 3 | -------------------------------------------------------------------------------- /MultiSigWalletOverride/Makefile: -------------------------------------------------------------------------------- 1 | init_multisig: 2 | cp ../MultiSigWalletOverride/migrations/* ../MultiSigWallet/migrations 3 | cp ../MultiSigWalletOverride/truffle.js ../MultiSigWallet/ 4 | cp ../MultiSigWalletOverride/package.json ../MultiSigWallet/ 5 | cp ../MultiSigWalletOverride/package-lock.json ../MultiSigWallet/ 6 | -------------------------------------------------------------------------------- /plasma_framework/migrations/200_fee_exit_game.js: -------------------------------------------------------------------------------- 1 | const FeeExitGame = artifacts.require('FeeExitGame'); 2 | 3 | module.exports = async ( 4 | deployer, 5 | _, 6 | // eslint-disable-next-line no-unused-vars 7 | [deployerAddress, maintainerAddress, authorityAddress], 8 | ) => { 9 | await deployer.deploy(FeeExitGame); 10 | }; 11 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/Imports.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | // Import contracts from third party Solidity libraries to make them available in tests. 4 | 5 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol"; 6 | 7 | contract Import { 8 | // dummy empty contract to allow the compiler not always trying to re-compile this file. 9 | } 10 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/utils/OnlyWithValueMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../src/utils/OnlyWithValue.sol"; 4 | 5 | contract OnlyWithValueMock is OnlyWithValue { 6 | event OnlyWithValuePassed(); 7 | 8 | function checkOnlyWithValue(uint256 _value) public payable onlyWithValue(_value) { 9 | emit OnlyWithValuePassed(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /plasma_framework/test/helpers/exitable.js: -------------------------------------------------------------------------------- 1 | const calculateNormalExitable = (minExitPeriod, now, blockTimestamp) => ( 2 | Math.max(blockTimestamp + minExitPeriod * 2, now + minExitPeriod) 3 | ); 4 | 5 | const calculateExitableForDepositExit = (minExitPeriod, now) => now + minExitPeriod; 6 | 7 | module.exports = { 8 | calculateNormalExitable, 9 | calculateExitableForDepositExit, 10 | }; 11 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/exits/fee/FeeExitGame.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | /** 4 | * It is an empty contract by design. We only want to be able to register the tx type to the framework. 5 | * For simplicity, a fee claiming tx does not have the ability to exit directly. 6 | * It should be first spend to a Payment tx and then exit the fund from Payment tx. 7 | */ 8 | contract FeeExitGame { 9 | } 10 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/utils/MerkleWrapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../src/utils/Merkle.sol"; 4 | 5 | contract MerkleWrapper { 6 | 7 | function checkMembership(bytes memory leaf, uint256 index, bytes32 rootHash, bytes memory proof) 8 | public 9 | pure 10 | returns (bool) 11 | { 12 | return Merkle.checkMembership(leaf, index, rootHash, proof); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /plasma_framework/migrations/50_deploy_spending_condition_registry.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const SpendingConditionRegistry = artifacts.require('SpendingConditionRegistry'); 4 | 5 | module.exports = async ( 6 | deployer, 7 | _, 8 | // eslint-disable-next-line no-unused-vars 9 | [deployerAddress, maintainerAddress, authorityAddress], 10 | ) => { 11 | await deployer.deploy(SpendingConditionRegistry); 12 | }; 13 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/plasma_core/constants.py: -------------------------------------------------------------------------------- 1 | NULL_BYTE = b'\x00' 2 | NULL_HASH = NULL_BYTE * 32 3 | NULL_SIGNATURE = NULL_BYTE * 65 4 | NULL_ADDRESS = NULL_BYTE * 20 5 | NULL_ADDRESS_HEX = '0x' + NULL_ADDRESS.hex() 6 | EMPTY_METADATA = NULL_BYTE * 32 7 | EMPTY_BYTES = b'' 8 | 9 | MINUTE = 60 10 | DAY = 60 * 60 * 24 11 | 12 | # used for suitable test contract instantiation 13 | MIN_EXIT_PERIOD = 4 * MINUTE 14 | 15 | CHILD_BLOCK_INTERVAL = 1000 16 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/framework/models/BlockModel.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | library BlockModel { 4 | /** 5 | * @notice Block data structure that is stored in the contract 6 | * @param root The Merkle root block hash of the Plasma blocks 7 | * @param timestamp The timestamp, in seconds, when the block is saved 8 | */ 9 | struct Block { 10 | bytes32 root; 11 | uint256 timestamp; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /plasma_framework/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "indent": ["error", 4], 5 | "bracket-align": "off", 6 | "reason-string": ["warn",{"maxLength":128}], 7 | "separate-by-one-line-in-contract": "warn", 8 | "no-empty-blocks": "off", 9 | "compiler-fixed": "off", 10 | "not-rely-on-time": "off", 11 | "mark-callable-contracts": "off", 12 | "compiler-version": ["error", "^0.5.0"] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/vaults/verifiers/IEthDepositVerifier.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | interface IEthDepositVerifier { 4 | /** 5 | * @notice Verifies a deposit transaction 6 | * @param depositTx The deposit transaction 7 | * @param amount The amount deposited 8 | * @param sender The owner of the deposit transaction 9 | */ 10 | function verify(bytes calldata depositTx, uint256 amount, address sender) external view; 11 | } 12 | -------------------------------------------------------------------------------- /plasma_framework/migrations/120_payment_state_transition_verifier.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const PaymentTransactionStateTransitionVerifier = artifacts.require('PaymentTransactionStateTransitionVerifier'); 4 | 5 | module.exports = async ( 6 | deployer, 7 | _, 8 | // eslint-disable-next-line no-unused-vars 9 | [deployerAddress, maintainerAddress, authorityAddress], 10 | ) => { 11 | await deployer.deploy(PaymentTransactionStateTransitionVerifier); 12 | }; 13 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/tests/tests_utils/constants.py: -------------------------------------------------------------------------------- 1 | import plasma_core.constants 2 | from plasma_core.transaction import Transaction 3 | 4 | EXIT_PERIOD = plasma_core.constants.MIN_EXIT_PERIOD 5 | 6 | SAFE_GAS_STIPEND = 2300 7 | 8 | INITIAL_IMMUNE_VAULTS = 2 9 | INITIAL_IMMUNE_EXIT_GAMES = 1 10 | 11 | GAS_LIMIT = 10000000 12 | START_GAS = GAS_LIMIT - 1000000 13 | 14 | INITIAL_ETH = 10000 * 10 ** 18 15 | 16 | PAYMENT_TX_MAX_INPUT_SIZE = Transaction.NUM_INPUTS 17 | PAYMENT_TX_MAX_OUTPUT_SIZE = Transaction.NUM_OUTPUTS 18 | -------------------------------------------------------------------------------- /plasma_framework/migrations/99_renounce_spending_condition_registry.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const SpendingConditionRegistry = artifacts.require('SpendingConditionRegistry'); 4 | 5 | module.exports = async ( 6 | _deployer, 7 | _, 8 | // eslint-disable-next-line no-unused-vars 9 | [deployerAddress, maintainerAddress, authorityAddress], 10 | ) => { 11 | const spendingConditionRegistry = await SpendingConditionRegistry.deployed(); 12 | await spendingConditionRegistry.renounceOwnership(); 13 | }; 14 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/plasma_core/utils/exit_priority.py: -------------------------------------------------------------------------------- 1 | def parse_exit_priority(priority): 2 | return parse_exitable_at(priority), parse_tx_pos(priority), parse_exit_id(priority) 3 | 4 | 5 | def parse_exitable_at(priority): 6 | return priority >> 224 # take 32 most significant bits 7 | 8 | 9 | def parse_tx_pos(priority): 10 | return (((priority >> 224) << 224) ^ priority) >> 168 # take 223-168 bits 11 | 12 | 13 | def parse_exit_id(priority): 14 | return ((priority >> 168) << 168) ^ priority # take 168 least significant bits 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10-alpine 2 | ARG VAULT 3 | RUN apk update && apk add make git g++ python 4 | COPY . /home/plasma-contracts 5 | 6 | WORKDIR /home/plasma-contracts/MultiSigWallet 7 | RUN if [ "$VAULT" = "true" ]; then \ 8 | rm -Rf ./node_modules && \ 9 | rm -Rf ./build && \ 10 | npm install && \ 11 | npx truffle version && \ 12 | npx truffle compile; \ 13 | fi 14 | 15 | WORKDIR /home/plasma-contracts/plasma_framework 16 | RUN rm -Rf ./node_modules && \ 17 | rm -Rf ./build && \ 18 | npm install && \ 19 | npx truffle version && \ 20 | npx truffle compile 21 | -------------------------------------------------------------------------------- /plasma_framework/migrations/1020_deploy_dev_contracts.js: -------------------------------------------------------------------------------- 1 | const PaymentEip712LibMock = artifacts.require('PaymentEip712LibMock'); 2 | const MerkleWrapper = artifacts.require('MerkleWrapper'); 3 | const ERC20Mintable = artifacts.require('ERC20Mintable'); 4 | 5 | module.exports = function (deployer) { 6 | const deployTestContracts = process.env.DEPLOY_TEST_CONTRACTS || false; 7 | if (deployTestContracts) { 8 | deployer.deploy(PaymentEip712LibMock); 9 | deployer.deploy(MerkleWrapper); 10 | deployer.deploy(ERC20Mintable); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /plasma_framework/test/helpers/testlang.js: -------------------------------------------------------------------------------- 1 | const { constants } = require('openzeppelin-test-helpers'); 2 | const { PaymentTransactionOutput, PlasmaDepositTransaction } = require('./transaction.js'); 3 | 4 | function deposit(outputType, amount, owner, tokenAddress = constants.ZERO_ADDRESS) { 5 | const output = new PaymentTransactionOutput(outputType, amount, owner, tokenAddress); 6 | const depositTx = new PlasmaDepositTransaction(output); 7 | return web3.utils.bytesToHex(depositTx.rlpEncoded()); 8 | } 9 | 10 | module.exports = { 11 | deposit, 12 | }; 13 | -------------------------------------------------------------------------------- /plasma_framework/test/helpers/sign.js: -------------------------------------------------------------------------------- 1 | const ethUtil = require('ethereumjs-util'); 2 | const sigUtil = require('eth-sig-util'); 3 | 4 | function sign(msgHashHex, privateKey) { 5 | // remove prefix '0x' 6 | const message = (msgHashHex.length === 66) ? msgHashHex.substring(2) : msgHashHex; 7 | const tosign = Buffer.from(message, 'hex'); 8 | const signed = ethUtil.ecsign( 9 | tosign, 10 | Buffer.from(privateKey.replace('0x', ''), 'hex'), 11 | ); 12 | return sigUtil.concatSig(signed.v, signed.r, signed.s); 13 | } 14 | 15 | module.exports = { sign }; 16 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/plasma_core/exceptions.py: -------------------------------------------------------------------------------- 1 | class TxAlreadySpentException(Exception): 2 | """the transaction is already spent""" 3 | 4 | 5 | class InvalidTxSignatureException(Exception): 6 | """the signature of a tx is invalid""" 7 | 8 | 9 | class InvalidBlockSignatureException(Exception): 10 | """the signature of a block is invalid""" 11 | 12 | 13 | class TxAmountMismatchException(Exception): 14 | """tx input total amount is not equal to output total amount""" 15 | 16 | 17 | class InvalidBlockMerkleException(Exception): 18 | """merkle tree of a block is invalid""" 19 | -------------------------------------------------------------------------------- /MultiSigWalletOverride/docker/immutability/config/vault.hcl: -------------------------------------------------------------------------------- 1 | default_lease_ttl = "168h" 2 | disable_mlock = "true" 3 | max_lease_ttl = "720h" 4 | 5 | backend "file" { 6 | path = "/home/vault/config/data" 7 | } 8 | 9 | ui = "false" 10 | 11 | api_addr = "https://localhost:8200" 12 | plugin_directory = "/home/vault/plugins" 13 | listener "tcp" { 14 | address = "0.0.0.0:8200" 15 | tls_cert_file = "/home/vault/config/my-service.crt" 16 | tls_client_ca_file = "/home/vault/config/ca.crt" 17 | tls_key_file = "/home/vault/config/my-service.key" 18 | tls_require_and_verify_client_cert = "false" 19 | } 20 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/plasma_core/utils/transactions.py: -------------------------------------------------------------------------------- 1 | BLKNUM_OFFSET = 1000000000 2 | TXINDEX_OFFSET = 10000 3 | 4 | 5 | def decode_utxo_id(utxo_id): 6 | blknum = utxo_id // BLKNUM_OFFSET 7 | txindex = (utxo_id % BLKNUM_OFFSET) // TXINDEX_OFFSET 8 | oindex = utxo_id % TXINDEX_OFFSET 9 | return blknum, txindex, oindex 10 | 11 | 12 | def encode_utxo_id(blknum, txindex, oindex): 13 | return (blknum * BLKNUM_OFFSET) + (txindex * TXINDEX_OFFSET) + (oindex * 1) 14 | 15 | 16 | def decode_tx_id(utxo_id): 17 | (blknum, txindex, _) = decode_utxo_id(utxo_id) 18 | return encode_utxo_id(blknum, txindex, 0) 19 | -------------------------------------------------------------------------------- /plasma_framework/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/framework/ProtocolWrapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../src/framework/Protocol.sol"; 4 | 5 | contract ProtocolWrapper { 6 | // solhint-disable-next-line func-name-mixedcase 7 | function MVP() public pure returns (uint8) { 8 | return Protocol.MVP(); 9 | } 10 | 11 | // solhint-disable-next-line func-name-mixedcase 12 | function MORE_VP() public pure returns (uint8) { 13 | return Protocol.MORE_VP(); 14 | } 15 | 16 | function isValidProtocol(uint8 protocol) public pure returns (bool) { 17 | return Protocol.isValidProtocol(protocol); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/transactions/eip712Libs/PaymentEip712LibMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../../src/transactions/eip712Libs/PaymentEip712Lib.sol"; 4 | import "../../../src/transactions/PaymentTransactionModel.sol"; 5 | 6 | contract PaymentEip712LibMock { 7 | function hashTx(address _verifyingContract, bytes memory _rlpTx) 8 | public 9 | pure 10 | returns (bytes32) 11 | { 12 | PaymentEip712Lib.Constants memory eip712 = PaymentEip712Lib.initConstants(_verifyingContract); 13 | return PaymentEip712Lib.hashTx(eip712, PaymentTransactionModel.decode(_rlpTx)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/tests/contracts/root_chain/test_tokens.py: -------------------------------------------------------------------------------- 1 | def test_exit_queue_adding_gas_cost(w3, plasma_framework): 2 | ADDRESS_A = b'\x00' * 19 + b'\x01' 3 | ADDRESS_B = b'\x00' * 19 + b'\x02' 4 | tx_hash = plasma_framework.addExitQueue(plasma_framework.erc20_vault_id, ADDRESS_A) 5 | gas = w3.eth.getTransactionReceipt(tx_hash).gasUsed 6 | print("PriorityQueue first deployment costs {} gas".format(gas)) 7 | tx_hash = plasma_framework.addExitQueue(plasma_framework.erc20_vault_id, ADDRESS_B) 8 | gas = w3.eth.getTransactionReceipt(tx_hash).gasUsed 9 | print("PriorityQueue second deployment costs {} gas".format(gas)) 10 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/vaults/verifiers/IErc20DepositVerifier.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | interface IErc20DepositVerifier { 4 | /** 5 | * @notice Verifies a deposit transaction 6 | * @param depositTx The deposit transaction 7 | * @param sender The owner of the deposit transaction 8 | * @param vault The address of the Erc20Vault contract 9 | * @return Verified (owner, token, amount) of the deposit ERC20 token data 10 | */ 11 | function verify(bytes calldata depositTx, address sender, address vault) 12 | external 13 | view 14 | returns (address owner, address token, uint256 amount); 15 | } 16 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/exits/dummyVaults/SpyErc20VaultForExitGame.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../../src/vaults/Erc20Vault.sol"; 4 | import "../../../src/framework/PlasmaFramework.sol"; 5 | 6 | contract SpyErc20VaultForExitGame is Erc20Vault { 7 | event Erc20WithdrawCalled( 8 | address target, 9 | address token, 10 | uint256 amount 11 | ); 12 | 13 | constructor(PlasmaFramework _framework) public Erc20Vault(_framework) {} 14 | 15 | /** override for test */ 16 | function withdraw(address payable _target, address _token, uint256 _amount) external { 17 | emit Erc20WithdrawCalled(_target, _token, _amount); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/tests/contracts/root_chain/test_init.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from eth_tester.exceptions import TransactionFailed 3 | from plasma_core.constants import MIN_EXIT_PERIOD, NULL_ADDRESS 4 | 5 | 6 | def test_exit_period_setting_has_effect(testlang): 7 | owner = testlang.accounts[0] 8 | deposit_id = testlang.deposit(owner, 100) 9 | 10 | spend_id = testlang.spend_utxo([deposit_id], [owner], outputs=[(owner.address, NULL_ADDRESS, 50)]) 11 | testlang.start_in_flight_exit(spend_id) 12 | 13 | testlang.forward_timestamp(2 * MIN_EXIT_PERIOD) 14 | 15 | with pytest.raises(TransactionFailed): 16 | testlang.piggyback_in_flight_exit_input(spend_id, 0, owner) 17 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/utils/FailFastReentrancyGuard.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../framework/ExitGameController.sol"; 4 | 5 | /** 6 | * @notice Reentrancy guard that fails immediately when a reentrace occurs 7 | * Works on multi-contracts level by activating and deactivating a reentrancy guard kept in plasma framework's state 8 | */ 9 | contract FailFastReentrancyGuard { 10 | 11 | /** 12 | * @dev Prevents reentrant calls by using a mutex. 13 | */ 14 | modifier nonReentrant(ExitGameController exitGameController) { 15 | exitGameController.activateNonReentrant(); 16 | _; 17 | exitGameController.deactivateNonReentrant(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/exits/dummyVaults/SpyEthVaultForExitGame.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../../src/vaults/EthVault.sol"; 4 | import "../../../src/framework/PlasmaFramework.sol"; 5 | 6 | contract SpyEthVaultForExitGame is EthVault { 7 | uint256 public constant SAFE_GAS_STIPEND = 2300; 8 | 9 | event EthWithdrawCalled( 10 | address target, 11 | uint256 amount 12 | ); 13 | 14 | constructor(PlasmaFramework _framework) public EthVault(_framework, SAFE_GAS_STIPEND) {} 15 | 16 | /** override for test */ 17 | function withdraw(address payable _target, uint256 _amount) external { 18 | emit EthWithdrawCalled(_target, _amount); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/framework/utils/PriorityQueueLoadTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../../src/framework/utils/PriorityQueue.sol"; 4 | 5 | contract PriorityQueueLoadTest is PriorityQueue { 6 | 7 | /** 8 | * Helper function to inject heap data. It only appends batch of data to the end of array used as heap. 9 | * The client using this should make sure the data is in the order of an valid heap. 10 | */ 11 | function setHeapData(uint256[] calldata heapList) external { 12 | for (uint i = 0; i < heapList.length; i++) { 13 | PriorityQueue.queue.heapList.push(heapList[i]); 14 | } 15 | PriorityQueue.queue.currentSize += heapList.length; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/utils/BitsWrapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../src/utils/Bits.sol"; 4 | 5 | contract BitsWrapper { 6 | function setBit(uint _self, uint8 _index) public pure returns (uint) 7 | { 8 | return Bits.setBit(_self, _index); 9 | } 10 | 11 | function clearBit(uint _self, uint8 _index) public pure returns (uint) 12 | { 13 | return Bits.clearBit(_self, _index); 14 | } 15 | 16 | /** 17 | * @dev It makes sense to expose just `bitSet` to be able to test both of Bits `getBit` and `bitSet` 18 | */ 19 | function bitSet(uint _self, uint8 _index) public pure returns (bool) 20 | { 21 | return Bits.bitSet(_self, _index); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/dispatch-sync-to-private.yml: -------------------------------------------------------------------------------- 1 | name: Dispatch sync event to plasma-contracts-private 2 | 3 | on: 4 | push: 5 | branches: [v2.0.0] 6 | 7 | jobs: 8 | dispatch-sync-event: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Repository Dispatch 13 | if: github.repository == 'omisego/plasma-contracts' 14 | run: | 15 | curl -X POST https://api.github.com/repos/omgnetwork/plasma-contracts-private/dispatches \ 16 | -H 'Accept: application/vnd.github.v3+json' \ 17 | -H 'authorization: token '${{ secrets.HOUSE_KEEPER_BOT_TOKEN }}'' \ 18 | --data '{"event_type": "sync-from-public", "client_payload": { "sha": "'"${{ github.sha }}"'" }}' 19 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/transactions/GenericTransactionWrapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../src/transactions/GenericTransaction.sol"; 5 | 6 | contract GenericTransactionWrapper { 7 | 8 | function decode(bytes memory transaction) public pure returns (GenericTransaction.Transaction memory) { 9 | return GenericTransaction.decode(transaction); 10 | } 11 | 12 | function getOutput(bytes memory transaction, uint16 outputIndex) public pure returns (GenericTransaction.Output memory) { 13 | GenericTransaction.Transaction memory genericTx = GenericTransaction.decode(transaction); 14 | return GenericTransaction.getOutput(genericTx, outputIndex); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/framework/Protocol.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | /** 4 | * @notice Protocols for the PlasmaFramework 5 | */ 6 | library Protocol { 7 | uint8 constant internal MVP_VALUE = 1; 8 | uint8 constant internal MORE_VP_VALUE = 2; 9 | 10 | // solhint-disable-next-line func-name-mixedcase 11 | function MVP() internal pure returns (uint8) { 12 | return MVP_VALUE; 13 | } 14 | 15 | // solhint-disable-next-line func-name-mixedcase 16 | function MORE_VP() internal pure returns (uint8) { 17 | return MORE_VP_VALUE; 18 | } 19 | 20 | function isValidProtocol(uint8 protocol) internal pure returns (bool) { 21 | return protocol == MVP_VALUE || protocol == MORE_VP_VALUE; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/exits/interfaces/IStateTransitionVerifier.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | interface IStateTransitionVerifier { 5 | 6 | /** 7 | * @notice Verifies state transition logic 8 | * @param txBytes The transaction that does the state transition to verify 9 | * @param inputTxs Input transaction to the transaction to verify 10 | * @param outputIndexOfInputTxs Output index of the input txs that the transaction input points to 11 | */ 12 | function isCorrectStateTransition( 13 | bytes calldata txBytes, 14 | bytes[] calldata inputTxs, 15 | uint16[] calldata outputIndexOfInputTxs 16 | ) 17 | external 18 | view 19 | returns (bool); 20 | } 21 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/exits/utils/OutputIdWrapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../../src/exits/utils/OutputId.sol"; 4 | 5 | contract OutputIdWrapper { 6 | function computeDepositOutputId( 7 | bytes memory _txBytes, 8 | uint8 _outputIndex, 9 | uint256 _utxoPosValue 10 | ) 11 | public 12 | pure 13 | returns (bytes32) 14 | { 15 | return OutputId.computeDepositOutputId(_txBytes, _outputIndex, _utxoPosValue); 16 | } 17 | 18 | function computeNormalOutputId( 19 | bytes memory _txBytes, 20 | uint8 _outputIndex 21 | ) 22 | public 23 | pure 24 | returns (bytes32) 25 | { 26 | return OutputId.computeNormalOutputId(_txBytes, _outputIndex); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/utils/QuarantineMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../src/framework/utils/Quarantine.sol"; 4 | 5 | contract QuarantineMock { 6 | using Quarantine for Quarantine.Data; 7 | Quarantine.Data internal _quarantine; 8 | 9 | constructor(uint256 _period, uint256 _initialImmuneCount) 10 | public 11 | { 12 | _quarantine.quarantinePeriod = _period; 13 | _quarantine.immunitiesRemaining = _initialImmuneCount; 14 | } 15 | 16 | function quarantineContract(address _contractAddress) public { 17 | _quarantine.quarantine(_contractAddress); 18 | } 19 | 20 | function isQuarantined(address _contractAddress) public view returns (bool) { 21 | return _quarantine.isQuarantined(_contractAddress); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /plasma_framework/migrations/40_deploy_erc20_vault.js: -------------------------------------------------------------------------------- 1 | const Erc20DepositVerifier = artifacts.require('Erc20DepositVerifier'); 2 | const Erc20Vault = artifacts.require('Erc20Vault'); 3 | const PlasmaFramework = artifacts.require('PlasmaFramework'); 4 | 5 | const config = require('../config.js'); 6 | 7 | module.exports = async ( 8 | deployer, 9 | _, 10 | // eslint-disable-next-line no-unused-vars 11 | [deployerAddress, maintainerAddress, authorityAddress], 12 | ) => { 13 | const plasmaFramework = await PlasmaFramework.deployed(); 14 | 15 | await deployer.deploy( 16 | Erc20DepositVerifier, 17 | config.registerKeys.txTypes.payment, 18 | config.registerKeys.outputTypes.payment, 19 | ); 20 | await deployer.deploy(Erc20Vault, plasmaFramework.address); 21 | }; 22 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/framework/ExitGameControllerMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../src/framework/ExitGameController.sol"; 5 | 6 | contract ExitGameControllerMock is ExitGameController { 7 | address private maintainer; 8 | 9 | constructor(uint256 _minExitPeriod, uint256 _initialImmuneExitGames) 10 | public 11 | ExitGameController(_minExitPeriod, _initialImmuneExitGames) 12 | { 13 | maintainer = msg.sender; 14 | } 15 | 16 | /** 17 | * override to make it non-abstract contract 18 | * this mock file set the user that deploys the contract as maintainer to simplify the test. 19 | */ 20 | function getMaintainer() public view returns (address) { 21 | return maintainer; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/framework/interfaces/IExitProcessor.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | /** 4 | * @dev An interface that allows custom logic to process exits for different requirements. 5 | * This interface is used to dispatch to each custom processor when 'processExits' is called on PlasmaFramework. 6 | */ 7 | interface IExitProcessor { 8 | /** 9 | * @dev Function interface for processing exits. 10 | * @param exitId Unique ID for exit per tx type 11 | * @param vaultId ID of the vault that funds the exit 12 | * @param token Address of the token contract 13 | * @param processExitInitiator Address of the processExit intitiator 14 | */ 15 | function processExit(uint168 exitId, uint256 vaultId, address token, address payable processExitInitiator) external; 16 | } 17 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/utils/SafeEthTransferMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../src/utils/SafeEthTransfer.sol"; 4 | 5 | contract SafeEthTransferMock { 6 | bool public transferResult; 7 | 8 | function transferRevertOnError(address payable receiver, uint256 amount, uint256 gasStipend) 9 | public 10 | { 11 | SafeEthTransfer.transferRevertOnError(receiver, amount, gasStipend); 12 | } 13 | 14 | function transferReturnResult(address payable receiver, uint256 amount, uint256 gasStipend) 15 | public 16 | { 17 | transferResult = SafeEthTransfer.transferReturnResult(receiver, amount, gasStipend); 18 | } 19 | 20 | /** helper function to pre-fund the contract to test */ 21 | function setupInitialFundToTestTransfer() external payable {} 22 | } 23 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/tests/contracts/root_chain/test_submit_block.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from eth_tester.exceptions import TransactionFailed 3 | 4 | 5 | def test_submit_block_valid_key_should_succeed(testlang): 6 | submitter = testlang.accounts[0] 7 | assert testlang.root_chain.nextChildBlock() == 1000 8 | blknum = testlang.submit_block([], submitter) 9 | 10 | block_info = testlang.root_chain.blocks(1000) 11 | assert block_info[0] == testlang.child_chain.get_block(blknum).root 12 | assert block_info[1] == testlang.timestamp 13 | assert testlang.root_chain.nextChildBlock() == 2000 14 | 15 | 16 | def test_submit_block_invalid_key_should_fail(testlang): 17 | submitter = testlang.accounts[1] 18 | 19 | with pytest.raises(TransactionFailed): 20 | testlang.submit_block([], submitter) 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Bytecode 2 | **/__pycache__/ 3 | 4 | # Development 5 | env*/ 6 | .vscode/ 7 | .DS_Store 8 | 9 | # Distibution 10 | dist/ 11 | *.egg-info/ 12 | 13 | # Testing 14 | .cache/ 15 | .pytest_cache/ 16 | contract_data/ 17 | 18 | *.pyc 19 | build/ 20 | 21 | # IDE 22 | .idea/ 23 | *.iml 24 | 25 | # contracts dependencies 26 | openzeppelin-solidity 27 | 28 | # dotenv environment vars 29 | .env 30 | 31 | # truffle build directory 32 | build/ 33 | 34 | # npm dependency directory 35 | node_modules 36 | 37 | # Debug log from npm 38 | npm-debug.log 39 | 40 | # coverage file output 41 | coverage 42 | coverage.json 43 | .coverage_artifacts/ 44 | .coverage_contracts/ 45 | 46 | # pytest-xprocess 47 | .xprocess/ 48 | 49 | #tenderly config 50 | tenderly.yaml 51 | 52 | MultiSigWallet 53 | MultiSigWalletOverride/ganache_data/ 54 | MultiSigWalletOverride/docker/immutability/ca/ 55 | -------------------------------------------------------------------------------- /plasma_framework/migrations/30_deploy_eth_vault.js: -------------------------------------------------------------------------------- 1 | const EthDepositVerifier = artifacts.require('EthDepositVerifier'); 2 | const EthVault = artifacts.require('EthVault'); 3 | const PlasmaFramework = artifacts.require('PlasmaFramework'); 4 | 5 | const config = require('../config.js'); 6 | 7 | module.exports = async ( 8 | deployer, 9 | _, 10 | // eslint-disable-next-line no-unused-vars 11 | [deployerAddress, maintainerAddress, authorityAddress], 12 | ) => { 13 | const plasmaFramework = await PlasmaFramework.deployed(); 14 | 15 | await deployer.deploy( 16 | EthDepositVerifier, 17 | config.registerKeys.txTypes.payment, 18 | config.registerKeys.outputTypes.payment, 19 | ); 20 | await deployer.deploy( 21 | EthVault, 22 | plasmaFramework.address, 23 | config.frameworks.safeGasStipend.v1, 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/exits/utils/ExitIdWrapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../../src/utils/PosLib.sol"; 4 | import "../../../src/exits/utils/ExitId.sol"; 5 | 6 | contract ExitIdWrapper { 7 | function isStandardExit(uint168 _exitId) public pure returns (bool) { 8 | return ExitId.isStandardExit(_exitId); 9 | } 10 | 11 | function getStandardExitId(bool _isDeposit, bytes memory _txBytes, uint256 _utxoPos) 12 | public 13 | pure 14 | returns (uint168) 15 | { 16 | PosLib.Position memory utxoPos = PosLib.decode(_utxoPos); 17 | return ExitId.getStandardExitId(_isDeposit, _txBytes, utxoPos); 18 | } 19 | 20 | function getInFlightExitId(bytes memory _txBytes) 21 | public 22 | pure 23 | returns (uint168) 24 | { 25 | return ExitId.getInFlightExitId(_txBytes); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/utils/RLPMockSecurity.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "../../src/utils/RLPReader.sol"; 6 | 7 | contract RLPMockSecurity { 8 | 9 | using RLPReader for bytes; 10 | using RLPReader for RLPReader.RLPItem; 11 | 12 | function decodeBytes32(bytes memory _data) public pure returns (bytes32) { 13 | return bytes32(_data.toRlpItem().toUint()); 14 | } 15 | 16 | function decodeBytes20(bytes memory _data) public pure returns (bytes20) { 17 | return bytes20(_data.toRlpItem().toAddress()); 18 | } 19 | 20 | function decodeUint(bytes memory _data) public pure returns (uint) { 21 | return _data.toRlpItem().toUint(); 22 | } 23 | 24 | function decodeList(bytes memory _data) public pure returns (RLPReader.RLPItem[] memory) { 25 | return _data.toRlpItem().toList(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/framework/registries/VaultRegistryMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../../src/framework/registries/VaultRegistry.sol"; 4 | 5 | contract VaultRegistryMock is VaultRegistry { 6 | address private maintainer; 7 | 8 | constructor (uint256 _minExitPeriod, uint256 _initialImmuneVaults) 9 | public 10 | VaultRegistry(_minExitPeriod, _initialImmuneVaults) 11 | { 12 | } 13 | 14 | /** override to make it non-abstract contract */ 15 | function getMaintainer() public view returns (address) { 16 | return maintainer; 17 | } 18 | 19 | /** test helper function */ 20 | function setMaintainer(address maintainerToSet) public { 21 | maintainer = maintainerToSet; 22 | } 23 | 24 | function checkOnlyFromNonQuarantinedVault() public onlyFromNonQuarantinedVault view returns (bool) { 25 | return true; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/utils/BondSizeMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../src/exits/utils/BondSize.sol"; 4 | 5 | contract BondSizeMock { 6 | using BondSize for BondSize.Params; 7 | 8 | BondSize.Params public bond; 9 | 10 | constructor (uint128 initialBondSize, uint128 initialExitBountySize, uint16 lowerBoundDivisor, uint16 upperBoundMultiplier) public { 11 | bond = BondSize.buildParams(initialBondSize, initialExitBountySize, lowerBoundDivisor, upperBoundMultiplier); 12 | } 13 | 14 | function bondSize() public view returns (uint128) { 15 | return bond.bondSize(); 16 | } 17 | 18 | function bountySize() public view returns (uint128) { 19 | return bond.exitBountySize(); 20 | } 21 | 22 | function updateBondSize(uint128 newBondSize, uint128 newExitBountySize) public { 23 | bond.updateBondSize(newBondSize, newExitBountySize); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/utils/PosLibWrapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../src/utils/PosLib.sol"; 5 | 6 | contract PosLibWrapper { 7 | using PosLib for PosLib.Position; 8 | 9 | function toStrictTxPos(PosLib.Position memory pos) 10 | public 11 | pure 12 | returns (PosLib.Position memory) 13 | { 14 | return pos.toStrictTxPos(); 15 | } 16 | 17 | function getTxPositionForExitPriority(PosLib.Position memory pos) 18 | public 19 | pure 20 | returns (uint56) 21 | { 22 | return pos.getTxPositionForExitPriority(); 23 | } 24 | 25 | function encode(PosLib.Position memory pos) public pure returns (uint256) { 26 | return pos.encode(); 27 | } 28 | 29 | function decode(uint256 pos) public pure returns (PosLib.Position memory) { 30 | return PosLib.decode(pos); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/framework/registries/ExitGameRegistryMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../../src/framework/registries/ExitGameRegistry.sol"; 4 | 5 | contract ExitGameRegistryMock is ExitGameRegistry { 6 | address private maintainer; 7 | 8 | constructor (uint256 _minExitPeriod, uint256 _initialImmuneExitGames) 9 | public 10 | ExitGameRegistry(_minExitPeriod, _initialImmuneExitGames) 11 | { 12 | } 13 | 14 | /** override to make it non-abstract contract */ 15 | function getMaintainer() public view returns (address) { 16 | return maintainer; 17 | } 18 | 19 | /** test helper function */ 20 | function setMaintainer(address maintainerToSet) public { 21 | maintainer = maintainerToSet; 22 | } 23 | 24 | function checkOnlyFromNonQuarantinedExitGame() public onlyFromNonQuarantinedExitGame view returns (bool) { 25 | return true; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/plasma_core/utils/utils.py: -------------------------------------------------------------------------------- 1 | from eth_utils import decode_hex 2 | 3 | 4 | def normalize_key(key): 5 | if isinstance(key, bytes): 6 | key = key.decode("utf-8") 7 | if isinstance(key, int): 8 | o = encode_int32(key) 9 | elif len(key) == 32: 10 | o = key 11 | elif len(key) == 64: 12 | o = decode_hex(key) 13 | elif len(key) == 66 and key[:2] == '0x': 14 | o = decode_hex(key[2:]) 15 | else: 16 | raise Exception("Invalid key format: %r" % key) 17 | if o == b'\x00' * 32: 18 | raise Exception("Zero privkey invalid") 19 | return o 20 | 21 | 22 | def hex_to_binary(h): 23 | assert isinstance(h, str) 24 | if h[:2] == '0x': 25 | h = h[2:] 26 | return bytes.fromhex(h) 27 | 28 | 29 | def decode_int32(v): 30 | return int.from_bytes(v, byteorder='big') 31 | 32 | 33 | def encode_int32(v): 34 | return v.to_bytes(32, byteorder='big') 35 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/attackers/FailFastReentrancyGuardAttacker.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../src/utils/FailFastReentrancyGuard.sol"; 5 | import "../../src/framework/PlasmaFramework.sol"; 6 | 7 | contract FailFastReentrancyGuardAttacker is FailFastReentrancyGuard { 8 | PlasmaFramework private framework; 9 | 10 | event RemoteCallFailed(); 11 | 12 | constructor(PlasmaFramework plasmaFramework) public { 13 | framework = plasmaFramework; 14 | } 15 | 16 | function guardedLocal() public nonReentrant(framework) { 17 | guardedLocal(); 18 | } 19 | 20 | function guardedRemote() external nonReentrant(framework) { 21 | // solhint-disable-next-line avoid-low-level-calls 22 | (bool success, ) = address(this).call(abi.encodeWithSignature("guardedRemote()")); 23 | if (!success) { 24 | emit RemoteCallFailed(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/tests/tests_utils/assertions.py: -------------------------------------------------------------------------------- 1 | def assert_events(events_objects, expected_events): 2 | assert len(events_objects) == len(expected_events) 3 | 4 | # sort received and expected events by name 5 | events_objects = sorted(events_objects, key=lambda e: e[1].event) 6 | expected_events = sorted(expected_events, key=lambda e: e[0]) 7 | 8 | for event_obj, expected_event in zip(events_objects, expected_events): 9 | assert_event(event_obj, *expected_event) 10 | 11 | 12 | def assert_event(event_obj, expected_event_name, expected_event_args=None, expected_contract_address=None): 13 | contract_address, event = event_obj 14 | 15 | if expected_event_args is None: 16 | expected_event_args = {} 17 | 18 | if expected_contract_address: 19 | assert contract_address == expected_contract_address 20 | 21 | assert event['event'] == expected_event_name 22 | assert expected_event_args.items() <= event['args'].items() 23 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/exits/interfaces/ISpendingCondition.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | /** 4 | * @notice Interface of the spending condition 5 | * @dev For the interface design and discussion, see the following GH issue 6 | * https://github.com/omisego/plasma-contracts/issues/214 7 | */ 8 | interface ISpendingCondition { 9 | 10 | /** 11 | * @notice Verifies the spending condition 12 | * @param inputTx Encoded input transaction, in bytes 13 | * @param utxoPos Position of the utxo 14 | * @param spendingTx Spending transaction, in bytes 15 | * @param inputIndex The input index of the spending tx that points to the output 16 | * @param witness The witness data of the spending condition 17 | */ 18 | function verify( 19 | bytes calldata inputTx, 20 | uint256 utxoPos, 21 | bytes calldata spendingTx, 22 | uint16 inputIndex, 23 | bytes calldata witness 24 | ) external view returns (bool); 25 | } 26 | -------------------------------------------------------------------------------- /plasma_framework/test/helpers/constants.js: -------------------------------------------------------------------------------- 1 | const PROTOCOL = { 2 | MVP: 1, 3 | MORE_VP: 2, 4 | }; 5 | 6 | const TX_TYPE = { 7 | PAYMENT: 1, 8 | PAYMENT_V2: 2, 9 | FEE: 3, 10 | }; 11 | 12 | const OUTPUT_TYPE = { 13 | PAYMENT: 1, 14 | FEE_CLAIM: 2, 15 | }; 16 | 17 | const EMPTY_BYTES = '0x'; 18 | const EMPTY_BYTES_32 = '0x0000000000000000000000000000000000000000000000000000000000000000'; 19 | 20 | const VAULT_ID = { 21 | ETH: 1, 22 | ERC20: 2, 23 | }; 24 | 25 | const CHILD_BLOCK_INTERVAL = 1000; 26 | const SAFE_GAS_STIPEND = 2300; 27 | 28 | 29 | const DUMMY_INPUT_1 = '0x0000000000000000000000000000000000000000000000000000000000000001'; 30 | const DUMMY_INPUT_2 = '0x0000000000000000000000000000000000000000000000000000000000000002'; 31 | 32 | module.exports = { 33 | EMPTY_BYTES, 34 | EMPTY_BYTES_32, 35 | PROTOCOL, 36 | TX_TYPE, 37 | OUTPUT_TYPE, 38 | CHILD_BLOCK_INTERVAL, 39 | VAULT_ID, 40 | SAFE_GAS_STIPEND, 41 | DUMMY_INPUT_1, 42 | DUMMY_INPUT_2, 43 | }; 44 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/exits/utils/ExitableTimestampWrapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../../src/exits/utils/ExitableTimestamp.sol"; 4 | 5 | contract ExitableTimestampWrapper { 6 | using ExitableTimestamp for ExitableTimestamp.Calculator; 7 | ExitableTimestamp.Calculator internal calculator; 8 | 9 | constructor(uint256 _minExitPeriod) public { 10 | calculator = ExitableTimestamp.Calculator(_minExitPeriod); 11 | } 12 | 13 | function calculateDepositTxOutputExitableTimestamp( 14 | uint256 _now 15 | ) 16 | public 17 | view 18 | returns (uint64) 19 | { 20 | return calculator.calculateDepositTxOutputExitableTimestamp(_now); 21 | } 22 | 23 | function calculateTxExitableTimestamp( 24 | uint256 _now, 25 | uint256 _blockTimestamp 26 | ) 27 | public 28 | view 29 | returns (uint64) 30 | { 31 | return calculator.calculateTxExitableTimestamp(_now, _blockTimestamp); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/framework/DummyVault.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "./registries/VaultRegistryMock.sol"; 4 | import "../../src/framework/BlockController.sol"; 5 | 6 | contract DummyVault { 7 | VaultRegistryMock internal vaultRegistry; 8 | BlockController internal blockController; 9 | 10 | // setter function only for test, not a real Vault function 11 | function setVaultRegistry(address _contract) public { 12 | vaultRegistry = VaultRegistryMock(_contract); 13 | } 14 | 15 | function checkOnlyFromNonQuarantinedVault() public view returns (bool) { 16 | return vaultRegistry.checkOnlyFromNonQuarantinedVault(); 17 | } 18 | 19 | // setter function only for test, not a real Vault function 20 | function setBlockController(address _contract) public { 21 | blockController = BlockController(_contract); 22 | } 23 | 24 | function submitDepositBlock(bytes32 _blockRoot) public { 25 | blockController.submitDepositBlock(_blockRoot); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/exits/utils/MoreVpFinalizationWrapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../../src/exits/utils/MoreVpFinalization.sol"; 5 | 6 | contract MoreVpFinalizationWrapper { 7 | function isStandardFinalized( 8 | PlasmaFramework framework, 9 | bytes memory txBytes, 10 | uint256 txPos, 11 | bytes memory inclusionProof 12 | ) 13 | public 14 | view 15 | returns (bool) 16 | { 17 | return MoreVpFinalization.isStandardFinalized( 18 | framework, 19 | txBytes, 20 | PosLib.decode(txPos), 21 | inclusionProof 22 | ); 23 | } 24 | 25 | function isProtocolFinalized( 26 | PlasmaFramework framework, 27 | bytes memory txBytes 28 | ) 29 | public 30 | view 31 | returns (bool) 32 | { 33 | return MoreVpFinalization.isProtocolFinalized( 34 | framework, 35 | txBytes 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/transactions/FungibleTokenOutputWrapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../src/transactions/FungibleTokenOutputModel.sol"; 5 | 6 | contract FungibleTokenOutputWrapper { 7 | using RLPReader for bytes; 8 | using RLPReader for RLPReader.RLPItem; 9 | 10 | function decodeOutput(bytes memory encodedOutput) 11 | public 12 | pure 13 | returns (FungibleTokenOutputModel.Output memory) 14 | { 15 | GenericTransaction.Output memory genericOutput = GenericTransaction.decodeOutput(encodedOutput.toRlpItem()); 16 | return FungibleTokenOutputModel.decodeOutput(genericOutput); 17 | } 18 | 19 | function getOutput(bytes memory transaction, uint16 outputIndex) 20 | public 21 | pure 22 | returns (FungibleTokenOutputModel.Output memory) 23 | { 24 | GenericTransaction.Transaction memory genericTx = GenericTransaction.decode(transaction); 25 | return FungibleTokenOutputModel.getOutput(genericTx, outputIndex); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/framework/utils/ExitPriorityWrapper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../../src/framework/utils/ExitPriority.sol"; 4 | import "../../../src/utils/PosLib.sol"; 5 | 6 | contract ExitPriorityWrapper { 7 | uint256 constant private SIZEOF_TIMESTAMP = 32; 8 | uint256 constant private SIZEOF_EXITID = 168; 9 | 10 | function computePriority(uint32 exitableAt, uint256 txPos, uint168 exitId) public pure returns (uint256) { 11 | return ExitPriority.computePriority(exitableAt, PosLib.decode(txPos), exitId); 12 | } 13 | 14 | function parseExitableAt(uint256 priority) public pure returns (uint32) { 15 | return ExitPriority.parseExitableAt(priority); 16 | } 17 | 18 | function parseExitId(uint256 priority) public pure returns (uint168) { 19 | return ExitPriority.parseExitId(priority); 20 | } 21 | 22 | function parseTxPos(uint256 priority) public pure returns (uint256) { 23 | uint256 pos = ((priority << SIZEOF_TIMESTAMP) >> SIZEOF_EXITID + SIZEOF_TIMESTAMP); 24 | return pos * 10000; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/auto-pr-for-branch-syncing.yml: -------------------------------------------------------------------------------- 1 | name: Auto PR for syncing master to master-private 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | auto-pr-for-branch-syncing: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Create Pull Request 13 | if: github.repository == 'omgnetwork/plasma-contracts-private' 14 | run: | 15 | set -o xtrace 16 | 17 | readonly FROM_BRANCH="master" 18 | readonly TO_BRANCH="master-private" 19 | readonly TITLE="sync: auto syncing from ${FROM_BRANCH} to ${TO_BRANCH}" 20 | readonly BODY="There is new update in \`${FROM_BRANCH}\`! Time to sync to \`${TO_BRANCH}\`!!" 21 | 22 | curl -X POST "https://api.github.com/repos/omgnetwork/plasma-contracts-private/pulls" \ 23 | -H "Accept: application/vnd.github.v3+json" \ 24 | -H "Authorization: token ${{ secrets.HOUSE_KEEPER_BOT_TOKEN }}" \ 25 | --data "{\"title\": \"${TITLE}\", \"head\": \"${FROM_BRANCH}\", \"base\": \"${TO_BRANCH}\", \"body\": \"${BODY}\"}" 26 | 27 | -------------------------------------------------------------------------------- /plasma_framework/test/helpers/positions.js: -------------------------------------------------------------------------------- 1 | const BLOCK_OFFSET = 1000000000; 2 | const TX_OFFSET = 10000; 3 | 4 | function buildUtxoPos(blockNum, txIndex, outputIndex) { 5 | return blockNum * BLOCK_OFFSET + txIndex * TX_OFFSET + outputIndex; 6 | } 7 | 8 | function buildTxPos(blockNum, txIndex) { 9 | return blockNum * BLOCK_OFFSET + txIndex * TX_OFFSET; 10 | } 11 | 12 | function utxoPosToTxPos(utxoPos) { 13 | return utxoPos - (utxoPos % TX_OFFSET); 14 | } 15 | 16 | function txPostionForExitPriority(utxoPos) { 17 | return Math.floor(utxoPos / TX_OFFSET); 18 | } 19 | 20 | class Position { 21 | constructor(utxoPos) { 22 | this.utxoPos = utxoPos; 23 | this.blockNum = Math.floor(this.utxoPos / BLOCK_OFFSET); 24 | this.txIndex = Math.floor((this.utxoPos % BLOCK_OFFSET) / TX_OFFSET); 25 | this.outputIndex = this.utxoPos % TX_OFFSET; 26 | this.txPos = buildTxPos(this.blockNum, this.txIndex); 27 | } 28 | } 29 | 30 | module.exports = { 31 | buildUtxoPos, 32 | buildTxPos, 33 | utxoPosToTxPos, 34 | txPostionForExitPriority, 35 | Position, 36 | }; 37 | -------------------------------------------------------------------------------- /MultiSigWalletOverride/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.2" 2 | services: 3 | ganache: 4 | image: trufflesuite/ganache-cli:latest 5 | ports: 6 | - "8545:8545" 7 | volumes: 8 | - ./ganache_data:/ganache_data 9 | entrypoint: 10 | - node 11 | - ./ganache-core.docker.cli.js 12 | - --deterministic 13 | - --db=/ganache_data 14 | - --mnemonic 15 | - 'minimum symptom minute gloom tragic situate silver mechanic salad amused elite beef' 16 | - --networkId 17 | - '5777' 18 | - --hostname 19 | - '0.0.0.0' 20 | - --debug 21 | vault_server: 22 | image: gcr.io/omisego-development/omgnetwork/vault:0.0.6 23 | ports: 24 | - "8200:8200" 25 | depends_on: 26 | - "ganache" 27 | links: 28 | - "ganache" 29 | volumes: 30 | - "./docker/immutability/ca:/home/vault/ca:rw" 31 | - "./docker/immutability/ca/certs/:/etc/ssl/certs/" 32 | - "./docker/immutability/config:/home/vault/config:rw" 33 | entrypoint: > 34 | /bin/sh -c " 35 | sleep 2 36 | /home/vault/config/entrypoint.sh 37 | " 38 | -------------------------------------------------------------------------------- /plasma_framework/test/src/utils/OnlyWithValue.test.js: -------------------------------------------------------------------------------- 1 | const OnlyWithValue = artifacts.require('OnlyWithValueMock'); 2 | 3 | const { expectRevert, expectEvent } = require('openzeppelin-test-helpers'); 4 | 5 | contract('OnlyWithValue', () => { 6 | beforeEach(async () => { 7 | this.contract = await OnlyWithValue.new(); 8 | }); 9 | 10 | describe('onlyWithValue', () => { 11 | it('should accept call when the value matches', async () => { 12 | const testValue = 100; 13 | const { receipt } = await this.contract.checkOnlyWithValue(testValue, { value: testValue }); 14 | await expectEvent.inTransaction( 15 | receipt.transactionHash, 16 | OnlyWithValue, 17 | 'OnlyWithValuePassed', 18 | {}, 19 | ); 20 | }); 21 | 22 | it('should reject call when value mismatches', async () => { 23 | await expectRevert( 24 | this.contract.checkOnlyWithValue(100, { value: 200 }), 25 | 'Input value must match msg.value', 26 | ); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /plasma_framework/Makefile: -------------------------------------------------------------------------------- 1 | PYTHON_TESTS_DIR = python_tests/ 2 | RUN_PYTHON_MAKEFILE_COMMAND = $(MAKE) -C $(PYTHON_TESTS_DIR) 3 | 4 | .PHONY: list 5 | list: 6 | @$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' 7 | 8 | .PHONY: clean 9 | clean: 10 | rm -rf node_modules/ 11 | $(RUN_PYTHON_MAKEFILE_COMMAND) clean 12 | 13 | .PHONY: init 14 | init: init_truffle init_python 15 | 16 | .PHONY: init_truffle 17 | init_truffle: 18 | npm install --python=python2 19 | 20 | .PHONY: init_python 21 | init_python: 22 | $(RUN_PYTHON_MAKEFILE_COMMAND) init dev 23 | 24 | .PHONY: test 25 | test: test_python test_truffle 26 | 27 | .PHONY: test_truffle 28 | test_truffle: 29 | npm test 30 | 31 | .PHONY: lint 32 | lint: 33 | $(RUN_PYTHON_MAKEFILE_COMMAND) lint 34 | ./node_modules/.bin/eslint . 35 | npm run linter-sol 36 | 37 | .PHONY: test_python 38 | test_python: 39 | $(RUN_PYTHON_MAKEFILE_COMMAND) test 40 | 41 | .PHONY: test_python_quick 42 | test_python_quick: 43 | $(RUN_PYTHON_MAKEFILE_COMMAND) test_quick 44 | 45 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/framework/BlockControllerMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../src/framework/BlockController.sol"; 5 | 6 | contract BlockControllerMock is BlockController { 7 | address private maintainer; 8 | 9 | constructor( 10 | uint256 interval, 11 | uint256 minExitPeriod, 12 | uint256 initialImmuneVaults, 13 | address authority 14 | ) 15 | public 16 | BlockController( 17 | interval, 18 | minExitPeriod, 19 | initialImmuneVaults, 20 | authority 21 | ) 22 | { 23 | maintainer = msg.sender; 24 | } 25 | 26 | /** 27 | * override to make it non-abstract contract 28 | * this mock file set the user that deploys the contract as maintainer to simplify the test. 29 | */ 30 | function getMaintainer() public view returns (address) { 31 | return maintainer; 32 | } 33 | 34 | function setBlock(uint256 _blockNum, bytes32 _root, uint256 _timestamp) external { 35 | blocks[_blockNum] = BlockModel.Block(_root, _timestamp); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/python_tests_wrappers/RLPTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "../../src/utils/RLPReader.sol"; 4 | 5 | 6 | /** 7 | * @title RLPTest 8 | * @dev Contract for testing RLP decoding. 9 | */ 10 | contract RLPTest { 11 | function eight(bytes memory tx_bytes) 12 | public 13 | pure 14 | returns (uint256, address, address) 15 | { 16 | RLPReader.RLPItem[] memory txList = RLPReader.toList(RLPReader.toRlpItem(tx_bytes)); 17 | return ( 18 | RLPReader.toUint(txList[5]), 19 | RLPReader.toAddress(txList[6]), 20 | RLPReader.toAddress(txList[7]) 21 | ); 22 | } 23 | 24 | function eleven(bytes memory tx_bytes) 25 | public 26 | pure 27 | returns (uint256, address, address, address) 28 | { 29 | RLPReader.RLPItem[] memory txList = RLPReader.toList(RLPReader.toRlpItem(tx_bytes)); 30 | return ( 31 | RLPReader.toUint(txList[7]), 32 | RLPReader.toAddress(txList[8]), 33 | RLPReader.toAddress(txList[9]), 34 | RLPReader.toAddress(txList[10]) 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/tests/utils/test_ganache.py: -------------------------------------------------------------------------------- 1 | def test_explicitly_mine_consecutive_blocks(w3, accounts): 2 | w3.eth.disable_auto_mine() 3 | 4 | latest_block = w3.eth.getBlock('latest') 5 | tx_hash = w3.eth.sendTransaction({'from': accounts[0].address, 'to': accounts[1].address, 'value': 100}) 6 | 7 | # new block has not been automatically mined 8 | assert w3.eth.getBlock('latest') == latest_block 9 | 10 | # mine the pending tx 11 | w3.eth.mine() 12 | 13 | new_block = w3.eth.getBlock('latest') 14 | assert new_block.number == latest_block.number + 1 15 | assert new_block.timestamp == latest_block.timestamp + 1 16 | assert tx_hash in new_block.transactions 17 | 18 | 19 | def test_auto_mine_transactions(w3, accounts): 20 | 21 | latest_block = w3.eth.getBlock('latest') 22 | tx_hash = w3.eth.sendTransaction({'from': accounts[0].address, 'to': accounts[1].address, 'value': 100}) 23 | 24 | # new block has been automatically mined 25 | 26 | new_block = w3.eth.getBlock('latest') 27 | assert new_block.number == latest_block.number + 1 28 | assert new_block.timestamp == latest_block.timestamp + 1 29 | assert tx_hash in new_block.transactions 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | ## Issue Type 6 | 7 | 8 | ``` 9 | [ ] bug report 10 | [ ] feature request 11 | ``` 12 | 13 | ## Current Behavior 14 | 15 | 16 | 17 | ## Expected Behavior 18 | 19 | 20 | 21 | ## Steps to Reproduce 22 | 23 | 24 | 1. 25 | 2. 26 | 3. 27 | 28 | - Full output of error: 29 | - Command that caused error: 30 | - Code that caused error: 31 | 32 | 33 | ## Suggested Fix 34 | 35 | 36 | 37 | ## Motivation for Change 38 | 39 | 40 | 41 | ## System Specs 42 | 43 | 44 | - python version: 45 | - pyetherem version: 46 | - environment (output of running `pip freeze`): 47 | - operating system: 48 | 49 | 53 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/exits/payment/PaymentExitGameArgs.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../registries/SpendingConditionRegistry.sol"; 4 | import "../interfaces/IStateTransitionVerifier.sol"; 5 | import "../../framework/PlasmaFramework.sol"; 6 | 7 | library PaymentExitGameArgs { 8 | /** 9 | * @param framework The Plasma framework 10 | * @param ethVaultId Vault id for EthVault 11 | * @param erc20VaultId Vault id for the Erc20Vault 12 | * @param spendingConditionRegistry the spendingConditionRegistry that can provide spending condition implementation by types 13 | * @param stateTransitionVerifier state transition verifier predicate contract that checks the transaction correctness 14 | * @param supportTxType the tx type of this exit game is using 15 | * @param safeGasStipend a gas amount limit when transferring Eth to protect from attack with draining gas 16 | */ 17 | struct Args { 18 | PlasmaFramework framework; 19 | uint256 ethVaultId; 20 | uint256 erc20VaultId; 21 | SpendingConditionRegistry spendingConditionRegistry; 22 | IStateTransitionVerifier stateTransitionVerifier; 23 | uint256 supportTxType; 24 | uint256 safeGasStipend; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /plasma_framework/docs/audits/README.md: -------------------------------------------------------------------------------- 1 | ## Audits 2 | 3 | The following list contains information on all the audits that have been conducted for the Plasma contracts component of the OMG network. 4 | 5 | | Release | Organization | Type | Period | Report | 6 | |--|--|--|--|--| 7 | | [v0.0.1](https://github.com/omisego/plasma-contracts/releases/tag/v0.0.1) | Quantstamp | Full Audit | 2018.09.24 - 2018.11.10 | [MD](https://github.com/omisego/plasma-contracts/blob/v0.0.1/docs/quantstamp-audit-3cc6097.md)| 8 | | [v1.0.0](https://github.com/omisego/plasma-contracts/releases/tag/v1.0.0) | Consensys Diligence | Full Audit (5 weeks / 2 auditors) | 2019.10.21 - 2019.11.22 and 2019.12.16 - 2020.01.03 | [URL](https://diligence.consensys.net/audits/2020/01/omisego-morevp/), [PDF](./docs/Diligence_Morevp_Report.pdf)| 9 | | [v1.0.0](https://github.com/omisego/plasma-contracts/releases/tag/v1.0.0) | Quantstamp | Full Audit (4 weeks / 2 auditors) | 2019.10.21 - 2019.11.22 and 2019.01.06 - 2020.01.10 |[PDF](./docs/Quantstamp_Plasma_Framework_Report.pdf) | 10 | | [v2-in-development](https://github.com/omgnetwork/plasma-contracts/commit/5ced05a49f4bb141d15d921c40432f214573c8e8) | Trail of Bits | Rapid Code Review (1 week / 1 auditor) | 2020.10.27 - 2020.10.30 |[PDF](./docs/TOB_Rapid_Code_Review_Report.pdf) | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/transactions/PaymentTransactionModelMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../src/transactions/PaymentTransactionModel.sol"; 5 | 6 | contract PaymentTransactionModelMock { 7 | using RLPReader for bytes; 8 | 9 | function decode(bytes memory transaction) public pure returns (PaymentTransactionModel.Transaction memory) { 10 | return PaymentTransactionModel.decode(transaction); 11 | } 12 | 13 | function getOutputOwner(uint256 outputType, address owner, address token, uint256 amount) public pure returns (address payable) { 14 | FungibleTokenOutputModel.Output memory output = FungibleTokenOutputModel.Output({ 15 | outputType: outputType, 16 | outputGuard: bytes20(owner), 17 | token: token, 18 | amount: amount 19 | }); 20 | return PaymentTransactionModel.getOutputOwner(output); 21 | } 22 | 23 | function getOutput(bytes memory transaction, uint16 outputIndex) public pure returns (FungibleTokenOutputModel.Output memory) { 24 | PaymentTransactionModel.Transaction memory decodedTx = PaymentTransactionModel.decode(transaction); 25 | return PaymentTransactionModel.getOutput(decodedTx, outputIndex); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /plasma_framework/migrations/20_deploy_plasma_framework.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const PlasmaFramework = artifacts.require('PlasmaFramework'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const config = require('../config.js'); 6 | 7 | module.exports = async ( 8 | deployer, 9 | _, 10 | // eslint-disable-next-line no-unused-vars 11 | [deployerAddress, maintainerAddress, authorityAddress], 12 | ) => { 13 | let authority; 14 | let maintainer; 15 | const vault = process.env.VAULT === 'true'; 16 | if (vault) { 17 | authority = fs.readFileSync('vault_authority').toString(); 18 | const multisigInstance = path.resolve(__dirname, '../../MultiSigWallet/build/multisig_instance'); 19 | maintainer = fs.readFileSync(multisigInstance, 'utf8'); 20 | } else { 21 | authority = authorityAddress; 22 | maintainer = maintainerAddress; 23 | } 24 | console.log(`Deploying plasma framework with authority ${authority} and maintainer ${maintainer}`); 25 | await deployer.deploy( 26 | PlasmaFramework, 27 | config.frameworks.minExitPeriod, 28 | config.frameworks.initialImmuneVaults, 29 | config.frameworks.initialImmuneExitGames, 30 | authority, 31 | maintainer, 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /MultiSigWalletOverride/truffle.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); // auto parse env variables from '.env' file 2 | const HDWalletProvider = require('@truffle/hdwallet-provider'); 3 | 4 | module.exports = { 5 | networks: { 6 | development: { 7 | host: "localhost", 8 | port: 8545, 9 | network_id: "*", // Match any network id 10 | gas: 4000000, 11 | gasPrice: 10000000000, // 10 gwei 12 | }, 13 | loadTest: { 14 | host: '127.0.0.1', 15 | port: 8545, 16 | network_id: '*', 17 | gas: 0xfffffffffff, 18 | }, 19 | local: { 20 | host: process.env.ETH_CLIENT_HOST || '127.0.0.1', 21 | port: process.env.ETH_CLIENT_PORT || 8545, 22 | from: process.env.DEPLOYER_ADDRESS, 23 | network_id: '*', 24 | }, 25 | remote: { 26 | gasPrice: process.env.GAS_PRICE || 20000000000, // default 20 gwei 27 | skipDryRun: true, 28 | provider: () => new HDWalletProvider( 29 | [ 30 | process.env.DEPLOYER_PRIVATEKEY || '0'.repeat(64), 31 | ], 32 | process.env.REMOTE_URL || 'http://127.0.0.1:8545', 33 | 0, 1, 34 | ), 35 | network_id: '*', 36 | } 37 | }, 38 | // Configure your compilers 39 | compilers: { 40 | solc: { 41 | version: '0.4.15' 42 | }, 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /plasma_framework/migrations/140_payment_exit_game.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const PaymentExitGame = artifacts.require('PaymentExitGame'); 4 | const PaymentTransactionStateTransitionVerifier = artifacts.require('PaymentTransactionStateTransitionVerifier'); 5 | const PlasmaFramework = artifacts.require('PlasmaFramework'); 6 | const SpendingConditionRegistry = artifacts.require('SpendingConditionRegistry'); 7 | const config = require('../config.js'); 8 | 9 | module.exports = async ( 10 | deployer, 11 | _, 12 | // eslint-disable-next-line no-unused-vars 13 | [deployerAddress, maintainerAddress, authorityAddress], 14 | ) => { 15 | const PAYMENT_TX_TYPE = config.registerKeys.txTypes.payment; 16 | const spendingConditionRegistry = await SpendingConditionRegistry.deployed(); 17 | const stateVerifier = await PaymentTransactionStateTransitionVerifier.deployed(); 18 | const plasmaFramework = await PlasmaFramework.deployed(); 19 | const paymentExitGameArgs = [ 20 | plasmaFramework.address, 21 | config.registerKeys.vaultId.eth, 22 | config.registerKeys.vaultId.erc20, 23 | spendingConditionRegistry.address, 24 | stateVerifier.address, 25 | PAYMENT_TX_TYPE, 26 | config.frameworks.safeGasStipend.v1, 27 | ]; 28 | await deployer.deploy(PaymentExitGame, paymentExitGameArgs); 29 | }; 30 | -------------------------------------------------------------------------------- /plasma_framework/migrations/100_payment_standard_controllers.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const PaymentExitGame = artifacts.require('PaymentExitGame'); 4 | const PaymentChallengeStandardExit = artifacts.require('PaymentChallengeStandardExit'); 5 | const PaymentStartStandardExit = artifacts.require('PaymentStartStandardExit'); 6 | const PaymentProcessStandardExit = artifacts.require('PaymentProcessStandardExit'); 7 | 8 | module.exports = async ( 9 | deployer, 10 | _, 11 | // eslint-disable-next-line no-unused-vars 12 | [deployerAddress, maintainerAddress, authorityAddress], 13 | ) => { 14 | // deploy and link standard exit game controllers 15 | 16 | await deployer.deploy(PaymentStartStandardExit); 17 | const startStandardExit = await PaymentStartStandardExit.deployed(); 18 | 19 | await deployer.deploy(PaymentChallengeStandardExit); 20 | const challengeStandardExit = await PaymentChallengeStandardExit.deployed(); 21 | 22 | await deployer.deploy(PaymentProcessStandardExit); 23 | const processStandardExit = await PaymentProcessStandardExit.deployed(); 24 | 25 | await PaymentExitGame.link('PaymentStartStandardExit', startStandardExit.address); 26 | await PaymentExitGame.link('PaymentChallengeStandardExit', challengeStandardExit.address); 27 | await PaymentExitGame.link('PaymentProcessStandardExit', processStandardExit.address); 28 | }; 29 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/Makefile: -------------------------------------------------------------------------------- 1 | init: 2 | (test -d openzeppelin-solidity || git clone https://github.com/OpenZeppelin/openzeppelin-solidity.git) && cd openzeppelin-solidity && git checkout v2.3.0 3 | pip install -e . 4 | 5 | .PHONY: help 6 | help: 7 | @echo "clean - remove build artifacts" 8 | @echo "lint - check style with flake8" 9 | @echo "test - runs tests with pytest" 10 | @echo "dev - installs dev dependencies" 11 | 12 | .PHONY: clean 13 | clean: clean-build clean-pyc 14 | 15 | clean-build: 16 | rm -fr build/ 17 | rm -fr dist/ 18 | rm -fr contract_data/ 19 | rm -fr *.egg-info 20 | rm -fr .pytest_cache 21 | rm -rf openzeppelin-solidity/ 22 | 23 | 24 | clean-pyc: 25 | find . -name '*.pyc' -exec rm -f {} + 26 | find . -name '*.pyo' -exec rm -f {} + 27 | find . -name '*~' -exec rm -f {} + 28 | find . -name '*pycache__' -exec rm -rf {} + 29 | 30 | .PHONY: lint 31 | lint: 32 | flake8 plasma_core testlang tests 33 | 34 | .PHONY: test 35 | test: 36 | python -m pytest -m "not slow" 37 | rm -fr .pytest_cache 38 | 39 | .PHONY: test_quick 40 | test_quick: 41 | python -m pytest -m "not slow" -n auto 42 | rm -fr .pytest_cache 43 | 44 | .PHONY: conctest 45 | conctest: 46 | python -m pytest -n auto 47 | rm -fr .pytest_cache 48 | 49 | .PHONY: runslow 50 | runslow: 51 | python -m pytest -m "slow" -s 52 | rm -fr .pytest_cache 53 | 54 | .PHONY: dev 55 | dev: 56 | pip install -e .[dev] 57 | -------------------------------------------------------------------------------- /MultiSigWalletOverride/migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | const MultisigWalletWithDailyLimit = artifacts.require('MultiSigWalletWithDailyLimit.sol') 2 | const MultisigWalletWithoutDailyLimit = artifacts.require('MultiSigWallet.sol') 3 | const MultisigWalletFactory = artifacts.require('MultiSigWalletWithDailyLimitFactory.sol') 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | 7 | module.exports = async (deployer, [_deployerAddress]) => { 8 | const args = process.argv.slice(); 9 | const accountsIndex = args.indexOf('--accounts'); 10 | const confirmationsIndex = args.indexOf('--confirmations'); 11 | const numberOfConfirmations = args[confirmationsIndex + 1]; 12 | const accounts = args[accountsIndex + 1].split(","); 13 | if (accountsIndex === -1 || confirmationsIndex === -1) { 14 | console.error('ABORTED. Use: --accounts 0xasdf,0xfdsa --confirmations 2'); 15 | process.exit(1); 16 | } else { 17 | console.log(`Accounts: ${accounts}`); 18 | console.log(`Confirmations: ${numberOfConfirmations}`); 19 | await deployer.deploy(MultisigWalletWithoutDailyLimit, accounts, numberOfConfirmations); 20 | const buildDir = path.resolve(__dirname, '../build'); 21 | if (!fs.existsSync(buildDir)) { 22 | fs.mkdirSync(buildDir); 23 | } 24 | fs.writeFileSync(path.resolve(buildDir, 'multisig_instance'), `${MultisigWalletWithoutDailyLimit.address}`.toLowerCase()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MultiSigWalletOverride/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multisig-wallet-gnosis", 3 | "version": "1.6.0", 4 | "description": "Ethereum MultiSignature Wallet. Main module, for installing all dependencies", 5 | "scripts": { 6 | "test-dapp": "cd dapp && npm test", 7 | "test": "sh truffle_test_runner.sh", 8 | "install": "cd dapp && npm install", 9 | "start": "cd dapp && npm start" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/gnosis/MultiSigWallet.git" 14 | }, 15 | "author": "Gnosis (https://gnosis.pm/)", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/gnosis/MultiSigWallet/issues" 19 | }, 20 | "homepage": "https://github.com/gnosis/MultiSigWallet#readme", 21 | "eslintConfig": { 22 | "env": { 23 | "browser": true, 24 | "node": false 25 | } 26 | }, 27 | "dependencies": { 28 | "@truffle/hdwallet-provider": "^1.1.1", 29 | "child_process": "1.0.2", 30 | "dotenv": "^8.2.0", 31 | "ganache-cli": "6.1.8", 32 | "solc": "^0.7.4", 33 | "truffle": "^5.1.51" 34 | }, 35 | "devDependencies": { 36 | "babel-eslint": "7.2.3", 37 | "eslint": "4.19.1", 38 | "eslint-config-airbnb": "15.1.0", 39 | "eslint-plugin-import": "2.12.0", 40 | "eslint-plugin-jsx-a11y": "5.1.1", 41 | "eslint-plugin-react": "7.8.2", 42 | "growl": "^1.10.5", 43 | "run-with-testrpc": "^0.3.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/exits/payment/routers/PaymentStandardExitRouterArgs.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | library PaymentStandardExitRouterArgs { 4 | /** 5 | * @notice Wraps arguments for startStandardExit 6 | * @param utxoPos Position of the exiting output 7 | * @param rlpOutputTx The RLP-encoded transaction that creates the exiting output 8 | * @param outputTxInclusionProof A Merkle proof showing that the transaction was included 9 | */ 10 | struct StartStandardExitArgs { 11 | uint256 utxoPos; 12 | bytes rlpOutputTx; 13 | bytes outputTxInclusionProof; 14 | } 15 | 16 | /** 17 | * @notice Input args data for challengeStandardExit 18 | * @param exitId Identifier of the standard exit to challenge 19 | * @param exitingTx RLP-encoded transaction that creates the exiting output 20 | * @param challengeTx RLP-encoded transaction that spends the exiting output 21 | * @param inputIndex Input of the challenging tx, corresponding to the exiting output 22 | * @param witness Witness data that proves the exiting output is spent 23 | * @param senderData A keccak256 hash of the sender's address 24 | */ 25 | struct ChallengeStandardExitArgs { 26 | uint168 exitId; 27 | bytes exitingTx; 28 | bytes challengeTx; 29 | uint16 inputIndex; 30 | bytes witness; 31 | bytes32 senderData; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/framework/ReentrancyExitGame.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../src/framework/ExitGameController.sol"; 5 | import "../../src/framework/interfaces/IExitProcessor.sol"; 6 | 7 | contract ReentrancyExitGame is IExitProcessor { 8 | ExitGameController public exitGameController; 9 | uint256 public vaultId; 10 | address public testToken; 11 | uint256 public reentryMaxExitToProcess; 12 | 13 | constructor(ExitGameController _controller, uint256 _vaultId, address _token, uint256 _reentryMaxExitToProcess) public { 14 | exitGameController = _controller; 15 | vaultId = _vaultId; 16 | testToken = _token; 17 | reentryMaxExitToProcess = _reentryMaxExitToProcess; 18 | } 19 | 20 | // override ExitProcessor interface 21 | // This would call the processExits back to mimic reentrancy attack 22 | function processExit(uint168, uint256, address, address payable) public { 23 | exitGameController.processExits(vaultId, testToken, 0, reentryMaxExitToProcess, keccak256(abi.encodePacked(msg.sender))); 24 | } 25 | 26 | function enqueue(uint256 _vaultId, address _token, uint32 _exitableAt, uint256 _txPos, uint168 _exitId, IExitProcessor _exitProcessor) 27 | public 28 | { 29 | exitGameController.enqueue(_vaultId, _token, _exitableAt, PosLib.decode(_txPos), _exitId, _exitProcessor); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/exits/utils/OutputId.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | library OutputId { 4 | /** 5 | * @notice Computes the output ID for a deposit tx 6 | * @dev Deposit tx bytes might not be unique because all inputs are empty 7 | * Two deposits with the same output value would result in the same tx bytes 8 | * As a result, we need to hash with utxoPos to ensure uniqueness 9 | * @param _txBytes Transaction bytes 10 | * @param _outputIndex Output index of the output 11 | * @param _utxoPosValue (Optional) UTXO position of the deposit output 12 | */ 13 | function computeDepositOutputId(bytes memory _txBytes, uint256 _outputIndex, uint256 _utxoPosValue) 14 | internal 15 | pure 16 | returns(bytes32) 17 | { 18 | return keccak256(abi.encodePacked(_txBytes, _outputIndex, _utxoPosValue)); 19 | } 20 | 21 | /** 22 | * @notice Computes the output ID for normal (non-deposit) tx 23 | * @dev Since txBytes for non-deposit tx is unique, directly hash the txBytes with outputIndex 24 | * @param _txBytes Transaction bytes 25 | * @param _outputIndex Output index of the output 26 | */ 27 | function computeNormalOutputId(bytes memory _txBytes, uint256 _outputIndex) 28 | internal 29 | pure 30 | returns(bytes32) 31 | { 32 | return keccak256(abi.encodePacked(_txBytes, _outputIndex)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/tests/utils/test_eip712_vectors.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from plasma_core.constants import NULL_ADDRESS 4 | from plasma_core.transaction import Transaction 5 | from plasma_core.utils.eip712_struct_hash import hash_struct 6 | 7 | 8 | verifyingContract = '44de0ec539b8c4a4b530c78620fe8320167f2f74' 9 | 10 | owner = bytes.fromhex('2258a5279850f6fb78888a7e45ea2a5eb1b3c436') 11 | token = bytes.fromhex('0123456789abcdef000000000000000000000000') 12 | 13 | metadata = bytes.fromhex('853a8d8af99c93405a791b97d57e819e538b06ffaa32ad70da2582500bc18d43') 14 | 15 | inputs = [ 16 | (1, 0, 0), 17 | (1000, 2, 3), 18 | (101000, 1337, 3) 19 | ] 20 | outputs = [ 21 | (owner, NULL_ADDRESS, 100), 22 | (token, NULL_ADDRESS, 111), 23 | (owner, token, 1337) 24 | ] 25 | 26 | 27 | @pytest.fixture 28 | def hash_lib_wrapper(get_contract): 29 | return get_contract("PaymentEip712LibMock") 30 | 31 | 32 | def test_sample_transaction(hash_lib_wrapper): 33 | tx = Transaction(inputs=inputs, outputs=outputs) 34 | assert hash_struct(tx, verifying_contract=verifyingContract) == hash_lib_wrapper.hashTx(bytes.fromhex(verifyingContract), tx.encoded) 35 | 36 | 37 | def test_transaction_with_metadata(hash_lib_wrapper): 38 | tx = Transaction(inputs=inputs, outputs=outputs, metadata=metadata) 39 | assert hash_struct(tx, verifying_contract=verifyingContract) == hash_lib_wrapper.hashTx(bytes.fromhex(verifyingContract), tx.encoded) 40 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/framework/utils/Quarantine.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | /** 4 | * @notice Provides a way to quarantine (disable) contracts for a specified period of time 5 | * @dev The immunitiesRemaining member allows deployment to the platform with some 6 | * pre-verified contracts that don't get quarantined 7 | */ 8 | library Quarantine { 9 | struct Data { 10 | mapping(address => uint256) store; 11 | uint256 quarantinePeriod; 12 | uint256 immunitiesRemaining; 13 | } 14 | 15 | /** 16 | * @notice Checks whether a contract is quarantined 17 | */ 18 | function isQuarantined(Data storage _self, address _contractAddress) internal view returns (bool) { 19 | return block.timestamp < _self.store[_contractAddress]; 20 | } 21 | 22 | /** 23 | * @notice Places a contract into quarantine 24 | * @param _contractAddress The address of the contract 25 | */ 26 | function quarantine(Data storage _self, address _contractAddress) internal { 27 | require(_contractAddress != address(0), "An empty address cannot be quarantined"); 28 | require(_self.store[_contractAddress] == 0, "The contract is already quarantined"); 29 | 30 | if (_self.immunitiesRemaining == 0) { 31 | _self.store[_contractAddress] = block.timestamp + _self.quarantinePeriod; 32 | } else { 33 | _self.immunitiesRemaining--; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /plasma_framework/test/src/utils/FailFastReentrancyGuard.test.js: -------------------------------------------------------------------------------- 1 | const { expectEvent, expectRevert } = require('openzeppelin-test-helpers'); 2 | 3 | const Attacker = artifacts.require('FailFastReentrancyGuardAttacker'); 4 | const SpyPlasmaFramework = artifacts.require('SpyPlasmaFrameworkForExitGame'); 5 | 6 | const { PROTOCOL, TX_TYPE } = require('../../helpers/constants.js'); 7 | 8 | contract('FailFastReentrancyGuard', () => { 9 | const MIN_EXIT_PERIOD = 1; 10 | const DUMMY_INITIAL_IMMUNE_VAULTS_NUM = 0; 11 | const INITIAL_IMMUNE_EXIT_GAME_NUM = 1; 12 | 13 | beforeEach(async () => { 14 | const framework = await SpyPlasmaFramework.new( 15 | MIN_EXIT_PERIOD, DUMMY_INITIAL_IMMUNE_VAULTS_NUM, INITIAL_IMMUNE_EXIT_GAME_NUM, 16 | ); 17 | this.attacker = await Attacker.new(framework.address); 18 | await framework.registerExitGame(TX_TYPE.PAYMENT, this.attacker.address, PROTOCOL.MORE_VP); 19 | }); 20 | 21 | it('should not allow local recursion', async () => { 22 | await expectRevert( 23 | this.attacker.guardedLocal(), 24 | 'Reentrant call', 25 | ); 26 | }); 27 | 28 | it('should not allow remote reentrancy but should not fail the top call', async () => { 29 | const { receipt } = await this.attacker.guardedRemote(); 30 | await expectEvent.inTransaction( 31 | receipt.transactionHash, 32 | Attacker, 33 | 'RemoteCallFailed', 34 | ); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /plasma_framework/docs/deploying/deploying.md: -------------------------------------------------------------------------------- 1 | # Deploying the contracts with Truffle 2 | 3 | ## Migration scripts 4 | Truffle migration scripts run in the order of the numbered prefix of the filename. The naming convention is as follows: 5 | 6 | - 1-98: Initial setup and main contracts like PlasmaFramework, SpendingConditionRegistry, etc. 7 | - 99: Renounce the SpendingConditionRegistry because this has to happen before any ExitGames 8 | - 100: First ExitGame contracts 9 | - 200: Second ExitGame contracts 10 | - 300: payment V2 experiment contracts 11 | - 1000: Cleanup, dev/test contracts and output 12 | - 2000: Future extensions, new exit games, etc. 13 | 14 | ## ENV VAR settings 15 | - DEPLOYER_PRIVATEKEY: Private key of the deployer. Required when deploying with `--network remote`. 16 | - MAINTAINER_PRIVATEKEY: Private key of the maintainer. Required when deploying with `--network remote`. 17 | - AUTHORITY_PRIVATEKEY: Private key of the authority. Required when deploying with `--network remote`. 18 | - REMOTE_URL: The url for the remote client to accept the call to Ethereum. eg. `https://rinkeby.infura.io/v3/${INFURA_API_TOKEN}`. 19 | - GAS_PRICE: The gas price (in wei) used to deploy the contracts. If not set, default to 20 Gwei. 20 | - TENDERLY: set to `true` when you need to push the contract to tenderly. Default not pushing to tenderly. 21 | - DEPLOY_TEST_CONTRACTS: set to `true` when you need to deploy some testing contracts like mock, wrapper for conformance testing. By default test contracts are not deployed. 22 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/requirements.txt: -------------------------------------------------------------------------------- 1 | apipkg==1.5 2 | astroid==2.3.2 3 | atomicwrites==1.3.0 4 | attrdict==2.0.1 5 | attrs==19.3.0 6 | base58==1.0.3 7 | certifi==2019.9.11 8 | chardet==3.0.4 9 | cytoolz==0.10.0 10 | eip712-structs==1.1.0 11 | entrypoints==0.3 12 | eth-abi==2.0.0 13 | eth-account==0.4.0 14 | eth-hash==0.2.0 15 | eth-keyfile==0.5.1 16 | eth-keys==0.2.4 17 | eth-rlp==0.1.2 18 | eth-tester==0.2.0b2 19 | eth-typing==2.1.0 20 | eth-utils==1.7.0 21 | execnet==1.7.1 22 | flake8==3.7.8 23 | hexbytes==0.2.0 24 | idna==2.8 25 | importlib-metadata==0.23 26 | ipfshttpclient==0.4.12 27 | isort==4.3.21 28 | jsonschema==2.6.0 29 | lazy-object-proxy==1.4.2 30 | lru-dict==1.1.6 31 | mccabe==0.6.1 32 | more-itertools==7.2.0 33 | multiaddr==0.0.8 34 | netaddr==0.7.19 35 | packaging==19.2 36 | parsimonious==0.8.1 37 | -e git+git@github.com:omisego/py-solc-simple.git@f84477e8a82f2bb548ad814f383f5be14ba1d086#egg=py-solc-simple 38 | pluggy==0.13.0 39 | protobuf==3.10.0 40 | psutil==5.6.6 41 | py==1.8.0 42 | py-solc-x==0.4.0 43 | pycodestyle==2.5.0 44 | pycryptodome==3.9.0 45 | pyflakes==2.1.1 46 | pylint==2.4.3 47 | pyparsing==2.4.2 48 | pysha3==1.0.2 49 | pytest==5.2.1 50 | pytest-cache==1.0 51 | pytest-forked==1.1.1 52 | pytest-xdist==1.30.0 53 | pytest-xprocess==0.12.1 54 | requests==2.22.0 55 | rlp==1.1.0 56 | semantic-version==2.8.2 57 | six==1.12.0 58 | toolz==0.10.0 59 | typed-ast==1.4.0 60 | urllib3==1.25.6 61 | varint==1.0.2 62 | wcwidth==0.1.7 63 | web3==5.0.0 64 | websockets==7.0 65 | wrapt==1.11.2 66 | zipp==0.6.0 67 | -------------------------------------------------------------------------------- /plasma_framework/migrations/61_spending_conditions_fee.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const FeeClaimOutputToPaymentTxCondition = artifacts.require('FeeClaimOutputToPaymentTxCondition'); 4 | const PlasmaFramework = artifacts.require('PlasmaFramework'); 5 | const SpendingConditionRegistry = artifacts.require('SpendingConditionRegistry'); 6 | 7 | const config = require('../config.js'); 8 | 9 | module.exports = async ( 10 | deployer, 11 | _, 12 | // eslint-disable-next-line no-unused-vars 13 | [deployerAddress, maintainerAddress, authorityAddress], 14 | ) => { 15 | const FEE_CLAIM_OUTPUT_TYPE = config.registerKeys.outputTypes.feeClaim; 16 | const PAYMENT_TX_TYPE = config.registerKeys.txTypes.payment; 17 | const FEE_TX_TYPE = config.registerKeys.txTypes.fee; 18 | 19 | const spendingConditionRegistry = await SpendingConditionRegistry.deployed(); 20 | const plasmaFramework = await PlasmaFramework.deployed(); 21 | 22 | await deployer.deploy( 23 | FeeClaimOutputToPaymentTxCondition, 24 | plasmaFramework.address, 25 | FEE_TX_TYPE, 26 | FEE_CLAIM_OUTPUT_TYPE, 27 | PAYMENT_TX_TYPE, 28 | ); 29 | const feeClaimOutputToPaymentTxCondition = await FeeClaimOutputToPaymentTxCondition.deployed(); 30 | console.log(`Registering feeClaimOutputToPaymentTxCondition (${feeClaimOutputToPaymentTxCondition.address}) to spendingConditionRegistry`); 31 | await spendingConditionRegistry.registerSpendingCondition( 32 | FEE_CLAIM_OUTPUT_TYPE, PAYMENT_TX_TYPE, feeClaimOutputToPaymentTxCondition.address, 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /plasma_framework/test/src/framework/Protocol.test.js: -------------------------------------------------------------------------------- 1 | const Protocol = artifacts.require('ProtocolWrapper'); 2 | 3 | const { BN } = require('openzeppelin-test-helpers'); 4 | const { expect } = require('chai'); 5 | 6 | const { PROTOCOL } = require('../../helpers/constants.js'); 7 | 8 | contract('Protocol', () => { 9 | before(async () => { 10 | this.test = await Protocol.new(); 11 | }); 12 | 13 | describe('MORE_VP()', () => { 14 | it('should return protocol value of MoreVP', async () => { 15 | expect(await this.test.MORE_VP()).to.be.bignumber.equal(new BN(PROTOCOL.MORE_VP)); 16 | }); 17 | }); 18 | 19 | describe('MVP()', () => { 20 | it('should return protocol value of MVP', async () => { 21 | expect(await this.test.MVP()).to.be.bignumber.equal(new BN(PROTOCOL.MVP)); 22 | }); 23 | }); 24 | 25 | describe('isValidProtocol', () => { 26 | it('should return true for MVP protocol', async () => { 27 | expect(await this.test.isValidProtocol(PROTOCOL.MVP)).to.be.true; 28 | }); 29 | 30 | it('should return true for MoreVP protocol', async () => { 31 | expect(await this.test.isValidProtocol(PROTOCOL.MORE_VP)).to.be.true; 32 | }); 33 | 34 | it('should return false for invalid protocol', async () => { 35 | const invalidProtocolValues = [0, 3, 255]; 36 | await Promise.all(invalidProtocolValues.map(async (protocol) => { 37 | expect(await this.test.isValidProtocol(protocol)).to.be.false; 38 | })); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /plasma_framework/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'extends': 'airbnb-base', 3 | 'env': { 4 | mocha: true, 5 | }, 6 | 'rules': { 7 | // Four space indent 8 | 'indent': ['error', 4], 9 | 10 | // Allow unused variables if they're called '_' 11 | 'no-unused-vars': ['error', { 12 | argsIgnorePattern: '_', 13 | varsIgnorePattern: '_', 14 | }], 15 | 16 | // Function hoisting is ok 17 | 'no-use-before-define': ['error', { functions: false }], 18 | 19 | // Allow unary increment and decrement operators 20 | 'no-plusplus': ['off'], 21 | 22 | // Longer max line length 23 | 'max-len': ['error', 120, 2, { 24 | ignoreUrls: true, 25 | ignoreComments: false, 26 | ignoreRegExpLiterals: true, 27 | ignoreStrings: true, 28 | ignoreTemplateLiterals: true, 29 | }], 30 | 31 | // Ignore warning around test helper codes using dev dependency 32 | 'import/no-extraneous-dependencies': [ 33 | true, 34 | {'packageDir': ['./test/']} 35 | ] 36 | }, 37 | 38 | // Define some truffle globals 39 | 'globals': { 40 | web3: false, 41 | contract: false, 42 | artifacts: false, 43 | }, 44 | 45 | 'overrides': [ 46 | { 47 | // Allow chai.expect expressions in test files, e.g. `expect(foo).to.be.true` 48 | files: ['*.test.js'], 49 | rules: { 50 | 'no-unused-expressions': 'off', 51 | }, 52 | }, 53 | ], 54 | }; 55 | -------------------------------------------------------------------------------- /plasma_framework/config.js: -------------------------------------------------------------------------------- 1 | const clonedeep = require('lodash.clonedeep'); 2 | 3 | const env = process.env.DEPLOYMENT_ENV || 'development'; 4 | 5 | const development = { 6 | frameworks: { 7 | minExitPeriod: process.env.MIN_EXIT_PERIOD || 60 * 10, // The minimum exit period for testing is 10 minutes. 8 | // Allow 2 vaults (ETH and ERC20) to be used without going through quarantine. 9 | initialImmuneVaults: 2, 10 | // Allow 2 exit games (PaymentExitGame, FeeExitGame) to be used without going through quarantine. 11 | initialImmuneExitGames: 2, 12 | protocols: { 13 | mvp: 1, 14 | moreVp: 2, 15 | }, 16 | // Defines how much gas should be considered safe when transferring ETH 17 | // Under version control so that it can be upgraded to reflect Ethereum network changes, 18 | // while keeping a record of the previous gas stipend. 19 | safeGasStipend: { 20 | v1: 2300, 21 | }, 22 | }, 23 | registerKeys: { 24 | txTypes: { 25 | payment: 1, 26 | paymentV2: 2, 27 | fee: 3, 28 | }, 29 | outputTypes: { 30 | payment: 1, 31 | feeClaim: 2, 32 | }, 33 | vaultId: { 34 | eth: 1, 35 | erc20: 2, 36 | }, 37 | }, 38 | }; 39 | 40 | const production = clonedeep(development); 41 | production.frameworks.minExitPeriod = 60 * 60 * 24 * 7; // The minimum exit period in production is 1 week. 42 | 43 | const config = { 44 | development, 45 | production, 46 | }; 47 | 48 | module.exports = config[env]; 49 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from os import path 3 | 4 | here = path.abspath(path.dirname(__file__)) 5 | 6 | with open(path.join(here, '../../README.md'), encoding='utf-8') as f: 7 | long_description = f.read() 8 | 9 | setup( 10 | name='plasma-contracts', 11 | version='0.0.1', 12 | description='Plasma root chain contracts', 13 | long_description=long_description, 14 | long_description_content_type='text/markdown', 15 | url='https://github.com/omisego/plasma-contracts', 16 | author='OmiseGO', 17 | classifiers=[ 18 | 'Development Status :: 3 - Alpha', 19 | 'Intended Audience :: Developers', 20 | 'Topic :: Software Development :: Libraries :: Python Modules', 21 | 'License :: OSI Approved :: Apache Software License', # Apache License, Version 2.0 (Apache-2.0) 22 | 'Programming Language :: Python :: 3' 23 | ], 24 | keywords='plasma contracts ethereum development solidity', 25 | packages=find_packages(exclude=['contrib', 'docs', 'tests']), 26 | install_requires=[ 27 | 'rlp==1.1.0', 28 | 'py-solc-simple@git+https://github.com/omisego/py-solc-simple@plasma_contracts_tmp_compilation', 29 | 'py-eth-sig-utils@git+https://github.com/omgnetwork/py-eth-sig-utils', 30 | 'web3==5.0.0', 31 | 'eip712-structs==1.1.0', 32 | 'eth_tester==0.2.0b2' 33 | ], 34 | extras_require={ 35 | 'dev': [ 36 | 'pytest>=4.6.0', 37 | 'pylint>=2.3.0', 38 | 'flake8>=3.7.0', 39 | 'pytest-xprocess>=0.12.0', 40 | 'pytest-xdist>=1.29.0' 41 | ] 42 | } 43 | ) 44 | -------------------------------------------------------------------------------- /plasma_framework/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plasma_framework", 3 | "version": "2.0.0", 4 | "description": "Plasma Framework", 5 | "directories": { 6 | "test": "test" 7 | }, 8 | "dependencies": { 9 | "@truffle/hdwallet-provider": "^1.1.1", 10 | "axios": "^0.21.1", 11 | "dotenv": "^8.0.0", 12 | "elliptic": ">=6.5.4", 13 | "lodash.clonedeep": "^4.5.0", 14 | "openzeppelin-solidity": "2.3.0", 15 | "solidoc": "^1.0.5" 16 | }, 17 | "devDependencies": { 18 | "@codechecks/client": "^0.1.10", 19 | "chai": "^4.2.0", 20 | "coveralls": "^3.0.6", 21 | "elliptic": ">=6.5.4", 22 | "eslint": "^5.3.0", 23 | "eslint-config-airbnb-base": "^13.1.0", 24 | "eslint-plugin-import": "^2.17.3", 25 | "eth-gas-reporter": "^0.2.17", 26 | "eth-sig-util": "^2.2.0", 27 | "ethereumjs-util": "^6.1.0", 28 | "openzeppelin-test-helpers": "^0.4.3", 29 | "rlp": "^2.2.3", 30 | "run-with-testrpc": "^0.3.1", 31 | "seedrandom": "^3.0.5", 32 | "solhint": "^2.2.0", 33 | "solidity-coverage": "^0.7.12", 34 | "truffle": "^5.1.48" 35 | }, 36 | "scripts": { 37 | "test": "truffle test", 38 | "coverage": "node_modules/.bin/truffle --max-old-space-size=4096 run coverage", 39 | "docgen": "./scripts/docgen.sh", 40 | "linter": "node_modules/eslint/bin/eslint.js . --fix", 41 | "linter-sol": "node_modules/.bin/solhint --max-warnings 0 \"contracts/**/*.sol\"" 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "git+https://github.com/omisego/plasma-contracts.git" 46 | }, 47 | "license": "Apache-2.0", 48 | "bugs": { 49 | "url": "https://github.com/omisego/plasma-contracts/issues" 50 | }, 51 | "homepage": "https://github.com/omisego/plasma-contracts#readme" 52 | } 53 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/python_tests_wrappers/PriorityQueueTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | import "../../src/framework/utils/PriorityQueue.sol"; 3 | 4 | /** 5 | * @title PriorityQueue 6 | * @dev Min-heap priority queue implementation. 7 | */ 8 | contract PriorityQueueTest{ 9 | 10 | /* 11 | * Events 12 | */ 13 | 14 | event DelMin(uint256 val); 15 | 16 | /* 17 | * Storage 18 | */ 19 | 20 | PriorityQueue public queue; 21 | /* 22 | * Public functions 23 | */ 24 | 25 | constructor() 26 | public 27 | { 28 | queue = new PriorityQueue(); 29 | } 30 | 31 | /** 32 | * @dev Inserts an element into the queue. Does not perform deduplication. 33 | */ 34 | function insert(uint256 _element) 35 | public 36 | { 37 | queue.insert(_element); 38 | } 39 | 40 | /** 41 | * @dev Overrides the default implementation, by simply emitting an even on deletion, so that the result is testable. 42 | * @return The smallest element in the priority queue. 43 | */ 44 | function delMin() 45 | public 46 | returns (uint256 value) 47 | { 48 | value = queue.delMin(); 49 | emit DelMin(value); 50 | } 51 | 52 | /* 53 | * Read-only functions 54 | */ 55 | /** 56 | * @dev Returns the top element of the heap. 57 | * @return The smallest element in the priority queue. 58 | */ 59 | function getMin() 60 | public 61 | view 62 | returns (uint256) 63 | { 64 | return queue.getMin(); 65 | } 66 | 67 | function currentSize() 68 | external 69 | view 70 | returns (uint256) 71 | { 72 | return queue.currentSize(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /plasma_framework/test/src/exits/utils/ExitableTimestamp.test.js: -------------------------------------------------------------------------------- 1 | const ExitableTimestamp = artifacts.require('ExitableTimestampWrapper'); 2 | 3 | const { BN, time } = require('openzeppelin-test-helpers'); 4 | const { expect } = require('chai'); 5 | 6 | contract('ExitableTimestamp', () => { 7 | const MIN_EXIT_PERIOD = 60 * 60 * 24 * 7; // 1 week 8 | 9 | before('setup', async () => { 10 | this.contract = await ExitableTimestamp.new(MIN_EXIT_PERIOD); 11 | }); 12 | 13 | describe('calculate', () => { 14 | it('should get the correct exit timestamp for deposit tx', async () => { 15 | const latestTimestamp = await time.latest(); 16 | expect(await this.contract.calculateDepositTxOutputExitableTimestamp(latestTimestamp)) 17 | .to.be.bignumber.equal(latestTimestamp.add(new BN(MIN_EXIT_PERIOD))); 18 | }); 19 | 20 | it('should get the correct exit timestamp for non deposit tx whose age is older than MIN_EXIT_PERIOD', async () => { 21 | const latestTimestamp = await time.latest(); 22 | const oldTimestamp = 0; 23 | expect(await this.contract.calculateTxExitableTimestamp(latestTimestamp, oldTimestamp)) 24 | .to.be.bignumber.equal(latestTimestamp.add(new BN(MIN_EXIT_PERIOD))); 25 | }); 26 | 27 | it('should get the correct exit timestamp for non deposit tx whose age is younger than MIN_EXIT_PERIOD', async () => { 28 | const latestTimestamp = await time.latest(); 29 | const youngTimestamp = latestTimestamp - 15; 30 | expect(await this.contract.calculateTxExitableTimestamp(latestTimestamp, youngTimestamp)) 31 | .to.be.bignumber.equal(new BN(youngTimestamp + 2 * MIN_EXIT_PERIOD)); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/vaults/verifiers/EthDepositVerifier.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "./IEthDepositVerifier.sol"; 4 | import {PaymentTransactionModel as DepositTx} from "../../transactions/PaymentTransactionModel.sol"; 5 | 6 | /** 7 | * @notice Implementation of ETH deposit verifier using payment transaction as the deposit transaction 8 | */ 9 | contract EthDepositVerifier is IEthDepositVerifier { 10 | uint256 public depositTxType; 11 | uint256 public supportedOutputType; 12 | 13 | constructor(uint256 txType, uint256 outputType) public { 14 | depositTxType = txType; 15 | supportedOutputType = outputType; 16 | } 17 | 18 | /** 19 | * @notice Overrides the function of IEthDepositVerifier and implements the verification logic 20 | * for payment transaction 21 | */ 22 | function verify(bytes calldata depositTx, uint256 amount, address sender) external view { 23 | DepositTx.Transaction memory decodedTx = DepositTx.decode(depositTx); 24 | 25 | require(decodedTx.txType == depositTxType, "Invalid transaction type"); 26 | 27 | require(decodedTx.inputs.length == 0, "Deposit must have no inputs"); 28 | 29 | require(decodedTx.outputs.length == 1, "Deposit must have exactly one output"); 30 | require(decodedTx.outputs[0].amount == amount, "Deposited value must match sent amount"); 31 | require(decodedTx.outputs[0].token == address(0), "Output requires correct currency (ETH)"); 32 | require(decodedTx.outputs[0].outputType == supportedOutputType, "Invalid output type"); 33 | 34 | address depositorsAddress = DepositTx.getOutputOwner(decodedTx.outputs[0]); 35 | require(depositorsAddress == sender, "Depositor's address must match sender's address"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/plasma_core/block.py: -------------------------------------------------------------------------------- 1 | import rlp 2 | from rlp.sedes import CountableList, big_endian_int 3 | from eth_utils import keccak 4 | from plasma_core.utils.merkle.fixed_merkle import FixedMerkle 5 | from plasma_core.transaction import Transaction 6 | from plasma_core.constants import NULL_SIGNATURE 7 | 8 | 9 | class Block(rlp.Serializable): 10 | 11 | fields = ( 12 | ('transactions', CountableList(Transaction)), 13 | ('number', big_endian_int), 14 | ) 15 | 16 | def __init__(self, transactions=None, number=0): 17 | if transactions is None: 18 | transactions = [] 19 | super().__init__(transactions, number) 20 | 21 | @property 22 | def hash(self): 23 | return keccak(self.encoded) 24 | 25 | @property 26 | def merklized_transaction_set(self): 27 | encoded_transactions = [tx.encoded for tx in self.transactions] 28 | return FixedMerkle(16, encoded_transactions) 29 | 30 | @property 31 | def root(self): 32 | return self.merklized_transaction_set.root 33 | 34 | @property 35 | def encoded(self): 36 | return rlp.encode(self) 37 | 38 | @property 39 | def is_deposit_block(self): 40 | return len(self.transactions) == 1 and self.transactions[0].is_deposit 41 | 42 | def sign(self, key): 43 | return SignedBlock(self, key.sign_msg_hash(self.hash)) 44 | 45 | 46 | class SignedBlock(Block): 47 | 48 | def __init__(self, block, signature=NULL_SIGNATURE): 49 | super().__init__(block.transactions, block.number) 50 | self._signature = signature 51 | 52 | @property 53 | def signature(self): 54 | return self._signature.to_bytes() 55 | 56 | @property 57 | def signer(self): 58 | return self._signature.recover_public_key_from_msg_hash(self.hash).to_checksum_address() 59 | -------------------------------------------------------------------------------- /.github/workflows/sync-from-public-to-private.yml: -------------------------------------------------------------------------------- 1 | name: Sync with plasma-contracts 2 | 3 | on: 4 | repository_dispatch: 5 | types: [sync-from-public] 6 | 7 | jobs: 8 | sync-public-repo: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | # use personal token to continue GH action workflows 14 | # by default GH would not trigger another workflow if it is from GH action token 15 | # see: https://github.com/ad-m/github-push-action/issues/32 16 | token: ${{ secrets.HOUSE_KEEPER_BOT_TOKEN }} 17 | - name: Event Information 18 | run: | 19 | echo "Syncing with sha '${{ github.event.client_payload.sha }}'" 20 | - name: Sync master branch in between public and private repos 21 | if: github.repository == 'omgnetwork/plasma-contracts-private' 22 | run: | 23 | set -e 24 | set -o xtrace 25 | 26 | # Credit repo-sync/github-sync for the script used in here 27 | # https://github.com/repo-sync/github-sync/blob/520596e97177727db1f2a1de14f4ded905624066/github-sync.sh#L23-L33 28 | 29 | readonly SOURCE_REPO="omgnetwork/plasma-contracts" 30 | readonly SOURCE_BRANCH="v2.0.0" 31 | readonly DESTINATION_BRANCH="master" 32 | 33 | echo "Syncing ${DESTINATION_BRANCH} from branch ${SOURCE_BRANCH} of ${SOURCE_REPO}..." 34 | 35 | git remote set-url origin "https://$GITHUB_ACTOR:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY" 36 | git remote add tmp_upstream "https://github.com/${SOURCE_REPO}.git" 37 | git fetch tmp_upstream 38 | git remote --verbose 39 | git push origin "refs/remotes/tmp_upstream/${SOURCE_BRANCH}:refs/heads/${DESTINATION_BRANCH}" --force 40 | git remote rm tmp_upstream 41 | git remote --verbose 42 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/exits/utils/ExitableTimestamp.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "openzeppelin-solidity/contracts/math/Math.sol"; 4 | 5 | library ExitableTimestamp { 6 | struct Calculator { 7 | uint256 minExitPeriod; 8 | } 9 | 10 | /** 11 | * @notice Calculates the exitable timestamp for a mined transaction 12 | * @dev This is the main function when asking for exitable timestamp in most cases. 13 | * The only exception is to calculate the exitable timestamp for a deposit output in standard exit. 14 | * Should use the function 'calculateDepositTxOutputExitableTimestamp' for that case. 15 | */ 16 | function calculateTxExitableTimestamp( 17 | Calculator memory _calculator, 18 | uint256 _now, 19 | uint256 _blockTimestamp 20 | ) 21 | internal 22 | pure 23 | returns (uint32) 24 | { 25 | return uint32(Math.max(_blockTimestamp + (_calculator.minExitPeriod * 2), _now + _calculator.minExitPeriod)); 26 | } 27 | 28 | /** 29 | * @notice Calculates the exitable timestamp for deposit transaction output for standard exit 30 | * @dev This function should only be used in standard exit for calculating exitable timestamp of a deposit output. 31 | * For in-fight exit, the priority of a input tx which is a deposit tx should still be using the another function 'calculateTxExitableTimestamp'. 32 | * See discussion here: https://git.io/Je4N5 33 | * Reason of deposit output has different exitable timestamp: https://git.io/JecCV 34 | */ 35 | function calculateDepositTxOutputExitableTimestamp( 36 | Calculator memory _calculator, 37 | uint256 _now 38 | ) 39 | internal 40 | pure 41 | returns (uint32) 42 | { 43 | return uint32(_now + _calculator.minExitPeriod); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/vaults/NonCompliantERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 4 | 5 | // A 'NonCompliantERC20' token is one that uses an old version of the ERC20 standard, 6 | // as described here https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca 7 | // Basically, this version does not return anything from `transfer` and `transferFrom`, 8 | // whereas most modern implementions of ERC20 return a boolean to indicate success or failure. 9 | contract NonCompliantERC20 { 10 | using SafeMath for uint256; 11 | 12 | mapping (address => uint256) private balances; 13 | mapping (address => mapping (address => uint256)) private allowances; 14 | uint256 private totalSupply; 15 | 16 | constructor(uint256 _initialAmount) public { 17 | balances[msg.sender] = _initialAmount; 18 | totalSupply = _initialAmount; 19 | } 20 | 21 | function balanceOf(address _account) public view returns (uint256) { 22 | return balances[_account]; 23 | } 24 | 25 | function transfer(address _to, uint _value) public { 26 | balances[msg.sender] = balances[msg.sender].sub(_value); 27 | balances[_to] = balances[_to].add(_value); 28 | } 29 | 30 | function transferFrom(address _from, address _to, uint _value) public { 31 | uint256 _allowance = allowances[_from][msg.sender]; 32 | 33 | balances[_to] = balances[_to].add(_value); 34 | balances[_from] = balances[_from].sub(_value); 35 | allowances[_from][msg.sender] = _allowance.sub(_value); 36 | } 37 | 38 | function approve(address _spender, uint _value) public { 39 | allowances[msg.sender][_spender] = _value; 40 | } 41 | 42 | function allowance(address _owner, address _spender) public view returns (uint256 remaining) { 43 | return allowances[_owner][_spender]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /plasma_framework/test/src/framework/PlasmaFramework.test.js: -------------------------------------------------------------------------------- 1 | const PlasmaFramework = artifacts.require('PlasmaFramework'); 2 | 3 | const { BN } = require('openzeppelin-test-helpers'); 4 | const { expect } = require('chai'); 5 | const { expectRevert } = require('openzeppelin-test-helpers'); 6 | const childProcess = require('child_process'); 7 | 8 | contract('PlasmaFramework', ([authority, maintainer, alice]) => { 9 | const INITIAL_IMMUNE_VAULTS = 1; 10 | const INITIAL_IMMUNE_EXIT_GAMES = 1; 11 | const TEST_MIN_EXIT_PERIOD = 1000; 12 | 13 | describe('constructor', () => { 14 | beforeEach(async () => { 15 | this.framework = await PlasmaFramework.new( 16 | TEST_MIN_EXIT_PERIOD, 17 | INITIAL_IMMUNE_VAULTS, 18 | INITIAL_IMMUNE_EXIT_GAMES, 19 | authority, 20 | maintainer, 21 | ); 22 | }); 23 | 24 | it('should set the min exit period', async () => { 25 | expect(await this.framework.minExitPeriod()) 26 | .to.be.bignumber.equal(new BN(TEST_MIN_EXIT_PERIOD)); 27 | }); 28 | 29 | it('should set maintainer address', async () => { 30 | expect(await this.framework.getMaintainer()).to.equal(maintainer); 31 | }); 32 | 33 | it('should set semver string', async () => { 34 | const sha = childProcess.execSync('git rev-parse HEAD').toString().trim() 35 | .substring(0, 7); 36 | await this.framework.setVersion(`1.0.1+${sha}`, { from: maintainer }); 37 | expect(await this.framework.getVersion()).to.equal(`1.0.1+${sha}`); 38 | }); 39 | 40 | it('should fail when semver not set by maintainer', async () => { 41 | await expectRevert( 42 | this.framework.setVersion('yolo', { from: alice }), 43 | 'Caller address is unauthorized', 44 | ); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /plasma_framework/test/helpers/utils.js: -------------------------------------------------------------------------------- 1 | const { Position } = require('./positions.js'); 2 | const { CHILD_BLOCK_INTERVAL } = require('./constants.js'); 3 | 4 | async function spentOnGas(receipt) { 5 | const tx = await web3.eth.getTransaction(receipt.transactionHash); 6 | return web3.utils.toBN(tx.gasPrice).muln(receipt.gasUsed); 7 | } 8 | 9 | function buildOutputGuard(outputGuardPreimage) { 10 | const hashValue = web3.utils.soliditySha3( 11 | { t: 'bytes', v: outputGuardPreimage }, 12 | ); 13 | const rightMostBytes20 = hashValue.substring(hashValue.length - 40, hashValue.length); 14 | return `0x${rightMostBytes20}`; 15 | } 16 | 17 | function computeDepositOutputId(txBytes, outputIndex, utxoPos) { 18 | return web3.utils.soliditySha3( 19 | { t: 'bytes', v: txBytes }, 20 | { t: 'uint256', v: outputIndex }, 21 | { t: 'uint256', v: utxoPos }, 22 | ); 23 | } 24 | 25 | function computeNormalOutputId(txBytes, outputIndex) { 26 | return web3.utils.soliditySha3( 27 | { t: 'bytes', v: txBytes }, 28 | { t: 'uint256', v: outputIndex }, 29 | ); 30 | } 31 | 32 | function getOutputId(txBytes, utxoPos) { 33 | const inputUtxoPos = new Position(utxoPos); 34 | const outputId = isDeposit(inputUtxoPos.blockNum) 35 | ? computeDepositOutputId(txBytes, inputUtxoPos.outputIndex, inputUtxoPos.utxoPos) 36 | : computeNormalOutputId(txBytes, inputUtxoPos.outputIndex); 37 | return outputId; 38 | } 39 | 40 | function isDeposit(blockNum) { 41 | return blockNum % CHILD_BLOCK_INTERVAL !== 0; 42 | } 43 | 44 | function exitQueueKey(vaultId, token) { 45 | return web3.utils.soliditySha3( 46 | { t: 'uint256', v: vaultId }, 47 | { t: 'address', v: token }, 48 | ); 49 | } 50 | 51 | module.exports = { 52 | spentOnGas, 53 | buildOutputGuard, 54 | computeDepositOutputId, 55 | computeNormalOutputId, 56 | getOutputId, 57 | isDeposit, 58 | exitQueueKey, 59 | }; 60 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/utils/Bits.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | /** 4 | * @title Bits 5 | * @dev Operations on individual bits of a word 6 | */ 7 | library Bits { 8 | /* 9 | * Storage 10 | */ 11 | 12 | uint constant internal ONE = uint(1); 13 | 14 | /* 15 | * Internal functions 16 | */ 17 | /** 18 | * @dev Sets the bit at the given '_index' in '_self' to '1' 19 | * @param _self Uint to modify 20 | * @param _index Index of the bit to set 21 | * @return The modified value 22 | */ 23 | function setBit(uint _self, uint8 _index) 24 | internal 25 | pure 26 | returns (uint) 27 | { 28 | return _self | ONE << _index; 29 | } 30 | 31 | /** 32 | * @dev Sets the bit at the given '_index' in '_self' to '0' 33 | * @param _self Uint to modify 34 | * @param _index Index of the bit to set 35 | * @return The modified value 36 | */ 37 | function clearBit(uint _self, uint8 _index) 38 | internal 39 | pure 40 | returns (uint) 41 | { 42 | return _self & ~(ONE << _index); 43 | } 44 | 45 | /** 46 | * @dev Returns the bit at the given '_index' in '_self' 47 | * @param _self Uint to check 48 | * @param _index Index of the bit to retrieve 49 | * @return The value of the bit at '_index' 50 | */ 51 | function getBit(uint _self, uint8 _index) 52 | internal 53 | pure 54 | returns (uint8) 55 | { 56 | return uint8(_self >> _index & 1); 57 | } 58 | 59 | /** 60 | * @dev Checks if the bit at the given '_index' in '_self' is '1' 61 | * @param _self Uint to check 62 | * @param _index Index of the bit to check 63 | * @return True, if the bit is '0'; otherwise, False 64 | */ 65 | function bitSet(uint _self, uint8 _index) 66 | internal 67 | pure 68 | returns (bool) 69 | { 70 | return getBit(_self, _index) == 1; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /plasma_framework/migrations/1010_tenderly_setup.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const { execSync } = require('child_process'); 3 | 4 | module.exports = async ( 5 | // eslint-disable-next-line no-unused-vars 6 | deployer, 7 | _, 8 | // eslint-disable-next-line no-unused-vars 9 | [deployerAddress, maintainerAddress, authorityAddress], 10 | ) => { 11 | const tenderly = process.env.TENDERLY || false; 12 | 13 | if (tenderly) { 14 | const installationStatus = execSync('curl https://raw.githubusercontent.com/Tenderly/tenderly-cli/master/scripts/install-linux.sh | sh', { encoding: 'utf-8' }); // the default is 'buffer' 15 | console.log('Installation Result:', installationStatus); 16 | const tenderlyToken = process.env.TENDERLY_TOKEN; 17 | if (!tenderlyToken) throw new Error('It is mandatory to set TENDERLY_TOKEN'); 18 | 19 | const tenderlyProject = process.env.TENDERLY_PROJECT; 20 | if (!tenderlyProject) throw new Error('It is mandatory to set TENDERLY_PROJECT'); 21 | 22 | const tenderlyProjectSetup = execSync(`tenderly init --create-project --project ${tenderlyProject}`, (error) => { 23 | if (error) { 24 | throw new Error(`Error for tenderly project init: ${error}`); 25 | } 26 | }); 27 | console.log('Tenderly project setup:', tenderlyProjectSetup); 28 | 29 | const tenderlyLogin = execSync(`tenderly login --force --authentication-method=token --token=${tenderlyToken}`, (error) => { 30 | if (error) { 31 | throw new Error(`Error for tenderly push: ${error}`); 32 | } 33 | }); 34 | 35 | console.log('Tenderly login:', tenderlyLogin); 36 | 37 | const tenderlyPush = execSync('tenderly push', (error) => { 38 | if (error) { 39 | throw new Error(`Error for tenderly push: ${error}`); 40 | } 41 | }); 42 | console.log('Tenderly push:', tenderlyPush); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/utils/SafeEthTransfer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | /** 4 | * @notice Utility library to safely transfer ETH 5 | * @dev transfer is no longer the recommended way to do ETH transfer. 6 | * see issue: https://github.com/omisego/plasma-contracts/issues/312 7 | * 8 | * This library limits the amount of gas used for external calls with value to protect against potential DOS/griefing attacks that try to use up all the gas. 9 | * see issue: https://github.com/omisego/plasma-contracts/issues/385 10 | */ 11 | library SafeEthTransfer { 12 | /** 13 | * @notice Try to transfer eth without using more gas than `gasStipend`. 14 | * Reverts if it fails to transfer the ETH. 15 | * @param receiver the address to receive ETH 16 | * @param amount the amount of ETH (in wei) to transfer 17 | * @param gasStipend the maximum amount of gas to be used for the call 18 | */ 19 | function transferRevertOnError(address payable receiver, uint256 amount, uint256 gasStipend) 20 | internal 21 | { 22 | bool success = transferReturnResult(receiver, amount, gasStipend); 23 | require(success, "SafeEthTransfer: failed to transfer ETH"); 24 | } 25 | 26 | /** 27 | * @notice Transfer ETH without using more gas than the `gasStipend`. 28 | * Returns whether the transfer call is successful or not. 29 | * @dev EVM will revert with "out of gas" error if there is not enough gas left for the call 30 | * @param receiver the address to receive ETH 31 | * @param amount the amount of ETH (in wei) to transfer 32 | * @param gasStipend the maximum amount of gas to be used during the transfer call 33 | * @return a flag showing the call is successful or not 34 | */ 35 | function transferReturnResult(address payable receiver, uint256 amount, uint256 gasStipend) 36 | internal 37 | returns (bool) 38 | { 39 | (bool success, ) = receiver.call.gas(gasStipend).value(amount)(""); 40 | return success; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.circleci/scripts/check_faucet_balance_and_warn.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from urllib import request, parse 4 | 5 | THRESHOLD = 3000000000000000000 # 3 ETH 6 | FAUCET_ADDRESS = os.environ['FAUCET_ADDRESS'] 7 | INFURA_API_TOKEN = os.environ['INFURA_API_TOKEN'] 8 | GITHUB_BOT_TOKEN = os.environ['GITHUB_BOT_TOKEN'] 9 | 10 | def get_faucet_balance(): 11 | url = f'https://rinkeby.infura.io/v3/{INFURA_API_TOKEN}' 12 | 13 | data_dict = { 14 | 'jsonrpc': '2.0', 15 | 'id': 4, 16 | 'method': 'eth_getBalance', 17 | 'params': [FAUCET_ADDRESS, 'latest'] 18 | } 19 | 20 | headers = {'Content-Type': 'application/json'} 21 | 22 | data = json.dumps(data_dict) 23 | 24 | req = request.Request(url, data.encode(), headers) 25 | with request.urlopen(req) as resp: 26 | result_decoded = (resp.read()).decode() 27 | result_json = json.loads(result_decoded) 28 | 29 | # hex string to int 30 | balance = int(result_json['result'], 16) 31 | 32 | return balance 33 | 34 | def submit_github_issue(balance): 35 | url = 'https://api.github.com/repos/omgnetwork/plasma-contracts/issues' 36 | 37 | data_dict = { 38 | 'title': 'Faucet address of CI is in low balance!', 39 | 'body': f'Please send some fund to the poor faucet: `{FAUCET_ADDRESS}`. '\ 40 | f'Current balance is: `{balance}` wei', 41 | } 42 | 43 | headers = { 44 | 'Accept': 'application/vnd.github.v3+json', 45 | 'authorization': f'token {GITHUB_BOT_TOKEN}' 46 | } 47 | 48 | data = json.dumps(data_dict) 49 | 50 | req = request.Request(url, data.encode(), headers) 51 | request.urlopen(req) 52 | with request.urlopen(req) as resp: 53 | result = (resp.read()).decode() 54 | print('GH submission result:', result) 55 | 56 | 57 | if __name__ == "__main__": 58 | balance = get_faucet_balance() 59 | print('Faucet balance:', balance, '(wei)') 60 | 61 | if balance < THRESHOLD: 62 | print('balance is low, submitting GH issue...') 63 | submit_github_issue(balance) 64 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/utils/Merkle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | /** 4 | * @title Merkle 5 | * @dev Library for working with Merkle trees 6 | */ 7 | library Merkle { 8 | byte private constant LEAF_SALT = 0x00; 9 | byte private constant NODE_SALT = 0x01; 10 | 11 | /** 12 | * @notice Checks that a leaf hash is contained in a root hash 13 | * @param leaf Leaf hash to verify 14 | * @param index Position of the leaf hash in the Merkle tree 15 | * @param rootHash Root of the Merkle tree 16 | * @param proof A Merkle proof demonstrating membership of the leaf hash 17 | * @return True, if the leaf hash is in the Merkle tree; otherwise, False 18 | */ 19 | function checkMembership(bytes memory leaf, uint256 index, bytes32 rootHash, bytes memory proof) 20 | internal 21 | pure 22 | returns (bool) 23 | { 24 | require(proof.length != 0, "Merkle proof must not be empty"); 25 | require(proof.length % 32 == 0, "Length of Merkle proof must be a multiple of 32"); 26 | 27 | // see https://github.com/omisego/plasma-contracts/issues/546 28 | require(index < 2**(proof.length/32), "Index does not match the length of the proof"); 29 | 30 | bytes32 proofElement; 31 | bytes32 computedHash = keccak256(abi.encodePacked(LEAF_SALT, leaf)); 32 | uint256 j = index; 33 | // Note: We're skipping the first 32 bytes of `proof`, which holds the size of the dynamically sized `bytes` 34 | for (uint256 i = 32; i <= proof.length; i += 32) { 35 | // solhint-disable-next-line no-inline-assembly 36 | assembly { 37 | proofElement := mload(add(proof, i)) 38 | } 39 | if (j % 2 == 0) { 40 | computedHash = keccak256(abi.encodePacked(NODE_SALT, computedHash, proofElement)); 41 | } else { 42 | computedHash = keccak256(abi.encodePacked(NODE_SALT, proofElement, computedHash)); 43 | } 44 | j = j / 2; 45 | } 46 | 47 | return computedHash == rootHash; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/framework/utils/ExitPriority.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../utils/PosLib.sol"; 4 | 5 | library ExitPriority { 6 | 7 | using PosLib for PosLib.Position; 8 | 9 | uint256 constant private SIZEOF_TIMESTAMP = 32; 10 | uint256 constant private SIZEOF_EXITID = 168; 11 | 12 | /** 13 | * @dev Returns an exit priority for a given UTXO position and a unique ID. 14 | * The priority for Plasma M(ore)VP protocol is a combination of 'exitableAt' and 'txPos'. 15 | * Since 'exitableAt' only provides granularity of block, adding 'txPos' gives higher priority to transactions added earlier in the same block. 16 | * @notice Detailed explanation on field lengths can be found at https://github.com/omisego/plasma-contracts/pull/303#discussion_r328850572 17 | * @param exitId Unique exit identifier 18 | * @return An exit priority 19 | * Anatomy of returned value, most significant bits first 20 | * 32 bits - The exitable_at timestamp (in seconds); can represent dates until the year 2106 21 | * 56 bits - The transaction position. Be aware that child chain block number jumps with the interval of CHILD_BLOCK_INTERVAL (usually 1000). 22 | blocknum * (BLOCK_OFFSET / TX_OFFSET) + txindex; 56 bits can represent all transactions for 342 years, assuming a 15 second block time. 23 | * 168 bits - The exit id 24 | */ 25 | function computePriority(uint32 exitableAt, PosLib.Position memory txPos, uint168 exitId) 26 | internal 27 | pure 28 | returns (uint256) 29 | { 30 | return (uint256(exitableAt) << (256 - SIZEOF_TIMESTAMP)) | (uint256(txPos.getTxPositionForExitPriority()) << SIZEOF_EXITID) | uint256(exitId); 31 | } 32 | 33 | function parseExitableAt(uint256 priority) internal pure returns (uint32) { 34 | return uint32(priority >> (256 - SIZEOF_TIMESTAMP)); 35 | } 36 | 37 | function parseExitId(uint256 priority) internal pure returns (uint168) { 38 | // Exit ID uses only 168 least significant bits 39 | return uint168(priority); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /plasma_framework/migrations/60_spending_conditions_payment.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const PaymentOutputToPaymentTxCondition = artifacts.require('PaymentOutputToPaymentTxCondition'); 4 | const PlasmaFramework = artifacts.require('PlasmaFramework'); 5 | const SpendingConditionRegistry = artifacts.require('SpendingConditionRegistry'); 6 | 7 | const config = require('../config.js'); 8 | 9 | module.exports = async ( 10 | deployer, 11 | _, 12 | // eslint-disable-next-line no-unused-vars 13 | [deployerAddress, maintainerAddress, authorityAddress], 14 | ) => { 15 | const PAYMENT_OUTPUT_TYPE = config.registerKeys.outputTypes.payment; 16 | const PAYMENT_TX_TYPE = config.registerKeys.txTypes.payment; 17 | const PAYMENT_V2_TX_TYPE = config.registerKeys.txTypes.paymentV2; 18 | 19 | const spendingConditionRegistry = await SpendingConditionRegistry.deployed(); 20 | const plasmaFramework = await PlasmaFramework.deployed(); 21 | 22 | await deployer.deploy( 23 | PaymentOutputToPaymentTxCondition, 24 | plasmaFramework.address, 25 | PAYMENT_OUTPUT_TYPE, 26 | PAYMENT_TX_TYPE, 27 | ); 28 | const paymentToPaymentCondition = await PaymentOutputToPaymentTxCondition.deployed(); 29 | console.log(`Registering paymentToPaymentCondition (${paymentToPaymentCondition.address}) to spendingConditionRegistry`); 30 | await spendingConditionRegistry.registerSpendingCondition( 31 | PAYMENT_OUTPUT_TYPE, PAYMENT_TX_TYPE, paymentToPaymentCondition.address, 32 | ); 33 | 34 | await deployer.deploy( 35 | PaymentOutputToPaymentTxCondition, 36 | plasmaFramework.address, 37 | PAYMENT_OUTPUT_TYPE, 38 | PAYMENT_V2_TX_TYPE, 39 | ); 40 | const paymentToPaymentV2Condition = await PaymentOutputToPaymentTxCondition.deployed(); 41 | console.log(`Registering paymentToPaymentV2Condition (${paymentToPaymentV2Condition.address}) to spendingConditionRegistry`); 42 | await spendingConditionRegistry.registerSpendingCondition( 43 | PAYMENT_OUTPUT_TYPE, PAYMENT_V2_TX_TYPE, paymentToPaymentV2Condition.address, 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/tests/utils/test_fixed_merkle.py: -------------------------------------------------------------------------------- 1 | import math 2 | import pytest 3 | from eth_utils import keccak as sha3 4 | 5 | from plasma_core.utils.merkle.fixed_merkle import FixedMerkle 6 | from plasma_core.constants import NULL_HASH 7 | 8 | LEAF_SALT = b'\x00' 9 | NODE_SALT = b'\x01' 10 | 11 | 12 | def get_empty_tree_hash(depth): 13 | root = sha3(LEAF_SALT + NULL_HASH) 14 | for _ in range(depth): 15 | root = sha3(NODE_SALT + root + root) 16 | return root 17 | 18 | 19 | @pytest.mark.parametrize("depth", [2, 3, 12]) 20 | def test_initial_state(depth): 21 | assert FixedMerkle(depth).leaves == [sha3(LEAF_SALT + NULL_HASH)] * (2 ** depth) 22 | 23 | 24 | @pytest.mark.parametrize("num_leaves", [3, 5, 9]) 25 | def test_initialize_with_leaves(num_leaves): 26 | depth = math.ceil(math.log(num_leaves, 2)) 27 | leaves = [b'asdf'] * num_leaves 28 | 29 | hashed_leaves = [sha3(LEAF_SALT + leaf) for leaf in leaves] 30 | empty_leaves = [sha3(LEAF_SALT + NULL_HASH)] * (2 ** depth - num_leaves) 31 | 32 | assert FixedMerkle(depth, leaves).leaves == hashed_leaves + empty_leaves 33 | 34 | 35 | def test_initialize_with_too_many_leaves(): 36 | depth = 1 37 | leaves = [b'asdf'] * (2 ** depth + 1) 38 | 39 | with pytest.raises(ValueError) as e: 40 | FixedMerkle(depth, leaves) 41 | 42 | assert str(e.value) == 'number of leaves should be at most depth ** 2' 43 | 44 | 45 | @pytest.mark.parametrize("depth", [1, 2, 16]) 46 | def test_empty_tree(depth): 47 | assert FixedMerkle(depth).root == get_empty_tree_hash(depth) 48 | 49 | 50 | def test_create_membership_proof(): 51 | leaf = b'c' 52 | leaves = [b'a', b'b', leaf] 53 | proof = FixedMerkle(2, leaves).create_membership_proof(leaf) 54 | sibling_hash = sha3(LEAF_SALT + NULL_HASH) 55 | node_hash = sha3(NODE_SALT + sha3(LEAF_SALT + leaves[0]) + sha3(LEAF_SALT + leaves[1])) 56 | assert proof == sibling_hash + node_hash 57 | 58 | 59 | def test_check_membership(): 60 | leaves = [b'a', b'b', b'c'] 61 | merkle = FixedMerkle(2, leaves) 62 | proof = merkle.create_membership_proof(leaves[2]) 63 | assert merkle.check_membership(leaves[2], 2, proof) 64 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/exits/payment/controllers/PaymentDeleteInFlightExit.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../PaymentExitDataModel.sol"; 5 | import "../PaymentInFlightExitModelUtils.sol"; 6 | import "../../../utils/SafeEthTransfer.sol"; 7 | import "../../../transactions/PaymentTransactionModel.sol"; 8 | 9 | library PaymentDeleteInFlightExit { 10 | using PaymentInFlightExitModelUtils for PaymentExitDataModel.InFlightExit; 11 | 12 | struct Controller { 13 | uint256 minExitPeriod; 14 | uint256 safeGasStipend; 15 | } 16 | 17 | event InFlightExitDeleted( 18 | uint168 indexed exitId 19 | ); 20 | 21 | /** 22 | * @notice Main logic function to delete the non piggybacked in-flight exit 23 | * @param exitId The exitId of the standard exit 24 | */ 25 | function run( 26 | Controller memory self, 27 | PaymentExitDataModel.InFlightExitMap storage exitMap, 28 | uint168 exitId 29 | ) 30 | public 31 | { 32 | PaymentExitDataModel.InFlightExit memory ife = exitMap.exits[exitId]; 33 | require(ife.exitStartTimestamp != 0, "In-flight exit does not exist"); 34 | require(!ife.isInFirstPhase(self.minExitPeriod), "Cannot delete in-flight exit still in first phase"); 35 | require(!isPiggybacked(ife), "The in-flight exit is already piggybacked"); 36 | 37 | delete exitMap.exits[exitId]; 38 | SafeEthTransfer.transferRevertOnError(ife.bondOwner, ife.bondSize, self.safeGasStipend); 39 | emit InFlightExitDeleted(exitId); 40 | } 41 | 42 | function isPiggybacked(ExitModel.InFlightExit memory ife) 43 | private 44 | pure 45 | returns (bool) 46 | { 47 | for (uint16 i = 0; i < PaymentTransactionModel.MAX_INPUT_NUM(); i++) { 48 | if (ife.isInputPiggybacked(i)) { 49 | return true; 50 | } 51 | } 52 | 53 | for (uint16 i = 0; i < PaymentTransactionModel.MAX_OUTPUT_NUM(); i++) { 54 | if (ife.isOutputPiggybacked(i)) { 55 | return true; 56 | } 57 | } 58 | 59 | return false; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/exits/SpendingConditionMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../src/exits/interfaces/ISpendingCondition.sol"; 5 | 6 | contract SpendingConditionMock is ISpendingCondition { 7 | bool internal expectedResult; 8 | bool internal shouldRevert; 9 | Args internal expectedArgs; 10 | 11 | string constant internal REVERT_MESSAGE = "Test spending condition reverts"; 12 | 13 | struct Args { 14 | bytes inputTx; 15 | uint256 utxoPos; 16 | bytes spendingTx; 17 | uint16 inputIndex; 18 | bytes witness; 19 | } 20 | 21 | /** mock what would "verify()" returns */ 22 | function mockResult(bool result) public { 23 | expectedResult = result; 24 | } 25 | 26 | /** when called, the spending condition would always revert on purpose */ 27 | function mockRevert() public { 28 | shouldRevert = true; 29 | } 30 | 31 | /** provide the expected args, it would check with the value called for "verify()" */ 32 | function shouldVerifyArgumentEquals(Args memory args) public { 33 | expectedArgs = args; 34 | } 35 | 36 | /** override */ 37 | function verify( 38 | bytes calldata inputTx, 39 | uint256 utxoPos, 40 | bytes calldata spendingTx, 41 | uint16 inputIndex, 42 | bytes calldata witness 43 | ) 44 | external 45 | view 46 | returns (bool) 47 | { 48 | if (shouldRevert) { 49 | revert(REVERT_MESSAGE); 50 | } 51 | 52 | // only run the check when "shouldVerifyArgumentEqauals" is called 53 | if (expectedArgs.inputTx.length > 0) { 54 | require(keccak256(expectedArgs.inputTx) == keccak256(inputTx), "input tx not as expected"); 55 | require(expectedArgs.utxoPos == utxoPos, "utxoPos not as expected"); 56 | require(keccak256(expectedArgs.spendingTx) == keccak256(spendingTx), "spending tx not as expected"); 57 | require(expectedArgs.inputIndex == inputIndex, "input index not as expected"); 58 | require(keccak256(expectedArgs.witness) == keccak256(witness), "witness not as expected"); 59 | } 60 | return expectedResult; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/exits/utils/ExitId.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "../../utils/Bits.sol"; 4 | import "../../utils/PosLib.sol"; 5 | import "./OutputId.sol"; 6 | 7 | library ExitId { 8 | using PosLib for PosLib.Position; 9 | using Bits for uint168; 10 | using Bits for uint256; 11 | 12 | uint8 constant private FIRST_BIT_LOCATION = 167; 13 | 14 | /** 15 | * @notice Checks whether exitId is a standard exit ID 16 | */ 17 | function isStandardExit(uint168 exitId) internal pure returns (bool) { 18 | return exitId.getBit(FIRST_BIT_LOCATION) == 0; 19 | } 20 | 21 | /** 22 | * @notice Given transaction bytes and UTXO position, returns its exit ID 23 | * @dev Computation of a deposit ID is different to any other tx because txBytes of a deposit tx can be a non-unique value 24 | * @param isDeposit Defines whether the tx for the exitId is a deposit tx 25 | * @param txBytes Transaction bytes 26 | * @param utxoPos UTXO position of the exiting output 27 | * @return standardExitId Unique ID of the standard exit 28 | * Anatomy of returned value, most significant bits first: 29 | * 1-bit - in-flight flag (0 for standard exit) 30 | * 167-bits - outputId 31 | */ 32 | function getStandardExitId( 33 | bool isDeposit, 34 | bytes memory txBytes, 35 | PosLib.Position memory utxoPos 36 | ) 37 | internal 38 | pure 39 | returns (uint168) 40 | { 41 | bytes32 outputId; 42 | if (isDeposit) { 43 | outputId = OutputId.computeDepositOutputId(txBytes, utxoPos.outputIndex, utxoPos.encode()); 44 | } else { 45 | outputId = OutputId.computeNormalOutputId(txBytes, utxoPos.outputIndex); 46 | } 47 | 48 | return uint168((uint256(outputId) >> (256 - FIRST_BIT_LOCATION))); 49 | } 50 | 51 | /** 52 | * @notice Given transaction bytes, returns in-flight exit ID 53 | * @param txBytes Transaction bytes 54 | * @return Unique in-flight exit ID 55 | */ 56 | function getInFlightExitId(bytes memory txBytes) internal pure returns (uint168) { 57 | return uint168((uint256(keccak256(txBytes)) >> (256 - FIRST_BIT_LOCATION)).setBit(FIRST_BIT_LOCATION)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /plasma_framework/truffle-config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); // auto parse env variables from '.env' file 2 | 3 | const HDWalletProvider = require('@truffle/hdwallet-provider'); 4 | 5 | module.exports = { 6 | networks: { 7 | loadTest: { 8 | host: '127.0.0.1', 9 | port: 8545, 10 | network_id: '*', 11 | gas: 0xfffffffffff, 12 | }, 13 | local: { 14 | host: process.env.ETH_CLIENT_HOST || '127.0.0.1', 15 | port: process.env.ETH_CLIENT_PORT || 8545, 16 | from: process.env.DEPLOYER_ADDRESS, 17 | network_id: '*', 18 | }, 19 | // Remote means that the remote client does not possess the private keys. 20 | // Transactions need to be signed locally with the given private keys 21 | // before getting submitted to the remote client. 22 | remote: { 23 | skipDryRun: true, 24 | provider: () => new HDWalletProvider( 25 | [ 26 | process.env.DEPLOYER_PRIVATEKEY || '0'.repeat(64), 27 | process.env.MAINTAINER_PRIVATEKEY || '0'.repeat(64), 28 | process.env.AUTHORITY_PRIVATEKEY || '0'.repeat(64), 29 | ], 30 | process.env.REMOTE_URL || 'http://127.0.0.1:8545', 31 | 0, 3, 32 | ), 33 | gasPrice: process.env.GAS_PRICE || 20000000000, // default 20 gwei 34 | network_id: '*', 35 | }, 36 | }, 37 | 38 | // Set default mocha options here, use special reporters etc. 39 | mocha: { 40 | reporter: process.env.MOCHA_REPORTER || '', 41 | reporterOptions: { 42 | currency: 'USD', 43 | showTimeSpent: true, 44 | rst: true, 45 | rstTitle: 'Gas Report', 46 | outputFile: './gasReport.rst', 47 | src: 'contracts/src/', 48 | }, 49 | }, 50 | 51 | // Configure your compilers 52 | compilers: { 53 | solc: { 54 | version: '0.5.11', 55 | settings: { 56 | optimizer: { 57 | enabled: true, 58 | runs: 200, 59 | }, 60 | }, 61 | }, 62 | }, 63 | 64 | plugins: ['solidity-coverage'], 65 | }; 66 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/exits/registries/SpendingConditionRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "openzeppelin-solidity/contracts/utils/Address.sol"; 5 | 6 | import "../interfaces/ISpendingCondition.sol"; 7 | 8 | /** 9 | * @title SpendingConditionRegistry 10 | * @notice The registry contracts of the spending condition 11 | * @dev This is designed to renounce ownership before injecting the registry contract to ExitGame contracts 12 | * After registering all the essential condition contracts, the owner should renounce its ownership to 13 | * ensure no further conditions are registered for an ExitGame contract. 14 | * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/ownership/Ownable.sol#L55 15 | */ 16 | contract SpendingConditionRegistry is Ownable { 17 | // mapping of hash(outputType, spendingTxTpye) => ISpendingCondition 18 | mapping(bytes32 => ISpendingCondition) internal _spendingConditions; 19 | 20 | function spendingConditions(uint256 outputType, uint256 spendingTxType) public view returns (ISpendingCondition) { 21 | bytes32 key = keccak256(abi.encode(outputType, spendingTxType)); 22 | return _spendingConditions[key]; 23 | } 24 | 25 | /** 26 | * @notice Register the spending condition contract 27 | * @param outputType The output type of the spending condition 28 | * @param spendingTxType Spending tx type of the spending condition 29 | * @param condition The spending condition contract 30 | */ 31 | function registerSpendingCondition(uint256 outputType, uint256 spendingTxType, ISpendingCondition condition) 32 | public 33 | onlyOwner 34 | { 35 | require(outputType != 0, "Registration not possible with output type 0"); 36 | require(spendingTxType != 0, "Registration not possible with spending tx type 0"); 37 | require(Address.isContract(address(condition)), "Registration not possible with a non-contract address"); 38 | 39 | bytes32 key = keccak256(abi.encode(outputType, spendingTxType)); 40 | require(address(_spendingConditions[key]) == address(0), "The (output type, spending tx type) pair is already registered"); 41 | 42 | _spendingConditions[key] = condition; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/tests/contracts/rlp/test_rlp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Regression test for bug in RLP decoder. 3 | """ 4 | 5 | import pytest 6 | import rlp 7 | 8 | from eth_utils import is_address, to_canonical_address 9 | from rlp.sedes import big_endian_int, Binary 10 | 11 | 12 | def normalize_args(args): 13 | def _normalize_arg(arg): 14 | if is_address(arg): 15 | return to_canonical_address(arg) 16 | else: 17 | return arg 18 | return tuple(map(_normalize_arg, args)) 19 | 20 | 21 | class Eight(rlp.Serializable): 22 | fields = [ 23 | ('f0', big_endian_int), 24 | ('f1', big_endian_int), 25 | ('f2', big_endian_int), 26 | ('f3', big_endian_int), 27 | ('f4', big_endian_int), 28 | ('f5', big_endian_int), 29 | ('f6', Binary.fixed_length(20)), 30 | ('f7', Binary.fixed_length(20)) 31 | ] 32 | 33 | def __init__(self, *args): 34 | args = normalize_args(args) 35 | super().__init__(*args) 36 | 37 | 38 | class Eleven(rlp.Serializable): 39 | fields = [ 40 | ('f0', big_endian_int), 41 | ('f1', big_endian_int), 42 | ('f2', big_endian_int), 43 | ('f3', big_endian_int), 44 | ('f4', big_endian_int), 45 | ('f5', big_endian_int), 46 | ('f6', big_endian_int), 47 | ('f7', big_endian_int), 48 | ('f8', Binary.fixed_length(20)), 49 | ('f9', Binary.fixed_length(20)), 50 | ('f10', Binary.fixed_length(20)) 51 | ] 52 | 53 | def __init__(self, *args): 54 | args = normalize_args(args) 55 | super().__init__(*args) 56 | 57 | 58 | @pytest.fixture 59 | def rlp_test(get_contract): 60 | contract = get_contract('RLPTest') 61 | return contract 62 | 63 | 64 | def test_rlp_tx_eight(accounts, rlp_test): 65 | tx = Eight(0, 1, 2, 3, 4, 5, accounts[0].address, accounts[1].address) 66 | tx_bytes = rlp.encode(tx, Eight) 67 | assert [5, accounts[0].address, accounts[1].address] == rlp_test.eight(tx_bytes) 68 | 69 | 70 | def test_rlp_tx_eleven(accounts, rlp_test): 71 | tx = Eleven(0, 1, 2, 3, 4, 5, 6, 7, accounts[0].address, accounts[1].address, accounts[2].address) 72 | tx_bytes = rlp.encode(tx, Eleven) 73 | assert [7, accounts[0].address, accounts[1].address, accounts[2].address] == rlp_test.eleven(tx_bytes) 74 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/exits/SpyPlasmaFrameworkForExitGame.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../src/framework/PlasmaFramework.sol"; 5 | import "../../src/utils/PosLib.sol"; 6 | import "../../src/framework/models/BlockModel.sol"; 7 | 8 | contract SpyPlasmaFrameworkForExitGame is PlasmaFramework { 9 | using PosLib for PosLib.Position; 10 | 11 | uint256 public enqueuedCount = 0; 12 | mapping (uint256 => BlockModel.Block) public blocks; 13 | 14 | event EnqueueTriggered( 15 | uint256 vaultId, 16 | address token, 17 | uint32 exitableAt, 18 | uint56 txPos, 19 | uint256 exitId, 20 | address exitProcessor 21 | ); 22 | 23 | /** This spy contract set the authority and maintainer both to the caller */ 24 | constructor(uint256 _minExitPeriod, uint256 _initialImmuneVaults, uint256 _initialImmuneExitGames) 25 | public 26 | PlasmaFramework(_minExitPeriod, _initialImmuneVaults, _initialImmuneExitGames, msg.sender, msg.sender) 27 | { 28 | } 29 | 30 | /** override for test */ 31 | function enqueue( 32 | uint256 _vaultId, 33 | address _token, 34 | uint32 _exitableAt, 35 | PosLib.Position calldata _txPos, 36 | uint168 _exitId, 37 | IExitProcessor _exitProcessor 38 | ) 39 | external 40 | returns (uint256) 41 | { 42 | enqueuedCount += 1; 43 | emit EnqueueTriggered( 44 | _vaultId, 45 | _token, 46 | _exitableAt, 47 | _txPos.getTxPositionForExitPriority(), 48 | _exitId, 49 | address(_exitProcessor) 50 | ); 51 | return enqueuedCount; 52 | } 53 | 54 | /** 55 | Custom test helpers 56 | */ 57 | function setBlock(uint256 _blockNum, bytes32 _root, uint256 _timestamp) external { 58 | blocks[_blockNum] = BlockModel.Block(_root, _timestamp); 59 | } 60 | 61 | function setOutputFinalized(bytes32 _outputId, uint168 _exitId) external { 62 | outputsFinalizations[_outputId] = _exitId; 63 | } 64 | 65 | /** 66 | * Override to remove check that block exists 67 | */ 68 | function isDeposit(uint256 blockNum) public view returns (bool) { 69 | return blockNum % childBlockInterval != 0; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/transactions/FungibleTokenOutputModel.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "./GenericTransaction.sol"; 5 | import "../utils/RLPReader.sol"; 6 | 7 | /** 8 | * @notice Data structure and its decode function for ouputs of fungible token transactions 9 | */ 10 | library FungibleTokenOutputModel { 11 | using RLPReader for RLPReader.RLPItem; 12 | 13 | struct Output { 14 | uint256 outputType; 15 | bytes20 outputGuard; 16 | address token; 17 | uint256 amount; 18 | } 19 | 20 | /** 21 | * @notice Given a GenericTransaction.Output, decodes the `data` field. 22 | * The data field is an RLP list that must satisfy the following conditions: 23 | * - It must have 3 elements: [`outputGuard`, `token`, `amount`] 24 | * - `outputGuard` is a 20 byte long array 25 | * - `token` is a 20 byte long array 26 | * - `amount` must be an integer value with no leading zeros. 27 | * @param genericOutput A GenericTransaction.Output 28 | * @return A fully decoded FungibleTokenOutputModel.Output struct 29 | */ 30 | function decodeOutput(GenericTransaction.Output memory genericOutput) 31 | internal 32 | pure 33 | returns (Output memory) 34 | { 35 | RLPReader.RLPItem[] memory dataList = genericOutput.data.toList(); 36 | require(dataList.length == 3, "Output data must have 3 items"); 37 | 38 | Output memory outputData = Output({ 39 | outputType: genericOutput.outputType, 40 | outputGuard: bytes20(dataList[0].toAddress()), 41 | token: dataList[1].toAddress(), 42 | amount: dataList[2].toUint() 43 | }); 44 | 45 | require(outputData.outputGuard != bytes20(0), "Output outputGuard must not be 0"); 46 | return outputData; 47 | } 48 | 49 | /** 50 | * @dev Decodes and returns the output at a specific index in the transaction 51 | */ 52 | function getOutput(GenericTransaction.Transaction memory transaction, uint16 outputIndex) 53 | internal 54 | pure 55 | returns 56 | (Output memory) 57 | { 58 | require(outputIndex < transaction.outputs.length, "Output index out of bounds"); 59 | return decodeOutput(transaction.outputs[outputIndex]); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/vaults/verifiers/Erc20DepositVerifier.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; 4 | 5 | import "./IErc20DepositVerifier.sol"; 6 | import {PaymentTransactionModel as DepositTx} from "../../transactions/PaymentTransactionModel.sol"; 7 | 8 | /** 9 | * @notice Implementation of Erc20 deposit verifier using payment transaction as the deposit tx 10 | */ 11 | contract Erc20DepositVerifier is IErc20DepositVerifier { 12 | uint256 public depositTxType; 13 | uint256 public supportedOutputType; 14 | 15 | constructor(uint256 txType, uint256 outputType) public { 16 | depositTxType = txType; 17 | supportedOutputType = outputType; 18 | } 19 | 20 | /** 21 | * @notice Overrides the function of IErc20DepositVerifier and implements the verification logic 22 | * for payment transaction 23 | * @dev Vault address must be approved to transfer from the sender address before doing the deposit 24 | * @return Verified (owner, token, amount) of the deposit ERC20 token data 25 | */ 26 | function verify(bytes calldata depositTx, address sender, address vault) 27 | external 28 | view 29 | returns ( 30 | address owner, 31 | address token, 32 | uint256 amount 33 | ) 34 | { 35 | DepositTx.Transaction memory decodedTx = DepositTx.decode(depositTx); 36 | 37 | require(decodedTx.txType == depositTxType, "Invalid transaction type"); 38 | 39 | require(decodedTx.inputs.length == 0, "Deposit must have no inputs"); 40 | 41 | require(decodedTx.outputs.length == 1, "Deposit must have exactly one output"); 42 | require(decodedTx.outputs[0].token != address(0), "Invalid output currency (ETH)"); 43 | require(decodedTx.outputs[0].outputType == supportedOutputType, "Invalid output type"); 44 | 45 | address depositorsAddress = DepositTx.getOutputOwner(decodedTx.outputs[0]); 46 | require(depositorsAddress == sender, "Depositor's address must match sender's address"); 47 | 48 | IERC20 erc20 = IERC20(decodedTx.outputs[0].token); 49 | require(erc20.allowance(depositorsAddress, vault) >= decodedTx.outputs[0].amount, "Tokens have not been approved"); 50 | 51 | return (depositorsAddress, decodedTx.outputs[0].token, decodedTx.outputs[0].amount); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /plasma_framework/test/src/utils/RLPOfficial.test.js: -------------------------------------------------------------------------------- 1 | const RLPMock = artifacts.require('RLPMock'); 2 | const { BN } = require('openzeppelin-test-helpers'); 3 | const { expect } = require('chai'); 4 | const { tests } = require('./fixture/rlptest.json'); 5 | 6 | // Using test fixtures from https://github.com/ethereum/tests/blob/develop/RLPTests/rlptest.json 7 | contract('RLP official tests', () => { 8 | const decode = async (encoded, expected) => { 9 | if (Number.isInteger(expected)) { 10 | const decoded = await this.rlp.decodeUint(encoded); 11 | expect(decoded).to.bignumber.equal(new BN(expected)); 12 | } else if (Array.isArray(expected)) { 13 | const decoded = await this.rlp.decodeList(encoded); 14 | expect(Array.isArray(decoded)).to.be.true; 15 | return Promise.all(decoded.map((elem, i) => decode(elem, expected[i]))); 16 | } else if (typeof expected === 'string' || expected instanceof String) { 17 | if (expected[0] === '#') { 18 | // We are testing a big number 19 | const bignum = new BN(expected.slice(1)); 20 | const decodedUint = await this.rlp.decodeUint(encoded); 21 | expect(decodedUint).to.bignumber.equal(bignum); 22 | } else { 23 | const decoded = await this.rlp.decodeString(encoded); 24 | expect(decoded).to.equal(expected); 25 | } 26 | } else { 27 | expect.fail(`input ${expected} is an unknown type`); 28 | } 29 | return true; 30 | }; 31 | 32 | before(async () => { 33 | this.rlp = await RLPMock.new(); 34 | }); 35 | 36 | Object.keys(tests).forEach((testName) => { 37 | it(`should pass ${testName}`, async () => { 38 | const original = tests[testName].in; 39 | const encoded = tests[testName].out; 40 | 41 | if (testName.startsWith('bigint')) { 42 | // Special test that encodes a number bigger than max uint256 so it has to be decoded as bytes. 43 | const expected = new BN(original.slice(1)); 44 | const decodedBytes = await this.rlp.decodeBytes(encoded); 45 | return expect(new BN(Buffer.from(decodedBytes.replace('0x', ''), 'hex'))).to.bignumber.equal(expected); 46 | } 47 | 48 | return decode(encoded, original); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/vaults/Erc20Vault.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "./Vault.sol"; 4 | import "./verifiers/IErc20DepositVerifier.sol"; 5 | import "../framework/PlasmaFramework.sol"; 6 | 7 | import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; 8 | import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol"; 9 | 10 | contract Erc20Vault is Vault { 11 | using SafeERC20 for IERC20; 12 | 13 | event Erc20Withdrawn( 14 | address indexed receiver, 15 | address indexed token, 16 | uint256 amount 17 | ); 18 | 19 | event DepositCreated( 20 | address indexed depositor, 21 | uint256 indexed blknum, 22 | address indexed token, 23 | uint256 amount 24 | ); 25 | 26 | constructor(PlasmaFramework _framework) public Vault(_framework) {} 27 | 28 | /** 29 | * @notice Deposits approved amount of ERC20 token(s) into the contract 30 | * Once the deposit is recognized, the owner (depositor) can transact on the OmiseGO Network 31 | * The approve function of the ERC20 token contract must be called before calling this function 32 | * for at least the amount that is deposited into the contract 33 | * @param depositTx RLP-encoded transaction to act as the deposit 34 | */ 35 | function deposit(bytes calldata depositTx) external { 36 | address depositVerifier = super.getEffectiveDepositVerifier(); 37 | require(depositVerifier != address(0), "Deposit verifier has not been set"); 38 | 39 | (address depositor, address token, uint256 amount) = IErc20DepositVerifier(depositVerifier) 40 | .verify(depositTx, msg.sender, address(this)); 41 | 42 | IERC20(token).safeTransferFrom(depositor, address(this), amount); 43 | 44 | uint256 blknum = super.submitDepositBlock(depositTx); 45 | 46 | emit DepositCreated(msg.sender, blknum, token, amount); 47 | } 48 | 49 | /** 50 | * @notice Withdraw ERC20 tokens that have successfully exited from the OmiseGO Network 51 | * @param receiver Address of the recipient 52 | * @param token Address of ERC20 token contract 53 | * @param amount Amount to transfer 54 | */ 55 | function withdraw(address payable receiver, address token, uint256 amount) external onlyFromNonQuarantinedExitGame { 56 | IERC20(token).safeTransfer(receiver, amount); 57 | emit Erc20Withdrawn(receiver, token, amount); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /plasma_framework/docs/design/fee-game.md: -------------------------------------------------------------------------------- 1 | # Fee ExitGame Implementation 2 | 3 | design doc: https://github.com/omisego/feat-fee/blob/master/docs/fee_exit_design.md 4 | 5 | ## Fee Transaction Data Structure 6 | 7 | ``` 8 | struct Transaction { 9 | uint256 txType; 10 | bytes32[] inputs; 11 | Output[] outputs; 12 | uint256 txData; 13 | bytes32 metaData; 14 | } 15 | 16 | struct Output { 17 | uint256 outputType; 18 | bytes20 outputGuard; 19 | address token; 20 | uint256 amount; 21 | } 22 | ``` 23 | 24 | `Transaction` is the top level structure and it has the following fields: 25 | 1. `txType` is a unique number that represents the type of transaction. The first fee tx uses `3` as its tx type. 26 | 1. The `inputs` should be an empty array. 27 | 1. The `outputs` field is an array of `Output` struct representing payment output information. There should be exactly one output. 28 | 2. `txData` is used to hold a `nonce` which is `hash(blockNum, token)`. `blockNum` is the block number that the fee tx is collecting fees from. `token` is the token that this fee tx is claiming. 29 | 3. `metaData` field is a `bytes32` field that can be used to add extra data to the transaction. It should contain `0x0` if the transaction does not have extra data. 30 | 31 | Output has four elements that fulfil the current `FungibleTokenOutputModel.Output`: 32 | 1. `outputType` represents the type of output. There is one output type for fee - the `feeClaim` output type, which has the value of `2`. 33 | 1. `outputGuard` is the field that represents the authentication data of the output. Its value must always be the same as the `owner` address of the output. For instance, if the output belongs to Alice, then the value of `outputGuard` equals Alice's address. 34 | 1. `token` is the ERC20 token contract address that represents the transferred asset. For `ETH`, it uses `address(0)`. 35 | 1. `amount` defines how many units of the asset are being transferred. 36 | 37 | ## ExitGame contract 38 | 39 | Fee is a really special case that instead of exiting via fee tx directly, operator would first generate fee tx and then spend the fee claim output into a payment transaction. After that, operator can exit via payment transaction just like any other payment transaction flows. 40 | 41 | As a result, the Fee exit game contract is an empty contract that exists only so that it can be registered to the framework. It declares its tx type and protocol as `MoreVP` at registration. 42 | -------------------------------------------------------------------------------- /plasma_framework/test/helpers/paymentEip712.js: -------------------------------------------------------------------------------- 1 | const ethsig = require('eth-sig-util'); 2 | const { Position } = require('./positions.js'); 3 | 4 | 5 | const domainSpec = [ 6 | { name: 'name', type: 'string' }, 7 | { name: 'version', type: 'string' }, 8 | { name: 'verifyingContract', type: 'address' }, 9 | { name: 'salt', type: 'bytes32' }, 10 | ]; 11 | const txSpec = [ 12 | { name: 'txType', type: 'uint256' }, 13 | { name: 'inputs', type: 'Input[]' }, 14 | { name: 'outputs', type: 'Output[]' }, 15 | { name: 'txData', type: 'uint256' }, 16 | { name: 'metadata', type: 'bytes32' }, 17 | ]; 18 | 19 | const inputSpec = [ 20 | { name: 'blknum', type: 'uint256' }, 21 | { name: 'txindex', type: 'uint256' }, 22 | { name: 'oindex', type: 'uint256' }, 23 | ]; 24 | 25 | const outputSpec = [ 26 | { name: 'outputType', type: 'uint256' }, 27 | { name: 'outputGuard', type: 'bytes20' }, 28 | { name: 'currency', type: 'address' }, 29 | { name: 'amount', type: 'uint256' }, 30 | ]; 31 | 32 | const SALT = '0xfad5c7f626d80f9256ef01929f3beb96e058b8b4b0e3fe52d84f054c0e2a7a83'; 33 | const domainData = { 34 | name: 'OMG Network', 35 | version: '2', 36 | verifyingContract: '', 37 | salt: SALT, 38 | }; 39 | 40 | const data = { 41 | types: { 42 | EIP712Domain: domainSpec, 43 | Transaction: txSpec, 44 | Input: inputSpec, 45 | Output: outputSpec, 46 | }, 47 | domain: domainData, 48 | primaryType: 'Transaction', 49 | message: '', 50 | }; 51 | 52 | const toInputStruct = (input) => { 53 | const utxoPos = new Position(input); 54 | return { 55 | blknum: utxoPos.blockNum, 56 | txindex: utxoPos.txIndex, 57 | oindex: utxoPos.outputIndex, 58 | }; 59 | }; 60 | 61 | const toOutputStruct = output => ({ 62 | outputType: output.outputType, 63 | outputGuard: output.outputGuard, 64 | currency: output.token, 65 | amount: output.amount, 66 | }); 67 | 68 | const toTxStruct = tx => ({ 69 | txType: tx.transactionType, 70 | inputs: tx.inputs.map(toInputStruct), 71 | outputs: tx.outputs.map(toOutputStruct), 72 | txData: tx.txData, 73 | metadata: tx.metaData, 74 | }); 75 | 76 | const hashTx = (tx, verifyingContract) => { 77 | data.domain.verifyingContract = verifyingContract; 78 | data.message = toTxStruct(tx); 79 | return `0x${ethsig.TypedDataUtils.sign(data).toString('hex')}`; 80 | }; 81 | 82 | module.exports = { hashTx }; 83 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/exits/utils/MoreVpFinalization.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../framework/PlasmaFramework.sol"; 5 | import "../../framework/Protocol.sol"; 6 | import "../../utils/Merkle.sol"; 7 | import "../../utils/PosLib.sol"; 8 | import "../../transactions/GenericTransaction.sol"; 9 | 10 | /** 11 | * @notice Library to check finalization for MoreVP protocol 12 | * @dev This library assumes that the tx is of the GenericTransaction format 13 | */ 14 | library MoreVpFinalization { 15 | using PosLib for PosLib.Position; 16 | 17 | /** 18 | * @notice Checks whether a transaction is "standard finalized". 19 | * For MoreVP, it means the transaction should be included in a plasma block. 20 | */ 21 | function isStandardFinalized( 22 | PlasmaFramework framework, 23 | bytes memory txBytes, 24 | PosLib.Position memory txPos, 25 | bytes memory inclusionProof 26 | ) 27 | internal 28 | view 29 | returns (bool) 30 | { 31 | require(txPos.outputIndex == 0, "Invalid transaction position"); 32 | GenericTransaction.Transaction memory genericTx = GenericTransaction.decode(txBytes); 33 | uint8 protocol = framework.protocols(genericTx.txType); 34 | require(protocol == Protocol.MORE_VP(), "MoreVpFinalization: not a MoreVP protocol tx"); 35 | 36 | (bytes32 root,) = framework.blocks(txPos.blockNum); 37 | require(root != bytes32(""), "Failed to get the root hash of the block num"); 38 | 39 | return Merkle.checkMembership( 40 | txBytes, txPos.txIndex, root, inclusionProof 41 | ); 42 | } 43 | 44 | /** 45 | * @notice Checks whether a transaction is "protocol finalized" 46 | * For MoreVP, since it allows in-flight tx, so only checks for the existence of the transaction 47 | */ 48 | function isProtocolFinalized( 49 | PlasmaFramework framework, 50 | bytes memory txBytes 51 | ) 52 | internal 53 | view 54 | returns (bool) 55 | { 56 | if (txBytes.length == 0) { 57 | return false; 58 | } 59 | 60 | GenericTransaction.Transaction memory genericTx = GenericTransaction.decode(txBytes); 61 | uint8 protocol = framework.protocols(genericTx.txType); 62 | require(protocol == Protocol.MORE_VP(), "MoreVpFinalization: not a MoreVP protocol tx"); 63 | 64 | return true; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/exits/StateTransitionVerifierMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../src/exits/interfaces/IStateTransitionVerifier.sol"; 5 | 6 | contract StateTransitionVerifierMock is IStateTransitionVerifier { 7 | bool public expectedResult; 8 | bool public shouldRevert; 9 | Args public expectedArgs; 10 | 11 | struct Args { 12 | bytes inFlightTx; 13 | bytes[] inputTxs; 14 | uint16[] outputIndexOfInputTxs; 15 | } 16 | 17 | /** mock what "isCorrectStateTransition()" returns */ 18 | function mockResult(bool result) public { 19 | expectedResult = result; 20 | } 21 | 22 | /** when called, the "isCorrectStateTransition" function reverts on purpose */ 23 | function mockRevert() public { 24 | shouldRevert = true; 25 | } 26 | 27 | /** provide the expected args, it would check with the value called for "verify()" */ 28 | function shouldVerifyArgumentEquals(Args memory args) 29 | public 30 | { 31 | expectedArgs = args; 32 | } 33 | 34 | function isCorrectStateTransition( 35 | bytes calldata inFlightTx, 36 | bytes[] calldata inputTxs, 37 | uint16[] calldata outputIndexOfInputTxs 38 | ) 39 | external 40 | view 41 | returns (bool) 42 | { 43 | if (shouldRevert) { 44 | revert("Failing on purpose"); 45 | } 46 | 47 | // only run the check when "shouldVerifyArgumentEqauals" is called 48 | if (expectedArgs.inFlightTx.length > 0) { 49 | require(keccak256(inFlightTx) == keccak256(expectedArgs.inFlightTx), "in-flight tx is not as expected"); 50 | 51 | require(inputTxs.length == expectedArgs.inputTxs.length, "input txs array length mismatches expected data"); 52 | for (uint i = 0; i < expectedArgs.inputTxs.length; i++) { 53 | require(keccak256(inputTxs[i]) == keccak256(expectedArgs.inputTxs[i]), "input tx is not as expected"); 54 | } 55 | 56 | require(outputIndexOfInputTxs.length == expectedArgs.outputIndexOfInputTxs.length, "outputIndex array length mismatches expected data"); 57 | for (uint i = 0; i < expectedArgs.outputIndexOfInputTxs.length; i++) { 58 | require(outputIndexOfInputTxs[i] == expectedArgs.outputIndexOfInputTxs[i], "output index of input txs is not as expected"); 59 | } 60 | } 61 | 62 | return expectedResult; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/vaults/EthVault.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "./Vault.sol"; 4 | import "./verifiers/IEthDepositVerifier.sol"; 5 | import "../framework/PlasmaFramework.sol"; 6 | import "../utils/SafeEthTransfer.sol"; 7 | 8 | contract EthVault is Vault { 9 | event EthWithdrawn( 10 | address indexed receiver, 11 | uint256 amount 12 | ); 13 | 14 | event WithdrawFailed( 15 | address indexed receiver, 16 | uint256 amount 17 | ); 18 | 19 | event DepositCreated( 20 | address indexed depositor, 21 | uint256 indexed blknum, 22 | address indexed token, 23 | uint256 amount 24 | ); 25 | 26 | uint256 public safeGasStipend; 27 | 28 | constructor(PlasmaFramework _framework, uint256 _safeGasStipend) public Vault(_framework) { 29 | safeGasStipend = _safeGasStipend; 30 | } 31 | 32 | /** 33 | * @notice Allows a user to deposit ETH into the contract 34 | * Once the deposit is recognized, the owner may transact on the OmiseGO Network 35 | * @param _depositTx RLP-encoded transaction to act as the deposit 36 | */ 37 | function deposit(bytes calldata _depositTx) external payable { 38 | address depositVerifier = super.getEffectiveDepositVerifier(); 39 | require(depositVerifier != address(0), "Deposit verifier has not been set"); 40 | 41 | IEthDepositVerifier(depositVerifier).verify(_depositTx, msg.value, msg.sender); 42 | uint256 blknum = super.submitDepositBlock(_depositTx); 43 | 44 | emit DepositCreated(msg.sender, blknum, address(0), msg.value); 45 | } 46 | 47 | /** 48 | * @notice Withdraw ETH that has successfully exited from the OmiseGO Network 49 | * @dev We do not want to block exit queue if a transfer is unsuccessful, so we don't revert on transfer error. 50 | * However, if there is not enough gas left for the safeGasStipend, then the EVM _will_ revert with an 'out of gas' error. 51 | * If this happens, the user should retry with higher gas. 52 | * @param receiver Address of the recipient 53 | * @param amount The amount of ETH to transfer 54 | */ 55 | function withdraw(address payable receiver, uint256 amount) external onlyFromNonQuarantinedExitGame { 56 | bool success = SafeEthTransfer.transferReturnResult(receiver, amount, safeGasStipend); 57 | if (success) { 58 | emit EthWithdrawn(receiver, amount); 59 | } else { 60 | emit WithdrawFailed(receiver, amount); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/tests/tests_utils/deployer.py: -------------------------------------------------------------------------------- 1 | from web3.contract import ConciseContract 2 | from web3 import Web3, HTTPProvider 3 | 4 | 5 | class Deployer(object): 6 | 7 | def __init__(self, builder, provider=HTTPProvider('http://localhost:8545')): 8 | self.builder = builder 9 | self.w3 = Web3(provider) 10 | 11 | def deploy_contract(self, contract_name, gas=5000000, args=(), concise=True): 12 | """Deploys a contract to the given Ethereum network using Web3 13 | 14 | Args: 15 | contract_name (str): Name of the contract to deploy. Must already be compiled. 16 | provider (HTTPProvider): The Web3 provider to deploy with. 17 | gas (int): Amount of gas to use when creating the contract. 18 | args (obj): Any additional arguments to include with the contract creation. 19 | concise (bool): Whether to return a Contract or ConciseContract instance. 20 | 21 | Returns: 22 | Contract: A Web3 contract instance. 23 | """ 24 | 25 | abi, bytecode = self.builder.get_contract_data(contract_name) 26 | 27 | contract = self.w3.eth.contract(abi=abi, bytecode=bytecode) 28 | 29 | # Get transaction hash from deployed contract 30 | tx_hash = contract.deploy(transaction={ 31 | 'from': self.w3.eth.accounts[0], 32 | 'gas': gas 33 | }, args=args) 34 | 35 | # Get tx receipt to get contract address 36 | tx_receipt = self.w3.eth.getTransactionReceipt(tx_hash) 37 | contract_address = tx_receipt['contractAddress'] 38 | 39 | contract_instance = self.w3.eth.contract(address=contract_address, abi=abi) 40 | 41 | print("Successfully deployed {0} contract!".format(contract_name)) 42 | 43 | return ConciseContract(contract_instance) if concise else contract_instance 44 | 45 | def get_contract_at_address(self, contract_name, address, concise=True): 46 | """Returns a Web3 instance of the given contract at the given address 47 | 48 | Args: 49 | contract_name (str): Name of the contract. Must already be compiled. 50 | address (str): Address of the contract. 51 | concise (bool): Whether to return a Contract or ConciseContract instance. 52 | 53 | Returns: 54 | Contract: A Web3 contract instance. 55 | """ 56 | 57 | abi, _ = self.builder.get_contract_data(contract_name) 58 | 59 | contract_instance = self.w3.eth.contract(abi=abi, address=address) 60 | 61 | return ConciseContract(contract_instance) if concise else contract_instance 62 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/exits/payment/routers/PaymentStandardExitRouterMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../../../src/exits/payment/PaymentExitGameArgs.sol"; 5 | import "../../../../src/exits/payment/routers/PaymentStandardExitRouter.sol"; 6 | import "../../../../src/exits/payment/routers/PaymentStandardExitRouterArgs.sol"; 7 | import "../../../../src/framework/PlasmaFramework.sol"; 8 | 9 | contract PaymentStandardExitRouterMock is PaymentStandardExitRouter { 10 | PlasmaFramework private framework; 11 | 12 | PaymentStandardExitRouterArgs.StartStandardExitArgs private startStandardExitArgs; 13 | PaymentStandardExitRouterArgs.ChallengeStandardExitArgs private challengeStandardExitArgs; 14 | 15 | function bootInternal(PaymentExitGameArgs.Args memory paymentExitGameArgs) 16 | public 17 | { 18 | PaymentStandardExitRouter.boot(paymentExitGameArgs); 19 | framework = paymentExitGameArgs.framework; 20 | } 21 | 22 | /** override and calls processStandardExit for test */ 23 | function processExit(uint168 exitId, uint256, address ercContract, address payable processExitInitiator) external { 24 | PaymentStandardExitRouter.processStandardExit(exitId, ercContract, processExitInitiator); 25 | } 26 | 27 | /** helper functions for testing */ 28 | function setExit(uint168 exitId, PaymentExitDataModel.StandardExit memory exitData) public { 29 | PaymentStandardExitRouter.standardExitMap.exits[exitId] = exitData; 30 | } 31 | 32 | function proxyFlagOutputFinalized(bytes32 outputId, uint168 exitId) public { 33 | framework.flagOutputFinalized(outputId, exitId); 34 | } 35 | 36 | function depositFundForTest() public payable {} 37 | 38 | /** 39 | * This function helps test reentrant by making this function itself the first call with 'nonReentrant' protection 40 | * So all other PaymentExitGame functions that is protected by 'nonReentrant' too would fail as it would be considered as re-entrancy 41 | */ 42 | function testNonReentrant(string memory testTarget) public nonReentrant(framework) { 43 | if (stringEquals(testTarget, "startStandardExit")) { 44 | PaymentStandardExitRouter.startStandardExit(startStandardExitArgs); 45 | } else if (stringEquals(testTarget, "challengeStandardExit")) { 46 | PaymentStandardExitRouter.challengeStandardExit(challengeStandardExitArgs); 47 | } 48 | } 49 | 50 | function stringEquals(string memory a, string memory b) private pure returns (bool) { 51 | return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/framework/PlasmaFramework.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "./BlockController.sol"; 5 | import "./ExitGameController.sol"; 6 | import "./registries/VaultRegistry.sol"; 7 | import "./registries/ExitGameRegistry.sol"; 8 | 9 | contract PlasmaFramework is VaultRegistry, ExitGameRegistry, ExitGameController, BlockController { 10 | uint256 public constant CHILD_BLOCK_INTERVAL = 1000; 11 | 12 | /** 13 | * The minimum finalization period is the Plasma guarantee that all exits are safe provided the user takes action within the specified time period 14 | * When the child chain is rogue, user should start their exit and challenge any invalid exit within this period 15 | * An exit can be processed/finalized after minimum two finalization periods from its inclusion position, unless it is an exit for a deposit, 16 | * which would use one finalization period, instead of two 17 | * 18 | * For the Abstract Layer Design, OmiseGO also uses some multitude of this period to update its framework 19 | * See also ExitGameRegistry.sol, VaultRegistry.sol, and Vault.sol for more information on the update waiting time (the quarantined period) 20 | * 21 | * MVP: https://ethresear.ch/t/minimal-viable-plasma/426 22 | * MoreVP: https://github.com/omisego/elixir-omg/blob/master/docs/morevp.md#timeline 23 | * Special period for deposit: https://git.io/JecCV 24 | */ 25 | uint256 public minExitPeriod; 26 | address private maintainer; 27 | string public version; 28 | 29 | constructor( 30 | uint256 _minExitPeriod, 31 | uint256 _initialImmuneVaults, 32 | uint256 _initialImmuneExitGames, 33 | address _authority, 34 | address _maintainer 35 | ) 36 | public 37 | BlockController(CHILD_BLOCK_INTERVAL, _minExitPeriod, _initialImmuneVaults, _authority) 38 | ExitGameController(_minExitPeriod, _initialImmuneExitGames) 39 | { 40 | minExitPeriod = _minExitPeriod; 41 | maintainer = _maintainer; 42 | } 43 | 44 | function getMaintainer() public view returns (address) { 45 | return maintainer; 46 | } 47 | 48 | /** 49 | * @notice Gets the semantic version of the current deployed contracts 50 | */ 51 | function getVersion() external view returns (string memory) { 52 | return version; 53 | } 54 | 55 | /** 56 | * @notice Sets the semantic version of the current deployed contracts 57 | * @param _version is semver string 58 | */ 59 | function setVersion(string memory _version) public onlyFrom(getMaintainer()) { 60 | version = _version; 61 | } 62 | } -------------------------------------------------------------------------------- /plasma_framework/migrations/110_payment_ife_controllers.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const PaymentExitGame = artifacts.require('PaymentExitGame'); 4 | const PaymentChallengeIFENotCanonical = artifacts.require('PaymentChallengeIFENotCanonical'); 5 | const PaymentChallengeIFEInputSpent = artifacts.require('PaymentChallengeIFEInputSpent'); 6 | const PaymentChallengeIFEOutputSpent = artifacts.require('PaymentChallengeIFEOutputSpent'); 7 | const PaymentDeleteInFlightExit = artifacts.require('PaymentDeleteInFlightExit'); 8 | const PaymentStartInFlightExit = artifacts.require('PaymentStartInFlightExit'); 9 | const PaymentPiggybackInFlightExit = artifacts.require('PaymentPiggybackInFlightExit'); 10 | const PaymentProcessInFlightExit = artifacts.require('PaymentProcessInFlightExit'); 11 | 12 | module.exports = async ( 13 | deployer, 14 | _, 15 | // eslint-disable-next-line no-unused-vars 16 | [deployerAddress, maintainerAddress, authorityAddress], 17 | ) => { 18 | // deploy and link in-flight exit game controllers 19 | 20 | await deployer.deploy(PaymentStartInFlightExit); 21 | const startInFlightExit = await PaymentStartInFlightExit.deployed(); 22 | 23 | await deployer.deploy(PaymentPiggybackInFlightExit); 24 | const piggybackInFlightExit = await PaymentPiggybackInFlightExit.deployed(); 25 | 26 | await deployer.deploy(PaymentChallengeIFENotCanonical); 27 | const challengeInFlightExitNotCanonical = await PaymentChallengeIFENotCanonical.deployed(); 28 | 29 | await deployer.deploy(PaymentChallengeIFEInputSpent); 30 | const challengeIFEInputSpent = await PaymentChallengeIFEInputSpent.deployed(); 31 | 32 | await deployer.deploy(PaymentChallengeIFEOutputSpent); 33 | const challengeIFEOutput = await PaymentChallengeIFEOutputSpent.deployed(); 34 | 35 | await deployer.deploy(PaymentDeleteInFlightExit); 36 | const deleteInFlightExit = await PaymentDeleteInFlightExit.deployed(); 37 | 38 | await deployer.deploy(PaymentProcessInFlightExit); 39 | const processInFlightExit = await PaymentProcessInFlightExit.deployed(); 40 | 41 | await PaymentExitGame.link('PaymentStartInFlightExit', startInFlightExit.address); 42 | await PaymentExitGame.link('PaymentPiggybackInFlightExit', piggybackInFlightExit.address); 43 | await PaymentExitGame.link('PaymentChallengeIFENotCanonical', challengeInFlightExitNotCanonical.address); 44 | await PaymentExitGame.link('PaymentChallengeIFEInputSpent', challengeIFEInputSpent.address); 45 | await PaymentExitGame.link('PaymentChallengeIFEOutputSpent', challengeIFEOutput.address); 46 | await PaymentExitGame.link('PaymentDeleteInFlightExit', deleteInFlightExit.address); 47 | await PaymentExitGame.link('PaymentProcessInFlightExit', processInFlightExit.address); 48 | }; 49 | -------------------------------------------------------------------------------- /plasma_framework/migrations/1050_output_results.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const config = require('../config.js'); 6 | 7 | const PlasmaFramework = artifacts.require('PlasmaFramework'); 8 | const PaymentEip712LibMock = artifacts.require('PaymentEip712LibMock'); 9 | const MerkleWrapper = artifacts.require('MerkleWrapper'); 10 | const ERC20Mintable = artifacts.require('ERC20Mintable'); 11 | module.exports = async ( 12 | deployer, 13 | _, 14 | // eslint-disable-next-line no-unused-vars 15 | [deployerAddress, _maintainerAddress, authorityAddress], 16 | ) => { 17 | const vault = process.env.VAULT === 'true'; 18 | let authority; 19 | if (vault) { 20 | authority = fs.readFileSync('vault_authority').toString(); 21 | } else { 22 | authority = authorityAddress; 23 | } 24 | const plasmaFramework = await PlasmaFramework.deployed(); 25 | const ethVault = await plasmaFramework.vaults(config.registerKeys.vaultId.eth); 26 | const erc20Vault = await plasmaFramework.vaults(config.registerKeys.vaultId.erc20); 27 | const paymentExitGame = await plasmaFramework.exitGames(config.registerKeys.txTypes.payment); 28 | const contracts = { 29 | authority_address: web3.utils.toChecksumAddress(`${authority}`), 30 | eth_vault: web3.utils.toChecksumAddress(`${ethVault}`), 31 | erc20_vault: web3.utils.toChecksumAddress(`${erc20Vault}`), 32 | payment_exit_game: web3.utils.toChecksumAddress(`${paymentExitGame}`), 33 | plasma_framework_tx_hash: `${PlasmaFramework.network.transactionHash}`, 34 | plasma_framework: web3.utils.toChecksumAddress(`${plasmaFramework.address}`), 35 | }; 36 | // add development contracts if present 37 | const deployTestContracts = process.env.DEPLOY_TEST_CONTRACTS || false; 38 | if (deployTestContracts) { 39 | const paymentEip712LibMock = await PaymentEip712LibMock.deployed(); 40 | contracts.paymentEip712LibMock = web3.utils.toChecksumAddress(`${paymentEip712LibMock.address}`); 41 | const merkleWrapper = await MerkleWrapper.deployed(); 42 | contracts.merkleWrapper = web3.utils.toChecksumAddress(`${merkleWrapper.address}`); 43 | const erc20Mintable = await ERC20Mintable.deployed(); 44 | contracts.erc20Mintable = web3.utils.toChecksumAddress(`${erc20Mintable.address}`); 45 | } 46 | // make a json 47 | const data = JSON.stringify(contracts); 48 | console.log(data); 49 | 50 | // Save to `output.json` 51 | const buildDir = path.resolve(__dirname, '../build'); 52 | if (!fs.existsSync(buildDir)) { 53 | fs.mkdirSync(buildDir); 54 | } 55 | fs.writeFileSync(path.resolve(buildDir, 'outputs.json'), data); 56 | }; 57 | -------------------------------------------------------------------------------- /plasma_framework/test/src/transactions/eip712Libs/PaymentEip712Lib.test.js: -------------------------------------------------------------------------------- 1 | const PaymentEip712Lib = artifacts.require('PaymentEip712LibMock'); 2 | 3 | const { expect } = require('chai'); 4 | const { constants } = require('openzeppelin-test-helpers'); 5 | const { 6 | PaymentTransactionOutput, PaymentTransaction, PlasmaDepositTransaction, 7 | } = require('../../../helpers/transaction.js'); 8 | const { buildUtxoPos } = require('../../../helpers/positions.js'); 9 | const { hashTx } = require('../../../helpers/paymentEip712.js'); 10 | const { OUTPUT_TYPE } = require('../../../helpers/constants.js'); 11 | 12 | const OUTPUT_GUARD = `0x${Array(40).fill(1).join('')}`; 13 | 14 | contract('PaymentEip712Lib', ([alice]) => { 15 | before(async () => { 16 | this.verifyingContract = (await PaymentEip712Lib.new(constants.ZERO_ADDRESS)).address; 17 | this.contract = await PaymentEip712Lib.new(this.verifyingContract); 18 | }); 19 | 20 | describe('hashTx', () => { 21 | it('should hash normal transaction correctly', async () => { 22 | const metaData = `0x${Array(64).fill(2).join('')}`; 23 | const output = new PaymentTransactionOutput(OUTPUT_TYPE.PAYMENT, 100, OUTPUT_GUARD, constants.ZERO_ADDRESS); 24 | const utxoPos = buildUtxoPos(10000, 0, 0); 25 | const tx = new PaymentTransaction(1, [utxoPos], [output], metaData); 26 | const txBytes = web3.utils.bytesToHex(tx.rlpEncoded()); 27 | 28 | expect(await this.contract.hashTx(this.verifyingContract, txBytes)) 29 | .to.equal(hashTx(tx, this.verifyingContract)); 30 | }); 31 | 32 | it('should hash deposit transaction correctly', async () => { 33 | const output = new PaymentTransactionOutput( 34 | OUTPUT_TYPE.PAYMENT, 100, alice, constants.ZERO_ADDRESS, 35 | ); 36 | const tx = new PlasmaDepositTransaction(output); 37 | const txBytes = web3.utils.bytesToHex(tx.rlpEncoded()); 38 | 39 | expect(await this.contract.hashTx(this.verifyingContract, txBytes)) 40 | .to.equal(hashTx(tx, this.verifyingContract)); 41 | }); 42 | 43 | it('should hash transaction correctly given transaction has empty metaData', async () => { 44 | const output = new PaymentTransactionOutput(OUTPUT_TYPE.PAYMENT, 100, OUTPUT_GUARD, constants.ZERO_ADDRESS); 45 | const utxoPos = buildUtxoPos(10000, 123, 2); 46 | const tx = new PaymentTransaction(1, [utxoPos], [output]); 47 | const txBytes = web3.utils.bytesToHex(tx.rlpEncoded()); 48 | 49 | expect(await this.contract.hashTx(this.verifyingContract, txBytes)) 50 | .to.equal(hashTx(tx, this.verifyingContract)); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /plasma_framework/migrations/9_create_authority.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /* eslint-disable prefer-destructuring */ 3 | const axios = require('axios'); 4 | const fs = require('fs'); 5 | 6 | module.exports = async ( 7 | _deployer, 8 | _, 9 | // eslint-disable-next-line no-unused-vars 10 | [_deployerAddress, _maintainerAddress, _authorityAddress], 11 | ) => { 12 | const authorityExists = fs.existsSync('vault_authority'); 13 | const vault = process.env.VAULT === 'true'; 14 | if (vault === true && authorityExists === false) { 15 | const chainId = `${process.env.CHAIN_ID}` || 1; 16 | const rpcUrl = process.env.VAULT_RPC_REMOTE_URL || 'http://127.0.0.1:8545'; 17 | const walletName = 'plasma-deployer'; 18 | // configuration 19 | const options = { 20 | url: `${process.env.VAULT_ADDR}/v1/immutability-eth-plugin/config`, 21 | method: 'PUT', 22 | headers: { 23 | 'X-Vault-Token': `${process.env.VAULT_TOKEN}`, 24 | 'X-Vault-Request': true, 25 | }, 26 | data: JSON.stringify({ 27 | chain_id: chainId, 28 | rpc_url: rpcUrl, 29 | }), 30 | }; 31 | console.log('Configuring vault'); 32 | let response = await axios(options); 33 | let responseObject = response.data; 34 | if (responseObject.data.chain_id !== chainId && responseObject.data.rpc_url !== rpcUrl) { 35 | throw new Error('Vault configuration did not stick'); 36 | } 37 | // wallet 38 | console.log('Creating wallet'); 39 | options.url = `${process.env.VAULT_ADDR}/v1/immutability-eth-plugin/wallets/${walletName}`; 40 | delete options.data; 41 | response = await axios(options); 42 | responseObject = response.data; 43 | if (typeof responseObject.request_id !== 'string') { 44 | throw new Error('Creating wallet failed'); 45 | } 46 | // account 47 | console.log('Creating account'); 48 | options.url = `${process.env.VAULT_ADDR}/v1/immutability-eth-plugin/wallets/${walletName}/accounts`; 49 | response = await axios(options); 50 | responseObject = response.data; 51 | if (typeof responseObject.data.address !== 'string') { 52 | throw new Error('Creating account failed'); 53 | } else { 54 | console.log(`Authority account now in vault ${responseObject.data.address}`); 55 | fs.writeFileSync('vault_authority', `${responseObject.data.address}`.toLowerCase()); 56 | } 57 | console.log('Done creating authority account inside vault'); 58 | } else { 59 | console.log(`Skipping because Vault ${vault} or perhaps authority exists ${authorityExists}`); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /plasma_framework/docs/design/add-new-tx-type-check-lists.md: -------------------------------------------------------------------------------- 1 | # Check List For Adding New Transaction Types 2 | 3 | This document provides some points that should be taken into consideration whenever designing a new transaction type. Since there are some known restrictions of the Plasma Framework design, please make sure that the newly created tx type and exit game does not break the global assumptions and limitations. 4 | 5 | 1. Aside from deposit transactions, all transactions should be unique. 6 | - To be unique it means that the encoded transaction should not collide with another transaction. 7 | - Usually, a tx with valid inputs would be unique. An input would hold the information of an output identifier that can point to an output. A valid output identifier should be unique as a result making the transaction unique as well. 8 | - Deposit transaction is a known exception of this as it does not have inputs and the outputs can be holding with same `amount` and `token`. See: [previous related issue of deposit tx not being unique](https://github.com/omisego/plasma-contracts/issues/80) 9 | 2. `OutputId` should be unique for all outputs. This means that for each output, the current `outputId` schema should be unique and not collide with each other. We use `outputId` to flag the output in `PlasmaFramework` thus require it to be unique. The schema of `OutputId` is: 10 | - Deposit Output: `hash(txBytes, outputIndex, utxoPos)` 11 | - Other Output: `hash(txBytes, outputIndex)` 12 | 3. If the new transaction type is going to introduce new output identifier schema, please make sure it does not collide with any existing schema. We have 2 schema used at this moment: `UtxoPos` and `OutputId`. 13 | 4. Output index is max to `TX_OFFSET` (10000) of the `UtxoPos` schema. As a result, no more than such amounts of outputs in one transaction. 14 | 5. `ExitId` for the new exit game contract must promise to be not collide with all existing `ExitId` schemas used in another exit game contracts. NOTICE: Each exit game contract can potentially introduce new schema if needed. Not recommended though. 15 | 6. When defining a new tx type, a new output type should be introduced along. Do not reuse an already existing output type. For example, even an extension of Payment V1 to Payment V2, the output should be using different output type. (see previous discussion: [here](https://github.com/omisego/plasma-contracts/issues/214#issuecomment-526466192)) 16 | 7. Output type and input pointer type (output identifier schema) should be 1:1 binding. For instance, all inputs pointing to output type 1 should all be using `utxoPos` while all inputs pointing to output type 2 should all be using `outputId` schema. We should not have two inputs using different schema pointing to same output type output. (See discussion: [here](https://github.com/omisego/research/issues/93#issuecomment-517734509)) 17 | -------------------------------------------------------------------------------- /plasma_framework/test/loadTests/PriorityQueue.load.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const PriorityQueue = artifacts.require('PriorityQueue'); 3 | const LoadTest = artifacts.require('PriorityQueueLoadTest'); 4 | 5 | const { expect } = require('chai'); 6 | const seedrandom = require('seedrandom'); 7 | 8 | contract.skip('PriorityQueue Load Test', ([framework]) => { 9 | // The total number of elements to inject to queue 10 | const TEST_SIZE = 1000000; 11 | const BATCH_SIZE = 10000; 12 | const MAX_GAS_EXPECTING = 1000000; 13 | 14 | beforeEach(async () => { 15 | this.priorityQueue = await PriorityQueue.new(); 16 | }); 17 | 18 | describe('load test on gas limit', () => { 19 | beforeEach(async () => { 20 | this.loadTest = await LoadTest.new(); 21 | this.determisticRandom = seedrandom('static seed'); 22 | }); 23 | 24 | /** 25 | * This test is designed to inject and deleted every BATCH_SIZE. 26 | * The test itself requires some gas more than usually Ethereum block gas limit, 27 | * also quite a lot much more initial Eth fund on the test accounts. 28 | * 29 | * During this test, it injects sorted value to heap as sorted array would fullfil the requirement for heap. 30 | * For each batch, it inserts 0 and delMin (which would always be 0 as 0 is the min value of uint). 31 | * Reason of using 0 is to make sure it would be testing worse case + it would be removed right after. 32 | */ 33 | it('should test and print the gas cost for each batch size items', async () => { 34 | const values = [...Array(TEST_SIZE)].map(() => { 35 | const num = this.determisticRandom.int32(); 36 | return num > 0 ? num : -1 * num; 37 | }); 38 | values.sort(); 39 | 40 | for (let i = 0; i < TEST_SIZE / BATCH_SIZE; i++) { 41 | const start = i * BATCH_SIZE; 42 | const end = (i + 1) * BATCH_SIZE; 43 | const heapDataToSet = values.slice(start, end); 44 | 45 | /* eslint-disable no-await-in-loop */ 46 | await this.loadTest.setHeapData(heapDataToSet); 47 | 48 | const txInsert = await this.loadTest.insert(0); 49 | const txDelMin = await this.loadTest.delMin(); 50 | 51 | console.log('# batch num:', i, '| gas used for insert:', txInsert.receipt.gasUsed, '| gas used for delMin:', txDelMin.receipt.gasUsed, '| remaining balance: ', await web3.eth.getBalance(framework)); 52 | 53 | expect(txInsert.receipt.gasUsed).to.be.at.most(MAX_GAS_EXPECTING); 54 | expect(txDelMin.receipt.gasUsed).to.be.at.most(MAX_GAS_EXPECTING); 55 | } 56 | }).timeout(12 * 60 * 60 * 1000); // 12 hours 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /plasma_framework/contracts/poc/fast_exits/QuasarPool.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../src/utils/SafeEthTransfer.sol"; 5 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 6 | import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; 7 | import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol"; 8 | 9 | contract QuasarPool { 10 | using SafeMath for uint256; 11 | using SafeERC20 for IERC20; 12 | 13 | address public quasarMaintainer; 14 | uint256 constant internal SAFE_GAS_STIPEND = 2300; 15 | 16 | modifier onlyQuasarMaintainer() { 17 | require(msg.sender == quasarMaintainer, "Only the Quasar Maintainer can invoke this method"); 18 | _; 19 | } 20 | 21 | mapping (address => uint256) public tokenUsableCapacity; 22 | event QuasarTotalCapacityUpdated(address token, uint256 balance); 23 | 24 | /** 25 | * @dev Add Eth Liquid funds to the quasar 26 | */ 27 | function addEthCapacity() public payable { 28 | address token = address(0); 29 | tokenUsableCapacity[token] = tokenUsableCapacity[token].add(msg.value); 30 | emit QuasarTotalCapacityUpdated(token, tokenUsableCapacity[token]); 31 | } 32 | 33 | /** 34 | * @dev Add ERC20 Liquid funds to the quasar 35 | */ 36 | function addTokenCapacity(address token, uint256 amount) public { 37 | IERC20(token).safeTransferFrom(msg.sender, address(this), amount); 38 | tokenUsableCapacity[token] = tokenUsableCapacity[token].add(amount); 39 | emit QuasarTotalCapacityUpdated(token, tokenUsableCapacity[token]); 40 | } 41 | 42 | /** 43 | * @dev Withdraw Eth funds from the contract 44 | * @param amount amount of Eth(in wei) to withdraw 45 | */ 46 | function withdrawEth(uint256 amount) public onlyQuasarMaintainer() { 47 | address token = address(0); 48 | require(amount <= tokenUsableCapacity[token], "Amount should be lower than claimable funds"); 49 | tokenUsableCapacity[token] = tokenUsableCapacity[token].sub(amount); 50 | SafeEthTransfer.transferRevertOnError(msg.sender, amount, SAFE_GAS_STIPEND); 51 | emit QuasarTotalCapacityUpdated(token, tokenUsableCapacity[token]); 52 | } 53 | 54 | /** 55 | * @dev Withdraw Erc20 funds from the contract 56 | * @param token the erc20 token 57 | * @param amount amount of the token to withdraw 58 | */ 59 | function withdrawErc20(address token, uint256 amount) public onlyQuasarMaintainer() { 60 | require(amount <= tokenUsableCapacity[token], "Amount should be lower than claimable funds"); 61 | tokenUsableCapacity[token] = tokenUsableCapacity[token].sub(amount); 62 | IERC20(token).safeTransfer(msg.sender, amount); 63 | emit QuasarTotalCapacityUpdated(token, tokenUsableCapacity[token]); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /plasma_framework/migrations/1040_setup.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const PlasmaFramework = artifacts.require('PlasmaFramework'); 3 | const PaymentExitGame = artifacts.require('PaymentExitGame'); 4 | const FeeExitGame = artifacts.require('FeeExitGame'); 5 | const EthVault = artifacts.require('EthVault'); 6 | const Erc20Vault = artifacts.require('Erc20Vault'); 7 | const EthDepositVerifier = artifacts.require('EthDepositVerifier'); 8 | const Erc20DepositVerifier = artifacts.require('Erc20DepositVerifier'); 9 | const childProcess = require('child_process'); 10 | const config = require('../config.js'); 11 | const pck = require('../package.json'); 12 | 13 | module.exports = async ( 14 | deployer, 15 | _, 16 | // eslint-disable-next-line no-unused-vars 17 | [deployerAddress, maintainerAddress, authorityAddress], 18 | ) => { 19 | const vault = process.env.VAULT === 'true'; 20 | if (vault === false) { 21 | console.log('Performing final setup'); 22 | const plasmaFramework = await PlasmaFramework.deployed(); 23 | const sha = childProcess.execSync('git rev-parse HEAD').toString().trim().substring(0, 7); 24 | const ethDepositVerifier = await EthDepositVerifier.deployed(); 25 | const ethVault = await EthVault.deployed(); 26 | const erc20DepositVerifier = await Erc20DepositVerifier.deployed(); 27 | const erc20Vault = await Erc20Vault.deployed(); 28 | const MORE_VP = config.frameworks.protocols.moreVp; 29 | const PAYMENT_TX_TYPE = config.registerKeys.txTypes.payment; 30 | const FEE_TX_TYPE = config.registerKeys.txTypes.fee; 31 | const paymentExitGame = await PaymentExitGame.deployed(); 32 | const feeExitGame = await FeeExitGame.deployed(); 33 | 34 | await ethVault.setDepositVerifier(ethDepositVerifier.address, { from: maintainerAddress }); 35 | await plasmaFramework.registerVault( 36 | config.registerKeys.vaultId.eth, 37 | ethVault.address, 38 | { from: maintainerAddress }, 39 | ); 40 | await erc20Vault.setDepositVerifier(erc20DepositVerifier.address, { from: maintainerAddress }); 41 | await plasmaFramework.registerVault( 42 | config.registerKeys.vaultId.erc20, 43 | erc20Vault.address, 44 | { from: maintainerAddress }, 45 | ); 46 | await paymentExitGame.init({ from: maintainerAddress }); 47 | await plasmaFramework.registerExitGame( 48 | PAYMENT_TX_TYPE, 49 | paymentExitGame.address, 50 | MORE_VP, 51 | { from: maintainerAddress }, 52 | ); 53 | await plasmaFramework.registerExitGame( 54 | FEE_TX_TYPE, 55 | feeExitGame.address, 56 | MORE_VP, 57 | { from: maintainerAddress }, 58 | ); 59 | await plasmaFramework.setVersion(`${pck.version}+${sha}`, { from: maintainerAddress }); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /plasma_framework/contracts/mocks/framework/DummyExitGame.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "./registries/ExitGameRegistryMock.sol"; 5 | import "../../src/framework/ExitGameController.sol"; 6 | import "../../src/framework/interfaces/IExitProcessor.sol"; 7 | import "../../src/vaults/Erc20Vault.sol"; 8 | import "../../src/vaults/EthVault.sol"; 9 | import "../../src/utils/PosLib.sol"; 10 | 11 | contract DummyExitGame is IExitProcessor { 12 | uint256 public priorityFromEnqueue; 13 | 14 | ExitGameRegistryMock public exitGameRegistry; 15 | ExitGameController public exitGameController; 16 | EthVault public ethVault; 17 | Erc20Vault public erc20Vault; 18 | 19 | event ExitFinalizedFromDummyExitGame ( 20 | uint256 indexed exitId, 21 | uint256 vaultId, 22 | address ercContract 23 | ); 24 | 25 | // override ExitProcessor interface 26 | function processExit(uint168 exitId, uint256 vaultId, address ercContract, address payable) public { 27 | emit ExitFinalizedFromDummyExitGame(exitId, vaultId, ercContract); 28 | } 29 | 30 | // setter function only for test, not a real Exit Game function 31 | function setExitGameRegistry(address _contract) public { 32 | exitGameRegistry = ExitGameRegistryMock(_contract); 33 | } 34 | 35 | function checkOnlyFromNonQuarantinedExitGame() public view returns (bool) { 36 | return exitGameRegistry.checkOnlyFromNonQuarantinedExitGame(); 37 | } 38 | 39 | // setter function only for test, not a real Exit Game function 40 | function setExitGameController(address _contract) public { 41 | exitGameController = ExitGameController(_contract); 42 | } 43 | 44 | function enqueue(uint256 vaultId, address token, uint32 exitableAt, uint256 txPos, uint168 exitId, IExitProcessor exitProcessor) 45 | public 46 | { 47 | priorityFromEnqueue = exitGameController.enqueue(vaultId, token, exitableAt, PosLib.decode(txPos), exitId, exitProcessor); 48 | } 49 | 50 | function proxyBatchFlagOutputsFinalized(bytes32[] memory outputIds, uint168 exitId) public { 51 | exitGameController.batchFlagOutputsFinalized(outputIds, exitId); 52 | } 53 | 54 | function proxyFlagOutputFinalized(bytes32 outputId, uint168 exitId) public { 55 | exitGameController.flagOutputFinalized(outputId, exitId); 56 | } 57 | 58 | // setter function only for test, not a real Exit Game function 59 | function setEthVault(EthVault vault) public { 60 | ethVault = vault; 61 | } 62 | 63 | function proxyEthWithdraw(address payable target, uint256 amount) public { 64 | ethVault.withdraw(target, amount); 65 | } 66 | 67 | // setter function only for test, not a real Exit Game function 68 | function setErc20Vault(Erc20Vault vault) public { 69 | erc20Vault = vault; 70 | } 71 | 72 | function proxyErc20Withdraw(address payable target, address token, uint256 amount) public { 73 | erc20Vault.withdraw(target, token, amount); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /plasma_framework/test/helpers/merkle.js: -------------------------------------------------------------------------------- 1 | const LeafSalt = '0x00'; 2 | const NodeSalt = '0x01'; 3 | const NullHash = web3.utils.sha3('\0'.repeat(33)); 4 | 5 | 6 | class MerkleNode { 7 | constructor(data, left = null, right = null) { 8 | this.data = data; 9 | this.left = left; 10 | this.right = right; 11 | } 12 | } 13 | 14 | class MerkleTree { 15 | constructor(leaves, height = 0) { 16 | const minHeightForLeaves = leaves.length === 1 ? 1 : parseInt(Math.ceil(Math.log2(leaves.length)), 10); 17 | 18 | if (height === 0) { 19 | this.height = minHeightForLeaves; 20 | } else if (height > minHeightForLeaves) { 21 | this.height = height; 22 | } else { 23 | throw new Error( 24 | `height should be at least ${minHeightForLeaves} for the list of leaves`, 25 | ); 26 | } 27 | 28 | this.leafCount = 2 ** this.height; 29 | 30 | this.leaves = leaves.map(MerkleTree.hashLeaf); 31 | 32 | const fill = Array.from({ length: this.leafCount - this.leaves.length }, () => NullHash); 33 | this.leaves = this.leaves.concat(fill); 34 | this.tree = [MerkleTree.createNodes(this.leaves)]; 35 | this.root = this.createTree(this.tree[0]); 36 | } 37 | 38 | static hashLeaf(leaf) { 39 | return web3.utils.sha3(LeafSalt + leaf.slice(2)); 40 | } 41 | 42 | static createNodes(leaves) { 43 | return leaves.map(leaf => new MerkleNode(leaf)); 44 | } 45 | 46 | createTree(level) { 47 | if (level.length === 1) { 48 | return level[0].data; 49 | } 50 | 51 | const levelSize = level.length; 52 | const nextLevel = []; 53 | 54 | let i = 0; 55 | while (i < levelSize) { 56 | // JS stores hashes as hex-encoded strings 57 | const combinedData = NodeSalt + level[i].data.slice(2) + level[i + 1].data.slice(2); 58 | const combined = web3.utils.sha3(combinedData); 59 | const nextNode = new MerkleNode(combined, level[i], level[i + 1]); 60 | nextLevel.push(nextNode); 61 | i += 2; 62 | } 63 | 64 | this.tree.push(nextLevel); 65 | return this.createTree(nextLevel); 66 | } 67 | 68 | getInclusionProof(leaf) { 69 | const hashedLeaf = MerkleTree.hashLeaf(leaf); 70 | 71 | let index = this.leaves.indexOf(hashedLeaf); 72 | if (index === -1) { 73 | throw new Error('Argument is not a leaf in the tree'); 74 | } 75 | 76 | let proof = '0x'; 77 | for (let i = 0; i < this.height; i++) { 78 | let siblingIndex; 79 | if (index % 2 === 0) { 80 | siblingIndex = index + 1; 81 | } else { 82 | siblingIndex = index - 1; 83 | } 84 | index = Math.floor(index / 2); 85 | 86 | proof += this.tree[i][siblingIndex].data.slice(2); 87 | } 88 | 89 | return proof; 90 | } 91 | } 92 | 93 | module.exports.MerkleTree = MerkleTree; 94 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/exits/payment/spendingConditions/PaymentOutputToPaymentTxCondition.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "openzeppelin-solidity/contracts/cryptography/ECDSA.sol"; 4 | 5 | import "../../interfaces/ISpendingCondition.sol"; 6 | import "../../../utils/PosLib.sol"; 7 | import "../../../transactions/PaymentTransactionModel.sol"; 8 | import "../../../transactions/eip712Libs/PaymentEip712Lib.sol"; 9 | 10 | contract PaymentOutputToPaymentTxCondition is ISpendingCondition { 11 | using PaymentEip712Lib for PaymentEip712Lib.Constants; 12 | using PosLib for PosLib.Position; 13 | using PaymentTransactionModel for PaymentTransactionModel.Transaction; 14 | 15 | uint256 internal supportInputTxType; 16 | uint256 internal supportSpendingTxType; 17 | PaymentEip712Lib.Constants internal eip712; 18 | 19 | /** 20 | * @dev This is designed to be re-useable for all versions of payment transaction, so that 21 | * inputTxType and spendingTxType of the payment output is injected instead 22 | */ 23 | constructor(address framework, uint256 inputTxType, uint256 spendingTxType) public { 24 | eip712 = PaymentEip712Lib.initConstants(framework); 25 | supportInputTxType = inputTxType; 26 | supportSpendingTxType = spendingTxType; 27 | } 28 | 29 | /** 30 | * @notice Verifies the spending condition 31 | * @param inputTxBytes Encoded input transaction, in bytes 32 | * @param utxoPos Position of the utxo 33 | * @param spendingTxBytes Spending transaction, in bytes 34 | * @param inputIndex Input index of the spending tx that points to the output 35 | * @param signature Signature of the output owner 36 | */ 37 | function verify( 38 | bytes calldata inputTxBytes, 39 | uint256 utxoPos, 40 | bytes calldata spendingTxBytes, 41 | uint16 inputIndex, 42 | bytes calldata signature 43 | ) 44 | external 45 | view 46 | returns (bool) 47 | { 48 | PaymentTransactionModel.Transaction memory inputTx = PaymentTransactionModel.decode(inputTxBytes); 49 | require(inputTx.txType == supportInputTxType, "Input tx is an unsupported payment tx type"); 50 | 51 | PaymentTransactionModel.Transaction memory spendingTx = PaymentTransactionModel.decode(spendingTxBytes); 52 | require(spendingTx.txType == supportSpendingTxType, "The spending tx is an unsupported payment tx type"); 53 | 54 | require( 55 | spendingTx.inputs[inputIndex] == bytes32(utxoPos), 56 | "Spending tx points to the incorrect output UTXO position" 57 | ); 58 | 59 | PosLib.Position memory decodedUtxoPos = PosLib.decode(utxoPos); 60 | address owner = PaymentTransactionModel.getOutputOwner(inputTx.getOutput(decodedUtxoPos.outputIndex)); 61 | address signer = ECDSA.recover(eip712.hashTx(spendingTx), signature); 62 | require(signer != address(0), "Failed to recover the signer from the signature"); 63 | require(owner == signer, "Tx is not signed correctly"); 64 | 65 | return true; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /plasma_framework/python_tests/plasma_core/utils/merkle/fixed_merkle.py: -------------------------------------------------------------------------------- 1 | from eth_utils import keccak as sha3 2 | from .exceptions import MemberNotExistException 3 | from plasma_core.constants import NULL_HASH 4 | 5 | LEAF_SALT = b'\x00' 6 | NODE_SALT = b'\x01' 7 | 8 | 9 | class MerkleNode(object): 10 | 11 | def __init__(self, data, left=None, right=None): 12 | self.data = data 13 | self.left = left 14 | self.right = right 15 | 16 | 17 | class FixedMerkle(object): 18 | 19 | def __init__(self, depth, leaves=[]): 20 | if depth < 1: 21 | raise ValueError('depth must be at least 1') 22 | 23 | self.depth = depth 24 | self.leaf_count = 2 ** depth 25 | 26 | if len(leaves) > self.leaf_count: 27 | raise ValueError('number of leaves should be at most depth ** 2') 28 | 29 | leaves = [sha3(LEAF_SALT + leaf) for leaf in leaves] 30 | 31 | self.leaves = leaves + [sha3(LEAF_SALT + NULL_HASH)] * (self.leaf_count - len(leaves)) 32 | self.tree = [self.__create_nodes(self.leaves)] 33 | self.__create_tree(self.tree[0]) 34 | 35 | def __create_nodes(self, leaves): 36 | return [MerkleNode(leaf) for leaf in leaves] 37 | 38 | def __create_tree(self, leaves): 39 | if len(leaves) == 1: 40 | self.root = leaves[0].data 41 | return 42 | 43 | next_level = len(leaves) 44 | tree_level = [] 45 | 46 | for i in range(0, next_level, 2): 47 | combined = sha3(NODE_SALT + leaves[i].data + leaves[i + 1].data) 48 | next_node = MerkleNode(combined, leaves[i], leaves[i + 1]) 49 | tree_level.append(next_node) 50 | 51 | self.tree.append(tree_level) 52 | self.__create_tree(tree_level) 53 | 54 | def check_membership(self, leaf, index, proof): 55 | hashed_leaf = sha3(LEAF_SALT + leaf) 56 | computed_hash = hashed_leaf 57 | computed_index = index 58 | 59 | for i in range(0, self.depth * 32, 32): 60 | proof_segment = proof[i:i + 32] 61 | 62 | if computed_index % 2 == 0: 63 | computed_hash = sha3(NODE_SALT + computed_hash + proof_segment) 64 | else: 65 | computed_hash = sha3(NODE_SALT + proof_segment + computed_hash) 66 | computed_index = computed_index // 2 67 | 68 | return computed_hash == self.root 69 | 70 | def create_membership_proof(self, leaf): 71 | hashed_leaf = sha3(LEAF_SALT + leaf) 72 | if not self.__is_member(hashed_leaf): 73 | raise MemberNotExistException('leaf is not in the merkle tree') 74 | 75 | index = self.leaves.index(hashed_leaf) 76 | proof = b'' 77 | 78 | for i in range(0, self.depth, 1): 79 | if index % 2 == 0: 80 | sibling_index = index + 1 81 | else: 82 | sibling_index = index - 1 83 | index = index // 2 84 | 85 | proof += self.tree[i][sibling_index].data 86 | 87 | return proof 88 | 89 | def __is_member(self, leaf): 90 | return leaf in self.leaves 91 | -------------------------------------------------------------------------------- /plasma_framework/contracts/src/utils/PosLib.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.11; 2 | 3 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 4 | 5 | /** 6 | * @dev UTXO position = (blknum * BLOCK_OFFSET + txIndex * TX_OFFSET + outputIndex). 7 | * TX position = (blknum * BLOCK_OFFSET + txIndex * TX_OFFSET) 8 | */ 9 | library PosLib { 10 | struct Position { 11 | uint64 blockNum; 12 | uint16 txIndex; 13 | uint16 outputIndex; 14 | } 15 | 16 | uint256 constant internal BLOCK_OFFSET = 1000000000; 17 | uint256 constant internal TX_OFFSET = 10000; 18 | 19 | uint256 constant internal MAX_OUTPUT_INDEX = TX_OFFSET - 1; 20 | // Since we are using a merkle tree of depth 16, max tx index size is 2^16 - 1 21 | uint256 constant internal MAX_TX_INDEX = 2 ** 16 - 1; 22 | // The maximum block number we can handle depends on ExitPriority. ExitPriority only uses 56 bits for blockNum + txIndex 23 | // This is 720575940379 blocks, which works out to be 342 years of blocks (given a block occurs every 15 seconds) 24 | uint256 constant internal MAX_BLOCK_NUM = ((2 ** 56 - 1) - MAX_TX_INDEX) / (BLOCK_OFFSET / TX_OFFSET); 25 | 26 | /** 27 | * @notice Returns transaction position which is an utxo position of zero index output 28 | * @param pos UTXO position of the output 29 | * @return Position of a transaction 30 | */ 31 | function toStrictTxPos(Position memory pos) 32 | internal 33 | pure 34 | returns (Position memory) 35 | { 36 | return Position(pos.blockNum, pos.txIndex, 0); 37 | } 38 | 39 | /** 40 | * @notice Used for calculating exit priority 41 | * @param pos UTXO position for the output 42 | * @return Identifier of the transaction 43 | */ 44 | function getTxPositionForExitPriority(Position memory pos) 45 | internal 46 | pure 47 | returns (uint56) 48 | { 49 | return uint56(encode(pos) / TX_OFFSET); 50 | } 51 | 52 | /** 53 | * @notice Encodes a position 54 | * @param pos Position 55 | * @return Position encoded as an integer 56 | */ 57 | function encode(Position memory pos) internal pure returns (uint256) { 58 | require(pos.outputIndex <= MAX_OUTPUT_INDEX, "Invalid output index"); 59 | require(pos.blockNum <= MAX_BLOCK_NUM, "Invalid block number"); 60 | 61 | return pos.blockNum * BLOCK_OFFSET + pos.txIndex * TX_OFFSET + pos.outputIndex; 62 | } 63 | 64 | /** 65 | * @notice Decodes a position from an integer value 66 | * @param pos Encoded position 67 | * @return Position 68 | */ 69 | function decode(uint256 pos) internal pure returns (Position memory) { 70 | uint256 blockNum = pos / BLOCK_OFFSET; 71 | uint256 txIndex = (pos % BLOCK_OFFSET) / TX_OFFSET; 72 | uint16 outputIndex = uint16(pos % TX_OFFSET); 73 | 74 | require(blockNum <= MAX_BLOCK_NUM, "blockNum exceeds max size allowed in PlasmaFramework"); 75 | require(txIndex <= MAX_TX_INDEX, "txIndex exceeds the size of uint16"); 76 | return Position(uint64(blockNum), uint16(txIndex), outputIndex); 77 | } 78 | } 79 | --------------------------------------------------------------------------------