├── .ecrc ├── .editorconfig ├── .env.sample ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── git.yaml │ └── test.yaml ├── .gitignore ├── .gitmodules ├── .husky ├── post-commit └── pre-commit ├── .mocharc.json ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── .solhintignore ├── .vscode └── tasks.json ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── contracts ├── Controller.sol ├── IController.sol ├── IManager.sol ├── Manager.sol ├── ManagerProxy.sol ├── ManagerProxyTarget.sol ├── ServiceRegistry.sol ├── arbitrum │ ├── DummyGateway.sol │ └── DummyL2LPTDataCache.sol ├── bonding │ ├── BondingManager.sol │ ├── BondingVotes.sol │ ├── IBondingManager.sol │ ├── IBondingVotes.sol │ └── libraries │ │ ├── EarningsPool.sol │ │ ├── EarningsPoolLIP36.sol │ │ └── SortedArrays.sol ├── governance │ └── Governor.sol ├── libraries │ ├── MathUtils.sol │ ├── MathUtilsV2.sol │ ├── PreciseMathUtils.sol │ └── SortedDoublyLL.sol ├── pm │ ├── TicketBroker.sol │ └── mixins │ │ ├── MixinContractRegistry.sol │ │ ├── MixinReserve.sol │ │ ├── MixinTicketBrokerCore.sol │ │ ├── MixinTicketProcessor.sol │ │ ├── MixinWrappers.sol │ │ └── interfaces │ │ ├── MContractRegistry.sol │ │ ├── MReserve.sol │ │ ├── MTicketBrokerCore.sol │ │ └── MTicketProcessor.sol ├── polling │ ├── Poll.sol │ └── PollCreator.sol ├── rounds │ ├── AdjustableRoundsManager.sol │ ├── IRoundsManager.sol │ └── RoundsManager.sol ├── snapshots │ ├── IMerkleSnapshot.sol │ └── MerkleSnapshot.sol ├── test │ ├── TestEarningsPool.sol │ ├── TestEarningsPoolLIP36.sol │ ├── TestMathUtils.sol │ ├── TestMathUtilsV2.sol │ ├── TestPreciseMathUtils.sol │ ├── TestSortedArrays.sol │ ├── TestSortedDoublyLLFindWithHints.sol │ ├── TestSortedDoublyLLFindWithHints2.sol │ ├── TestSortedDoublyLLInsert.sol │ ├── TestSortedDoublyLLRemove.sol │ ├── TestSortedDoublyLLUpdateKey.sol │ ├── helpers │ │ ├── RevertProxy.sol │ │ └── truffle │ │ │ ├── Assert.sol │ │ │ ├── AssertAddress.sol │ │ │ ├── AssertAddressArray.sol │ │ │ ├── AssertAddressPayableArray.sol │ │ │ ├── AssertBalance.sol │ │ │ ├── AssertBool.sol │ │ │ ├── AssertBytes32.sol │ │ │ ├── AssertBytes32Array.sol │ │ │ ├── AssertGeneral.sol │ │ │ ├── AssertInt.sol │ │ │ ├── AssertIntArray.sol │ │ │ ├── AssertString.sol │ │ │ ├── AssertUint.sol │ │ │ └── AssertUintArray.sol │ └── mocks │ │ ├── AlphaJobsManagerMock.sol │ │ ├── BondingManagerMock.sol │ │ ├── BondingVotesERC5805Harness.sol │ │ ├── BondingVotesMock.sol │ │ ├── EarningsPoolFixture.sol │ │ ├── GenericMock.sol │ │ ├── GovenorInterfacesFixture.sol │ │ ├── GovernorCountingOverridableHarness.sol │ │ ├── LivepeerGovernorUpgradeMock.sol │ │ ├── ManagerFixture.sol │ │ ├── ManagerProxyTargetMockV1.sol │ │ ├── ManagerProxyTargetMockV2.sol │ │ ├── ManagerProxyTargetMockV3.sol │ │ ├── MinterMock.sol │ │ ├── SetUint256.sol │ │ ├── SortedArraysFixture.sol │ │ ├── SortedDoublyLLFixture.sol │ │ ├── TicketBrokerExtendedMock.sol │ │ └── VotesMock.sol ├── token │ ├── ILivepeerToken.sol │ ├── IMinter.sol │ ├── LivepeerToken.sol │ ├── LivepeerTokenFaucet.sol │ └── Minter.sol ├── treasury │ ├── GovernorCountingOverridable.sol │ ├── IVotes.sol │ ├── LivepeerGovernor.sol │ └── Treasury.sol └── zeppelin │ ├── MerkleProof.sol │ ├── Ownable.sol │ └── Pausable.sol ├── deploy ├── deploy_ai_service_registry.ts ├── deploy_arbitrum_lpt_dummies.ts ├── deploy_bonding_manager.ts ├── deploy_bonding_votes.ts ├── deploy_contracts.ts ├── deploy_delta_upgrade.ts ├── deploy_dummy_gateway.ts ├── deploy_minter.ts ├── deploy_poll.ts ├── deploy_ticket_broker.ts ├── genesis.config.ts └── migrations.config.ts ├── deployments ├── arbitrumMainnet │ ├── .chainId │ ├── AIServiceRegistry.json │ ├── BondingManager.json │ ├── BondingManagerProxy.json │ ├── BondingManagerTarget.json │ ├── BondingVotes.json │ ├── BondingVotesProxy.json │ ├── BondingVotesTarget.json │ ├── Controller.json │ ├── Governor.json │ ├── LivepeerGovernor.json │ ├── LivepeerGovernorProxy.json │ ├── LivepeerGovernorTarget.json │ ├── ManagerProxy.json │ ├── MerkleSnapshot.json │ ├── Minter.json │ ├── PollCreator.json │ ├── RoundsManager.json │ ├── RoundsManagerProxy.json │ ├── RoundsManagerTarget.json │ ├── ServiceRegistry.json │ ├── ServiceRegistryProxy.json │ ├── ServiceRegistryTarget.json │ ├── SortedDoublyLL.json │ ├── TicketBroker.json │ ├── TicketBrokerProxy.json │ ├── TicketBrokerTarget.json │ ├── Treasury.json │ └── solcInputs │ │ ├── 13bbf81b78bf748dd89a83d5a98b32f5.json │ │ ├── 1877b67be8e28d84b2a3aa483674460a.json │ │ ├── 233ecab6413b5c14fb112afc259743c6.json │ │ ├── 27d66ea2a888a79c7ec1cad8a29cd53d.json │ │ ├── 38b657e341fe6dcb1b812a658e825f5e.json │ │ ├── 3ff5221b290e455902c32c32981b5340.json │ │ ├── 4453767352a742839dd2030ae7070f8c.json │ │ ├── 6ef8695a2f240c3b9460c170f7b2aff1.json │ │ ├── 7ecca35a4ebb76de8fe36f44cdd991c9.json │ │ ├── 87d3973b1539ca7f39c4e5c21e5f3ca9.json │ │ ├── a7c2f43b8dedbc725d6bfa7ee94775bb.json │ │ ├── b4307db432f8a65a8c8ad6b3664fc085.json │ │ ├── c3ed922e96af8791a5d9012efe089995.json │ │ ├── d94ffe25b800e8b5f253113d5894db83.json │ │ ├── e0424c78571e350313a9f65dd4baea4e.json │ │ └── fbb7c6c031c5ea66d51283bdfeec92b9.json ├── arbitrumRinkeby │ ├── .chainId │ ├── BondingManager.json │ ├── BondingManagerProxy.json │ ├── BondingManagerTarget.json │ ├── Controller.json │ ├── Governor.json │ ├── LivepeerTokenFaucet.json │ ├── ManagerProxy.json │ ├── MerkleSnapshot.json │ ├── Minter.json │ ├── PollCreator.json │ ├── RoundsManager.json │ ├── RoundsManagerProxy.json │ ├── RoundsManagerTarget.json │ ├── ServiceRegistry.json │ ├── SortedDoublyLL.json │ ├── TicketBroker.json │ ├── TicketBrokerProxy.json │ ├── TicketBrokerTarget.json │ └── solcInputs │ │ ├── 21ff0b4c55faaf36a27c7c9c43cfb232.json │ │ ├── 52de547bf900d68228bf4e4cdfe1d28d.json │ │ ├── 6b31450babf2f796469bca2cd1bfe6ba.json │ │ ├── cd771027a406633e1258619751cc7c14.json │ │ └── fd71754917ff1fac1bc941be820e6e93.json └── arbitrumRinkebyDevnet │ ├── .chainId │ ├── BondingManager.json │ ├── BondingManagerProxy.json │ ├── BondingManagerTarget.json │ ├── Controller.json │ ├── LivepeerTokenFaucet.json │ ├── ManagerProxy.json │ ├── MerkleSnapshot.json │ ├── Minter.json │ ├── PollCreator.json │ ├── RoundsManager.json │ ├── RoundsManagerProxy.json │ ├── RoundsManagerTarget.json │ ├── ServiceRegistry.json │ ├── SortedDoublyLL.json │ ├── TicketBroker.json │ ├── TicketBrokerProxy.json │ ├── TicketBrokerTarget.json │ └── solcInputs │ ├── 52de547bf900d68228bf4e4cdfe1d28d.json │ ├── cd771027a406633e1258619751cc7c14.json │ └── ee8a03df27d11be2aa41bb260f2bb7e7.json ├── doc ├── deploy_delta.md ├── devnet.md └── upgrade.md ├── docker-compose.yml ├── foundry.toml ├── hardhat.config.ts ├── package.json ├── remappings.txt ├── scripts ├── run_integration_tests.sh └── verifyPendingStake.ts ├── src └── test │ ├── BondingManagerForceChangeDelegateFix.sol │ ├── BondingManagerForceSelfDelegationFix.sol │ ├── BondingManagerForceSelfDelegationPoC.sol │ ├── BondingManagerFuzzer.sol │ ├── BondingManagerInflatedTicketFix.sol │ ├── BondingManagerInflatedTicketPoc.sol │ ├── BondingManagerNullDelegateBondFix.sol │ ├── BondingManagerNullDelegateTransferBondFix.sol │ ├── BondingManagerRebondUninitializedFactorsFix.sol │ ├── BondingManagerRebondUninitializedFactorsPoC.sol │ ├── BondingManagerRewardsGriefingFix.sol │ ├── BondingManagerRewardsGriefingPoC.sol │ ├── BondingManagerTransferBondFix.sol │ ├── BondingVotesFeeLessVotesFix.sol │ ├── BondingVotesStateInitialization.sol │ ├── MinterGlobalTotalSupplyFix.sol │ ├── TicketBrokerForceCancelUnlockFix.sol │ ├── TicketBrokerForceCancelUnlockPoC.sol │ ├── base │ └── GovernorBaseTest.sol │ └── interfaces │ ├── ICheatCodes.sol │ ├── IGovernor.sol │ └── IL2Migrator.sol ├── tasks ├── etherscan-verify-deployment.ts ├── print-contract-address.ts ├── register-gateway-with-router.ts ├── set-contract-info.ts ├── set-round-length.ts ├── treasury-renounce-admin-role.ts ├── unpause.ts ├── verify-delta-deployment.ts └── verify-protocol.ts ├── test ├── gas-report │ ├── checkpoints.js │ ├── poolsize.js │ └── redeemTicket.js ├── helpers │ ├── calcTxCost.js │ ├── executeLIP36Upgrade.js │ ├── expectFail.js │ ├── expectThrow.js │ ├── governorEnums.js │ ├── math.js │ ├── setupIntegrationTest.ts │ ├── signMsg.js │ └── ticket.js ├── integration │ ├── BondingVotes.js │ ├── BroadcasterWithdrawalFlow.js │ ├── Delegation.js │ ├── Earnings.js │ ├── GovernorUpdate.js │ ├── LivepeerGovernor.ts │ ├── MinterUpgrade.js │ ├── PoolUpdatesWithHints.js │ ├── Rewards.js │ ├── RoundInitialization.js │ ├── SystemPause.js │ ├── TicketFlow.js │ └── TicketFrontRun.js └── unit │ ├── BondingManager.js │ ├── BondingVotes.js │ ├── Controller.js │ ├── EarningsPool.js │ ├── Governor.js │ ├── GovernorCountingOverridable.js │ ├── LivepeerTokenFaucet.js │ ├── Manager.js │ ├── ManagerProxy.js │ ├── MathUtils.js │ ├── MathUtilsV2.js │ ├── MerkleSnapshot.js │ ├── Minter.js │ ├── Poll.js │ ├── PollCreator.js │ ├── PreciseMathUtils.js │ ├── RoundsManager.js │ ├── ServiceRegistry.js │ ├── SortedArrays.js │ ├── SortedDoublyLL.js │ ├── TicketBroker.js │ └── helpers │ ├── Fixture.js │ ├── expectCheckpoints.ts │ └── runSolidityTest.js ├── tsconfig.json ├── utils ├── arbitrum │ ├── abis │ │ ├── ArbRetryableTx.json │ │ ├── Inbox.json │ │ └── NodeInterface.json │ ├── contracts.ts │ ├── gas.ts │ ├── index.ts │ ├── messaging.ts │ └── transactions.ts ├── constants.js ├── deployer.ts ├── helpers.js ├── merkleTree.js └── rpc.js └── yarn.lock /.ecrc: -------------------------------------------------------------------------------- 1 | { 2 | "Verbose": false, 3 | "Debug": false, 4 | "IgnoreDefaults": false, 5 | "SpacesAftertabs": false, 6 | "NoColor": false, 7 | "Exclude": [ 8 | "deployments/.*/.chainId", 9 | ".*.sol", 10 | "deployments/arbitrum.*.json" 11 | ], 12 | "AllowedContentTypes": [], 13 | "PassedFiles": [], 14 | "Disable": { 15 | "EndOfLine": false, 16 | "Indentation": false, 17 | "InsertFinalNewline": false, 18 | "TrimTrailingWhitespace": false, 19 | "IndentSize": false, 20 | "MaxLineLength": false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{js,sol}] 12 | indent_style = true 13 | indent_size = 4 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY= 2 | INFURA_KEY= 3 | REPORT_GAS= 4 | ETHERSCAN_API_KEY=XXXX-XXXX-XXXX-XXXX 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | typechain 2 | abi 3 | cache 4 | artifacts 5 | coverage 6 | keystore 7 | deployments 8 | scripts/**/*.sh 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": [ 4 | "@typescript-eslint" 5 | ], 6 | "extends": [ 7 | "./node_modules/eslint-config-google/index.js" 8 | ], 9 | "parserOptions": { 10 | "ecmaVersion": 8, 11 | "sourceType": "module" 12 | }, 13 | "rules": { 14 | "arrow-parens": ["error", "as-needed"], 15 | "max-len": "off", 16 | "new-cap": ["error", {"capIsNew": false}], 17 | "require-jsdoc": "off", 18 | "semi": ["error", "never"], 19 | "quotes": ["error", "double"], 20 | "comma-dangle": ["error", "never"], 21 | "indent": ["error", 4], 22 | "space-infix-ops": ["error"], 23 | "camelcase": "off" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **What does this pull request do? Explain your changes. (required)** 2 | 3 | 4 | **Specific updates (required)** 5 | 6 | - ... 7 | - ... 8 | - ... 9 | 10 | **How did you test each of these updates (required)** 11 | 12 | 13 | 14 | **Does this pull request close any open issues?** 15 | 16 | 17 | 18 | **Checklist:** 19 | 20 | 21 | - [ ] README and other documentation updated 22 | - [ ] All tests using `yarn test` pass 23 | -------------------------------------------------------------------------------- /.github/workflows/git.yaml: -------------------------------------------------------------------------------- 1 | name: Git Checks 2 | 3 | on: [push] 4 | 5 | jobs: 6 | block-fixup: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2.0.0 10 | - name: Block Fixup Commit Merge 11 | uses: 13rac1/block-fixup-merge-action@v1.1.1 12 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Run test suite for the project 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - delta 8 | - confluence 9 | - streamflow 10 | - next 11 | - master 12 | 13 | jobs: 14 | test: 15 | name: Test with coverage 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Check out code 19 | uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 0 22 | # Check https://github.com/livepeer/go-livepeer/pull/1891 23 | # for ref value discussion 24 | ref: ${{ github.event.pull_request.head.sha }} 25 | 26 | - uses: actions/setup-node@v3 27 | with: 28 | node-version: "16" 29 | cache: "yarn" 30 | 31 | - name: Install dependencies 32 | run: yarn install 33 | 34 | - name: Run linter 35 | run: yarn lint 36 | 37 | - name: Compile contracts 38 | run: yarn compile 39 | 40 | - name: 💡 Run contract tests with coverage reporting 41 | run: yarn test:coverage 42 | 43 | - name: Upload coverage reports 44 | uses: codecov/codecov-action@v4 45 | with: 46 | files: ./coverage/lcov.info 47 | name: ${{ github.event.repository.name }} 48 | token: ${{ secrets.CI_CODECOV_TOKEN }} 49 | 50 | editorconfig: 51 | name: Run editorconfig checker 52 | runs-on: ubuntu-latest 53 | steps: 54 | - name: Check out code 55 | uses: actions/checkout@v4.1.1 56 | with: 57 | # Check https://github.com/livepeer/go-livepeer/pull/1891 58 | # for ref value discussion 59 | ref: ${{ github.event.pull_request.head.sha }} 60 | 61 | - name: Install editorconfig-checker 62 | uses: editorconfig-checker/action-editorconfig-checker@main 63 | 64 | - name: Run editorconfig checker against the repo 65 | run: editorconfig-checker 66 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | 5 | [submodule "lib/forge-std"] 6 | path = lib/forge-std 7 | url = https://github.com/foundry-rs/forge-std 8 | -------------------------------------------------------------------------------- /.husky/post-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . "$(dirname "$0")/_/husky.sh" 4 | 5 | git status 6 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . "$(dirname "$0")/_/husky.sh" 4 | 5 | yarn prettier 6 | yarn lint 7 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": "hardhat/register", 3 | "timeout": 40000, 4 | "_": [ 5 | "test/**/*.ts", 6 | "test/**/*.js" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.sol", 5 | "options": { 6 | "tabWidth": 4, 7 | "arrowParens": "avoid", 8 | "bracketSpacing": true, 9 | "endOfLine": "auto", 10 | "printWidth": 120, 11 | "singleQuote": false, 12 | "trailingComma": "all", 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mocha: { 3 | timeout: 100000, 4 | }, 5 | testCommand: "npx hardhat deploy && npx hardhat test", 6 | skipFiles: [ 7 | "test", 8 | "zeppelin", 9 | "rounds/AdjustableRoundsManager.sol", 10 | "pm/mixins/interfaces", 11 | "bonding/deprecated", 12 | "token/LivepeerToken.sol", // https://github.com/livepeer/arbitrum-lpt-bridge/blob/main/test/unit/L2/livepeerToken.test.ts 13 | "token/ArbitrumLivepeerToken.sol", // testnet only, 14 | "arbitrum", 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "not-rely-on-time": "off", 6 | "reason-string": "off", 7 | "no-empty-blocks": "off", 8 | "prettier/prettier": "error", 9 | "compiler-version": ["error", "^0.8.8"], 10 | "func-visibility": ["warn", {"ignoreConstructors": true}] 11 | } 12 | } -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | contracts/test 3 | contracts/zeppelin 4 | contracts/Migrations.sol 5 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "lint", 9 | "problemMatcher": ["$eslint-stylish"], 10 | "presentation": { 11 | "reveal": "never", 12 | "panel": "shared" 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Livepeer Protocol 2 | 3 | ## Style Guidelines 4 | 5 | We use [Solium](https://github.com/duaraghav8/Solium) to enforce Solidity coding conventions and [ESLint](https://eslint.org/) to enforce Javascript coding conventions. 6 | Below are a few style guidelines to be used when contributing to the Livepeer Protocol. 7 | 8 | #### Solidity 9 | 10 | ##### Explicit Function Visibility Specifiers 11 | 12 | Always define an explicit visibility specifier for all functions. Public functions should have 13 | an explicit `public` visibility specifier even though functions currently default to `public` if 14 | an explicit visibility specifier is not provided. This guideline might change in the future if the 15 | Solidity compiler is updated such that functions default to either `internal` or `private` if an explicit visibility 16 | specifier is not provided. 17 | 18 | ##### Public Vs. External Function Visibility Specifier 19 | 20 | Prefer `external` to `public` if you know a function will only be called externally. If there is a possibility 21 | of a function being called internally as well as externally, the function should be `public`. `external` functions can be more 22 | efficient when a function parameter is a large array of data. 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.5.0 2 | 3 | WORKDIR /app/ 4 | 5 | # Need .git so we can get the git head commit hash 6 | COPY / /app/ 7 | 8 | RUN yarn 9 | RUN yarn compile 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Livepeer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /contracts/Controller.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./IController.sol"; 5 | import "./IManager.sol"; 6 | 7 | import "./zeppelin/Pausable.sol"; 8 | 9 | contract Controller is Pausable, IController { 10 | // Track information about a registered contract 11 | struct ContractInfo { 12 | address contractAddress; // Address of contract 13 | bytes20 gitCommitHash; // SHA1 hash of head Git commit during registration of this contract 14 | } 15 | 16 | // Track contract ids and contract info 17 | mapping(bytes32 => ContractInfo) private registry; 18 | 19 | constructor() { 20 | // Start system as paused 21 | paused = true; 22 | } 23 | 24 | /** 25 | * @notice Register contract id and mapped address 26 | * @param _id Contract id (keccak256 hash of contract name) 27 | * @param _contractAddress Contract address 28 | */ 29 | function setContractInfo( 30 | bytes32 _id, 31 | address _contractAddress, 32 | bytes20 _gitCommitHash 33 | ) external override onlyOwner { 34 | registry[_id].contractAddress = _contractAddress; 35 | registry[_id].gitCommitHash = _gitCommitHash; 36 | 37 | emit SetContractInfo(_id, _contractAddress, _gitCommitHash); 38 | } 39 | 40 | /** 41 | * @notice Update contract's controller 42 | * @param _id Contract id (keccak256 hash of contract name) 43 | * @param _controller Controller address 44 | */ 45 | function updateController(bytes32 _id, address _controller) external override onlyOwner { 46 | return IManager(registry[_id].contractAddress).setController(_controller); 47 | } 48 | 49 | /** 50 | * @notice Return contract info for a given contract id 51 | * @param _id Contract id (keccak256 hash of contract name) 52 | */ 53 | function getContractInfo(bytes32 _id) public view returns (address, bytes20) { 54 | return (registry[_id].contractAddress, registry[_id].gitCommitHash); 55 | } 56 | 57 | /** 58 | * @notice Get contract address for an id 59 | * @param _id Contract id 60 | */ 61 | function getContract(bytes32 _id) public view override returns (address) { 62 | return registry[_id].contractAddress; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /contracts/IController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./zeppelin/Pausable.sol"; 5 | 6 | abstract contract IController is Pausable { 7 | event SetContractInfo(bytes32 id, address contractAddress, bytes20 gitCommitHash); 8 | 9 | function setContractInfo( 10 | bytes32 _id, 11 | address _contractAddress, 12 | bytes20 _gitCommitHash 13 | ) external virtual; 14 | 15 | function updateController(bytes32 _id, address _controller) external virtual; 16 | 17 | function getContract(bytes32 _id) public view virtual returns (address); 18 | } 19 | -------------------------------------------------------------------------------- /contracts/IManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | interface IManager { 5 | event SetController(address controller); 6 | event ParameterUpdate(string param); 7 | 8 | function setController(address _controller) external; 9 | } 10 | -------------------------------------------------------------------------------- /contracts/Manager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./IManager.sol"; 5 | import "./IController.sol"; 6 | 7 | contract Manager is IManager { 8 | // Controller that contract is registered with 9 | IController public controller; 10 | 11 | // Check if sender is controller 12 | modifier onlyController() { 13 | _onlyController(); 14 | _; 15 | } 16 | 17 | // Check if sender is controller owner 18 | modifier onlyControllerOwner() { 19 | _onlyControllerOwner(); 20 | _; 21 | } 22 | 23 | // Check if controller is not paused 24 | modifier whenSystemNotPaused() { 25 | _whenSystemNotPaused(); 26 | _; 27 | } 28 | 29 | // Check if controller is paused 30 | modifier whenSystemPaused() { 31 | _whenSystemPaused(); 32 | _; 33 | } 34 | 35 | constructor(address _controller) { 36 | controller = IController(_controller); 37 | } 38 | 39 | /** 40 | * @notice Set controller. Only callable by current controller 41 | * @param _controller Controller contract address 42 | */ 43 | function setController(address _controller) external onlyController { 44 | controller = IController(_controller); 45 | 46 | emit SetController(_controller); 47 | } 48 | 49 | function _onlyController() private view { 50 | require(msg.sender == address(controller), "caller must be Controller"); 51 | } 52 | 53 | function _onlyControllerOwner() private view { 54 | require(msg.sender == controller.owner(), "caller must be Controller owner"); 55 | } 56 | 57 | function _whenSystemNotPaused() private view { 58 | require(!controller.paused(), "system is paused"); 59 | } 60 | 61 | function _whenSystemPaused() private view { 62 | require(controller.paused(), "system is not paused"); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /contracts/ManagerProxyTarget.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./Manager.sol"; 5 | 6 | /** 7 | * @title ManagerProxyTarget 8 | * @notice The base contract that target contracts used by a proxy contract should inherit from 9 | * @dev Both the target contract and the proxy contract (implemented as ManagerProxy) MUST inherit from ManagerProxyTarget in order to guarantee 10 | that both contracts have the same storage layout. Differing storage layouts in a proxy contract and target contract can 11 | potentially break the delegate proxy upgradeability mechanism 12 | */ 13 | abstract contract ManagerProxyTarget is Manager { 14 | // Used to look up target contract address in controller's registry 15 | bytes32 public targetContractId; 16 | } 17 | -------------------------------------------------------------------------------- /contracts/ServiceRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./ManagerProxyTarget.sol"; 5 | 6 | /** 7 | * @title ServiceRegistry 8 | * @notice Maintains a registry of service metadata associated with service provider addresses (transcoders/orchestrators) 9 | */ 10 | contract ServiceRegistry is ManagerProxyTarget { 11 | // Store service metadata 12 | struct Record { 13 | string serviceURI; // Service URI endpoint that can be used to send off-chain requests 14 | } 15 | 16 | // Track records for addresses 17 | mapping(address => Record) private records; 18 | 19 | // Event fired when a caller updates its service URI endpoint 20 | event ServiceURIUpdate(address indexed addr, string serviceURI); 21 | 22 | /** 23 | * @notice ServiceRegistry constructor. Only invokes constructor of base Manager contract with provided Controller address 24 | * @param _controller Address of a Controller that this contract will be registered with 25 | */ 26 | constructor(address _controller) Manager(_controller) {} 27 | 28 | /** 29 | * @notice Stores service URI endpoint for the caller that can be used to send requests to the caller off-chain 30 | * @param _serviceURI Service URI endpoint for the caller 31 | */ 32 | function setServiceURI(string calldata _serviceURI) external { 33 | records[msg.sender].serviceURI = _serviceURI; 34 | 35 | emit ServiceURIUpdate(msg.sender, _serviceURI); 36 | } 37 | 38 | /** 39 | * @notice Returns service URI endpoint stored for a given address 40 | * @param _addr Address for which a service URI endpoint is desired 41 | */ 42 | function getServiceURI(address _addr) public view returns (string memory) { 43 | return records[_addr].serviceURI; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/arbitrum/DummyGateway.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | contract DummyGateway { 5 | function calculateL2TokenAddress(address _token) external pure returns (address) { 6 | return _token; 7 | } 8 | 9 | function counterpartGateway() external view returns (address) { 10 | return address(this); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/arbitrum/DummyL2LPTDataCache.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | contract DummyL2LPTDataCache { 5 | function l1CirculatingSupply() external pure returns (uint256) { 6 | return 0; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /contracts/bonding/IBondingVotes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../treasury/IVotes.sol"; 5 | 6 | /** 7 | * @title Interface for BondingVotes 8 | */ 9 | interface IBondingVotes is IVotes { 10 | error InvalidCaller(address caller, address required); 11 | error InvalidStartRound(uint256 checkpointRound, uint256 requiredRound); 12 | error FutureLastClaimRound(uint256 lastClaimRound, uint256 maxAllowed); 13 | error InvalidTotalStakeCheckpointRound(uint256 checkpointRound, uint256 requiredRound); 14 | 15 | error FutureLookup(uint256 queryRound, uint256 maxAllowed); 16 | error MissingEarningsPool(address transcoder, uint256 round); 17 | 18 | // Indicates that the called function is not supported in this contract and should be performed through the 19 | // BondingManager instead. This is mostly used for IVotes delegation methods which must be bonds instead. 20 | error MustCallBondingManager(string bondingManagerFunction); 21 | 22 | /** 23 | * @dev Emitted when a checkpoint results in changes to a delegator's `bondedAmount`. This complements the events 24 | * from IERC5805 by also supporting voting power for the delegators themselves, though requiring knowledge about our 25 | * specific reward-claiming protocol to calculate voting power based on this value. 26 | */ 27 | event DelegatorBondedAmountChanged( 28 | address indexed delegate, 29 | uint256 previousBondedAmount, 30 | uint256 previousLastClaimRound, 31 | uint256 newBondedAmount, 32 | uint256 newLastClaimRound 33 | ); 34 | 35 | // BondingManager hooks 36 | 37 | function checkpointBondingState( 38 | address _account, 39 | uint256 _startRound, 40 | uint256 _bondedAmount, 41 | address _delegateAddress, 42 | uint256 _delegatedAmount, 43 | uint256 _lastClaimRound, 44 | uint256 _lastRewardRound 45 | ) external; 46 | 47 | function checkpointTotalActiveStake(uint256 _totalStake, uint256 _round) external; 48 | 49 | // Historical stake access functions 50 | 51 | function hasCheckpoint(address _account) external view returns (bool); 52 | 53 | function getTotalActiveStakeAt(uint256 _round) external view returns (uint256); 54 | 55 | function getVotesAndDelegateAtRoundStart(address _account, uint256 _round) 56 | external 57 | view 58 | returns (uint256 amount, address delegateAddress); 59 | } 60 | -------------------------------------------------------------------------------- /contracts/bonding/libraries/EarningsPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../../libraries/MathUtils.sol"; 5 | 6 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 7 | 8 | /** 9 | * @title EarningsPool 10 | * @dev Manages reward and fee pools for delegators and transcoders 11 | */ 12 | library EarningsPool { 13 | using SafeMath for uint256; 14 | 15 | struct Data { 16 | uint256 totalStake; // Transcoder's total stake during the earnings pool's round 17 | uint256 transcoderRewardCut; // Transcoder's reward cut during the earnings pool's round 18 | uint256 transcoderFeeShare; // Transcoder's fee share during the earnings pool's round 19 | // LIP-36 (https://github.com/livepeer/LIPs/blob/master/LIPs/LIP-36.md) fields 20 | // See EarningsPoolLIP36.sol 21 | uint256 cumulativeRewardFactor; 22 | uint256 cumulativeFeeFactor; 23 | } 24 | 25 | /** 26 | * @dev Sets transcoderRewardCut and transcoderFeeshare for an EarningsPool 27 | * @param earningsPool Storage pointer to EarningsPool struct 28 | * @param _rewardCut Reward cut of transcoder during the earnings pool's round 29 | * @param _feeShare Fee share of transcoder during the earnings pool's round 30 | */ 31 | function setCommission( 32 | EarningsPool.Data storage earningsPool, 33 | uint256 _rewardCut, 34 | uint256 _feeShare 35 | ) internal { 36 | earningsPool.transcoderRewardCut = _rewardCut; 37 | earningsPool.transcoderFeeShare = _feeShare; 38 | } 39 | 40 | /** 41 | * @dev Sets totalStake for an EarningsPool 42 | * @param earningsPool Storage pointer to EarningsPool struct 43 | * @param _stake Total stake of the transcoder during the earnings pool's round 44 | */ 45 | function setStake(EarningsPool.Data storage earningsPool, uint256 _stake) internal { 46 | earningsPool.totalStake = _stake; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/bonding/libraries/SortedArrays.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../../libraries/MathUtils.sol"; 5 | 6 | import "@openzeppelin/contracts/utils/Arrays.sol"; 7 | 8 | /** 9 | * @title SortedArrays 10 | * @dev Handles maintaining and looking up on sorted uint256 arrays. 11 | */ 12 | library SortedArrays { 13 | using Arrays for uint256[]; 14 | 15 | error DecreasingValues(uint256 newValue, uint256 lastValue); 16 | 17 | /** 18 | * @notice Searches a sorted _array and returns the last element to be lower or equal to _val. If there is no such 19 | * element (all elements in array are higher than the searched element), the array length is returned. 20 | * 21 | * @dev This basically converts OpenZeppelin's {Arrays-findUpperBound} into findLowerBound, meaning it also uses a 22 | * binary search in the worst case after trying some shortcuts. Worst case time complexity is O(log n). The only 23 | * change being that the returned index points to the element lower or equal to _val, instead of higher or equal. 24 | * @param _array Array to search in 25 | * @param _val Value to search for 26 | * @return lower Index of the lower bound found in array 27 | */ 28 | function findLowerBound(uint256[] storage _array, uint256 _val) internal view returns (uint256) { 29 | uint256 len = _array.length; 30 | if (len == 0) { 31 | return 0; 32 | } 33 | 34 | if (_array[len - 1] <= _val) { 35 | return len - 1; 36 | } 37 | 38 | uint256 upperIdx = _array.findUpperBound(_val); 39 | 40 | // we already checked the last element above so the upper will always be inside the array 41 | assert(upperIdx < len); 42 | 43 | // the exact value we were searching is in the array 44 | if (_array[upperIdx] == _val) { 45 | return upperIdx; 46 | } 47 | 48 | // a 0 idx means that the first elem is already higher than the searched value (and not equal, checked above) 49 | if (upperIdx == 0) { 50 | return len; 51 | } 52 | 53 | // the element at upperIdx is the first element higher than the value we want, so return the previous element 54 | return upperIdx - 1; 55 | } 56 | 57 | /** 58 | * @notice Pushes a value into an already sorted array. 59 | * @dev Values must be pushed in increasing order as to avoid shifting values in the array. This function only 60 | * guarantees that the pushed value will not create duplicates nor make the array out of order. 61 | * @param array Array to push the value into 62 | * @param val Value to push into array. Must be greater than or equal to the highest (last) element. 63 | */ 64 | function pushSorted(uint256[] storage array, uint256 val) internal { 65 | if (array.length == 0) { 66 | array.push(val); 67 | } else { 68 | uint256 last = array[array.length - 1]; 69 | 70 | // values must be pushed in order 71 | if (val < last) { 72 | revert DecreasingValues(val, last); 73 | } 74 | 75 | // don't push duplicate values 76 | if (val != last) { 77 | array.push(val); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /contracts/libraries/MathUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 5 | 6 | library MathUtils { 7 | using SafeMath for uint256; 8 | 9 | // Divisor used for representing percentages 10 | uint256 public constant PERC_DIVISOR = 1000000; 11 | 12 | /** 13 | * @dev Returns whether an amount is a valid percentage out of PERC_DIVISOR 14 | * @param _amount Amount that is supposed to be a percentage 15 | */ 16 | function validPerc(uint256 _amount) internal pure returns (bool) { 17 | return _amount <= PERC_DIVISOR; 18 | } 19 | 20 | /** 21 | * @dev Compute percentage of a value with the percentage represented by a fraction 22 | * @param _amount Amount to take the percentage of 23 | * @param _fracNum Numerator of fraction representing the percentage 24 | * @param _fracDenom Denominator of fraction representing the percentage 25 | */ 26 | function percOf( 27 | uint256 _amount, 28 | uint256 _fracNum, 29 | uint256 _fracDenom 30 | ) internal pure returns (uint256) { 31 | return _amount.mul(percPoints(_fracNum, _fracDenom)).div(PERC_DIVISOR); 32 | } 33 | 34 | /** 35 | * @dev Compute percentage of a value with the percentage represented by a fraction over PERC_DIVISOR 36 | * @param _amount Amount to take the percentage of 37 | * @param _fracNum Numerator of fraction representing the percentage with PERC_DIVISOR as the denominator 38 | */ 39 | function percOf(uint256 _amount, uint256 _fracNum) internal pure returns (uint256) { 40 | return _amount.mul(_fracNum).div(PERC_DIVISOR); 41 | } 42 | 43 | /** 44 | * @dev Compute percentage representation of a fraction 45 | * @param _fracNum Numerator of fraction represeting the percentage 46 | * @param _fracDenom Denominator of fraction represeting the percentage 47 | */ 48 | function percPoints(uint256 _fracNum, uint256 _fracDenom) internal pure returns (uint256) { 49 | return _fracNum.mul(PERC_DIVISOR).div(_fracDenom); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /contracts/libraries/MathUtilsV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 5 | 6 | library MathUtils { 7 | using SafeMath for uint256; 8 | 9 | // Divisor used for representing percentages 10 | uint256 public constant PERC_DIVISOR = 1000000000; 11 | 12 | /** 13 | * @dev Returns whether an amount is a valid percentage out of PERC_DIVISOR 14 | * @param _amount Amount that is supposed to be a percentage 15 | */ 16 | function validPerc(uint256 _amount) internal pure returns (bool) { 17 | return _amount <= PERC_DIVISOR; 18 | } 19 | 20 | /** 21 | * @dev Compute percentage of a value with the percentage represented by a fraction 22 | * @param _amount Amount to take the percentage of 23 | * @param _fracNum Numerator of fraction representing the percentage 24 | * @param _fracDenom Denominator of fraction representing the percentage 25 | */ 26 | function percOf( 27 | uint256 _amount, 28 | uint256 _fracNum, 29 | uint256 _fracDenom 30 | ) internal pure returns (uint256) { 31 | return _amount.mul(percPoints(_fracNum, _fracDenom)).div(PERC_DIVISOR); 32 | } 33 | 34 | /** 35 | * @dev Compute percentage of a value with the percentage represented by a fraction over PERC_DIVISOR 36 | * @param _amount Amount to take the percentage of 37 | * @param _fracNum Numerator of fraction representing the percentage with PERC_DIVISOR as the denominator 38 | */ 39 | function percOf(uint256 _amount, uint256 _fracNum) internal pure returns (uint256) { 40 | return _amount.mul(_fracNum).div(PERC_DIVISOR); 41 | } 42 | 43 | /** 44 | * @dev Compute percentage representation of a fraction 45 | * @param _fracNum Numerator of fraction represeting the percentage 46 | * @param _fracDenom Denominator of fraction represeting the percentage 47 | */ 48 | function percPoints(uint256 _fracNum, uint256 _fracDenom) internal pure returns (uint256) { 49 | return _fracNum.mul(PERC_DIVISOR).div(_fracDenom); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /contracts/libraries/PreciseMathUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 5 | 6 | library PreciseMathUtils { 7 | using SafeMath for uint256; 8 | 9 | // Divisor used for representing percentages 10 | uint256 public constant PERC_DIVISOR = 10**27; 11 | 12 | /** 13 | * @dev Returns whether an amount is a valid percentage out of PERC_DIVISOR 14 | * @param _amount Amount that is supposed to be a percentage 15 | */ 16 | function validPerc(uint256 _amount) internal pure returns (bool) { 17 | return _amount <= PERC_DIVISOR; 18 | } 19 | 20 | /** 21 | * @dev Compute percentage of a value with the percentage represented by a fraction 22 | * @param _amount Amount to take the percentage of 23 | * @param _fracNum Numerator of fraction representing the percentage 24 | * @param _fracDenom Denominator of fraction representing the percentage 25 | */ 26 | function percOf( 27 | uint256 _amount, 28 | uint256 _fracNum, 29 | uint256 _fracDenom 30 | ) internal pure returns (uint256) { 31 | return _amount.mul(percPoints(_fracNum, _fracDenom)).div(PERC_DIVISOR); 32 | } 33 | 34 | /** 35 | * @dev Compute percentage of a value with the percentage represented by a fraction over PERC_DIVISOR 36 | * @param _amount Amount to take the percentage of 37 | * @param _fracNum Numerator of fraction representing the percentage with PERC_DIVISOR as the denominator 38 | */ 39 | function percOf(uint256 _amount, uint256 _fracNum) internal pure returns (uint256) { 40 | return _amount.mul(_fracNum).div(PERC_DIVISOR); 41 | } 42 | 43 | /** 44 | * @dev Compute percentage representation of a fraction 45 | * @param _fracNum Numerator of fraction represeting the percentage 46 | * @param _fracDenom Denominator of fraction represeting the percentage 47 | */ 48 | function percPoints(uint256 _fracNum, uint256 _fracDenom) internal pure returns (uint256) { 49 | return _fracNum.mul(PERC_DIVISOR).div(_fracDenom); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /contracts/pm/TicketBroker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./mixins/MixinContractRegistry.sol"; 5 | import "./mixins/MixinReserve.sol"; 6 | import "./mixins/MixinTicketBrokerCore.sol"; 7 | import "./mixins/MixinTicketProcessor.sol"; 8 | import "./mixins/MixinWrappers.sol"; 9 | 10 | contract TicketBroker is 11 | MixinContractRegistry, 12 | MixinReserve, 13 | MixinTicketBrokerCore, 14 | MixinTicketProcessor, 15 | MixinWrappers 16 | { 17 | /** 18 | * @notice TicketBroker constructor. Only invokes constructor of base Manager contract with provided Controller address 19 | * @dev This constructor will not initialize any state variables besides `controller`. The following setter functions 20 | * should be used to initialize state variables post-deployment: 21 | * - setUnlockPeriod() 22 | * - setTicketValidityPeriod() 23 | * @param _controller Address of Controller that this contract will be registered with 24 | */ 25 | constructor(address _controller) 26 | MixinContractRegistry(_controller) 27 | MixinReserve() 28 | MixinTicketBrokerCore() 29 | MixinTicketProcessor() 30 | {} 31 | 32 | /** 33 | * @notice Sets unlockPeriod value. Only callable by the Controller owner 34 | * @param _unlockPeriod Value for unlockPeriod 35 | */ 36 | function setUnlockPeriod(uint256 _unlockPeriod) external onlyControllerOwner { 37 | unlockPeriod = _unlockPeriod; 38 | 39 | emit ParameterUpdate("unlockPeriod"); 40 | } 41 | 42 | /** 43 | * @notice Sets ticketValidityPeriod value. Only callable by the Controller owner 44 | * @param _ticketValidityPeriod Value for ticketValidityPeriod 45 | */ 46 | function setTicketValidityPeriod(uint256 _ticketValidityPeriod) external onlyControllerOwner { 47 | require(_ticketValidityPeriod > 0, "ticketValidityPeriod must be greater than 0"); 48 | 49 | ticketValidityPeriod = _ticketValidityPeriod; 50 | 51 | emit ParameterUpdate("ticketValidityPeriod"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /contracts/pm/mixins/MixinContractRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../../ManagerProxyTarget.sol"; 5 | import "./interfaces/MContractRegistry.sol"; 6 | 7 | abstract contract MixinContractRegistry is MContractRegistry, ManagerProxyTarget { 8 | /** 9 | * @dev Checks if the current round has been initialized 10 | */ 11 | modifier currentRoundInitialized() override { 12 | require(roundsManager().currentRoundInitialized(), "current round is not initialized"); 13 | _; 14 | } 15 | 16 | constructor(address _controller) Manager(_controller) {} 17 | 18 | /** 19 | * @dev Returns an instance of the IBondingManager interface 20 | */ 21 | function bondingManager() internal view override returns (IBondingManager) { 22 | return IBondingManager(controller.getContract(keccak256("BondingManager"))); 23 | } 24 | 25 | /** 26 | * @dev Returns an instance of the IMinter interface 27 | */ 28 | function minter() internal view override returns (IMinter) { 29 | return IMinter(controller.getContract(keccak256("Minter"))); 30 | } 31 | 32 | /** 33 | * @dev Returns an instance of the IRoundsManager interface 34 | */ 35 | function roundsManager() internal view override returns (IRoundsManager) { 36 | return IRoundsManager(controller.getContract(keccak256("RoundsManager"))); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/pm/mixins/MixinTicketProcessor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./interfaces/MTicketProcessor.sol"; 5 | import "./MixinContractRegistry.sol"; 6 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 7 | 8 | abstract contract MixinTicketProcessor is MixinContractRegistry, MTicketProcessor { 9 | using SafeMath for uint256; 10 | 11 | // Number of rounds that a ticket is valid for starting from 12 | // its creationRound 13 | uint256 public ticketValidityPeriod; 14 | 15 | /** 16 | * @dev Process sent funds. 17 | * @param _amount Amount of funds sent 18 | */ 19 | function processFunding(uint256 _amount) internal override { 20 | // Send funds to Minter 21 | minter().depositETH{ value: _amount }(); 22 | } 23 | 24 | /** 25 | * @dev Transfer withdrawal funds for a ticket sender 26 | * @param _amount Amount of withdrawal funds 27 | */ 28 | function withdrawTransfer(address payable _sender, uint256 _amount) internal override { 29 | // Ask Minter to send withdrawal funds to the ticket sender 30 | minter().trustedWithdrawETH(_sender, _amount); 31 | } 32 | 33 | /** 34 | * @dev Transfer funds for a recipient's winning ticket 35 | * @param _recipient Address of recipient 36 | * @param _amount Amount of funds for the winning ticket 37 | * @param _auxData Auxilary data for the winning ticket 38 | */ 39 | function winningTicketTransfer( 40 | address _recipient, 41 | uint256 _amount, 42 | bytes memory _auxData 43 | ) internal override { 44 | (uint256 creationRound, ) = getCreationRoundAndBlockHash(_auxData); 45 | 46 | // Ask BondingManager to update fee pool for recipient with 47 | // winning ticket funds 48 | bondingManager().updateTranscoderWithFees(_recipient, _amount, creationRound); 49 | } 50 | 51 | /** 52 | * @dev Validates a ticket's auxilary data (succeeds or reverts) 53 | * @param _auxData Auxilary data inclueded in a ticket 54 | */ 55 | function requireValidTicketAuxData(bytes memory _auxData) internal view override { 56 | (uint256 creationRound, bytes32 creationRoundBlockHash) = getCreationRoundAndBlockHash(_auxData); 57 | bytes32 blockHash = roundsManager().blockHashForRound(creationRound); 58 | 59 | require(blockHash != bytes32(0), "ticket creationRound does not have a block hash"); 60 | require(creationRoundBlockHash == blockHash, "ticket creationRoundBlockHash invalid for creationRound"); 61 | 62 | uint256 currRound = roundsManager().currentRound(); 63 | 64 | require(creationRound.add(ticketValidityPeriod) > currRound, "ticket is expired"); 65 | } 66 | 67 | /** 68 | * @dev Returns a ticket's creationRound and creationRoundBlockHash parsed from ticket auxilary data 69 | * @param _auxData Auxilary data for a ticket 70 | * @return creationRound and creationRoundBlockHash parsed from `_auxData` 71 | */ 72 | function getCreationRoundAndBlockHash(bytes memory _auxData) 73 | internal 74 | pure 75 | returns (uint256 creationRound, bytes32 creationRoundBlockHash) 76 | { 77 | require(_auxData.length == 64, "invalid length for ticket auxData: must be 64 bytes"); 78 | 79 | // _auxData format: 80 | // Bytes [0:31] = creationRound 81 | // Bytes [32:63] = creationRoundBlockHash 82 | assembly { 83 | creationRound := mload(add(_auxData, 32)) 84 | creationRoundBlockHash := mload(add(_auxData, 64)) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /contracts/pm/mixins/MixinWrappers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./interfaces/MTicketBrokerCore.sol"; 5 | import "./MixinContractRegistry.sol"; 6 | 7 | abstract contract MixinWrappers is MixinContractRegistry, MTicketBrokerCore { 8 | /** 9 | * @notice Redeems multiple winning tickets. The function will redeem all of the provided tickets and handle any failures gracefully without reverting the entire function 10 | * @param _tickets Array of winning tickets to be redeemed in order to claim payment 11 | * @param _sigs Array of sender signatures over the hash of tickets (`_sigs[i]` corresponds to `_tickets[i]`) 12 | * @param _recipientRands Array of preimages for the recipientRandHash included in each ticket (`_recipientRands[i]` corresponds to `_tickets[i]`) 13 | */ 14 | function batchRedeemWinningTickets( 15 | Ticket[] memory _tickets, 16 | bytes[] memory _sigs, 17 | uint256[] memory _recipientRands 18 | ) public whenSystemNotPaused currentRoundInitialized { 19 | for (uint256 i = 0; i < _tickets.length; i++) { 20 | redeemWinningTicketNoRevert(_tickets[i], _sigs[i], _recipientRands[i]); 21 | } 22 | } 23 | 24 | /** 25 | * @dev Redeems a winning ticket that has been signed by a sender and reveals the 26 | recipient recipientRand that corresponds to the recipientRandHash included in the ticket 27 | This function wraps `redeemWinningTicket()` and returns false if the underlying call reverts 28 | * @param _ticket Winning ticket to be redeemed in order to claim payment 29 | * @param _sig Sender's signature over the hash of `_ticket` 30 | * @param _recipientRand The preimage for the recipientRandHash included in `_ticket` 31 | * @return success Boolean indicating whether the underlying `redeemWinningTicket()` call succeeded 32 | */ 33 | function redeemWinningTicketNoRevert( 34 | Ticket memory _ticket, 35 | bytes memory _sig, 36 | uint256 _recipientRand 37 | ) internal returns (bool success) { 38 | // ABI encode calldata for `redeemWinningTicket()` 39 | // A tuple type is used to represent the Ticket struct in the function signature 40 | bytes memory redeemWinningTicketCalldata = abi.encodeWithSignature( 41 | "redeemWinningTicket((address,address,uint256,uint256,uint256,bytes32,bytes),bytes,uint256)", 42 | _ticket, 43 | _sig, 44 | _recipientRand 45 | ); 46 | 47 | // Call `redeemWinningTicket()` 48 | // solium-disable-next-line 49 | (success, ) = address(this).call(redeemWinningTicketCalldata); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /contracts/pm/mixins/interfaces/MContractRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../../../bonding/IBondingManager.sol"; 5 | import "../../../token/IMinter.sol"; 6 | import "../../../rounds/IRoundsManager.sol"; 7 | 8 | abstract contract MContractRegistry { 9 | /** 10 | * @notice Checks if the current round has been initialized 11 | * @dev Executes the 'currentRoundInitialized' modifier in 'MixinContractRegistry' 12 | */ 13 | modifier currentRoundInitialized() virtual { 14 | _; 15 | } 16 | 17 | /** 18 | * @dev Returns an instance of the IBondingManager interface 19 | */ 20 | function bondingManager() internal view virtual returns (IBondingManager); 21 | 22 | /** 23 | * @dev Returns an instance of the IMinter interface 24 | */ 25 | function minter() internal view virtual returns (IMinter); 26 | 27 | /** 28 | * @dev Returns an instance of the IRoundsManager interface 29 | */ 30 | function roundsManager() internal view virtual returns (IRoundsManager); 31 | } 32 | -------------------------------------------------------------------------------- /contracts/pm/mixins/interfaces/MReserve.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | abstract contract MReserve { 5 | struct ReserveInfo { 6 | uint256 fundsRemaining; // Funds remaining in reserve 7 | uint256 claimedInCurrentRound; // Funds claimed from reserve in current round 8 | } 9 | 10 | // Emitted when funds are added to a reserve 11 | event ReserveFunded(address indexed reserveHolder, uint256 amount); 12 | // Emitted when funds are claimed from a reserve 13 | event ReserveClaimed(address indexed reserveHolder, address claimant, uint256 amount); 14 | 15 | /** 16 | * @notice Returns info about a reserve 17 | * @param _reserveHolder Address of reserve holder 18 | * @return info Info about the reserve for `_reserveHolder` 19 | */ 20 | function getReserveInfo(address _reserveHolder) public view virtual returns (ReserveInfo memory info); 21 | 22 | /** 23 | * @notice Returns the amount of funds claimed by a claimant from a reserve 24 | * @param _reserveHolder Address of reserve holder 25 | * @param _claimant Address of claimant 26 | * @return Amount of funds claimed by `_claimant` from the reserve for `_reserveHolder` 27 | */ 28 | function claimedReserve(address _reserveHolder, address _claimant) public view virtual returns (uint256); 29 | 30 | /** 31 | * @dev Adds funds to a reserve 32 | * @param _reserveHolder Address of reserve holder 33 | * @param _amount Amount of funds to add to reserve 34 | */ 35 | function addReserve(address _reserveHolder, uint256 _amount) internal virtual; 36 | 37 | /** 38 | * @dev Clears contract storage used for a reserve 39 | * @param _reserveHolder Address of reserve holder 40 | */ 41 | function clearReserve(address _reserveHolder) internal virtual; 42 | 43 | /** 44 | * @dev Claims funds from a reserve 45 | * @param _reserveHolder Address of reserve holder 46 | * @param _claimant Address of claimant 47 | * @param _amount Amount of funds to claim from the reserve 48 | * @return Amount of funds (<= `_amount`) claimed by `_claimant` from the reserve for `_reserveHolder` 49 | */ 50 | function claimFromReserve( 51 | address _reserveHolder, 52 | address _claimant, 53 | uint256 _amount 54 | ) internal virtual returns (uint256); 55 | 56 | /** 57 | * @dev Returns the amount of funds remaining in a reserve 58 | * @param _reserveHolder Address of reserve holder 59 | * @return Amount of funds remaining in the reserve for `_reserveHolder` 60 | */ 61 | function remainingReserve(address _reserveHolder) internal view virtual returns (uint256); 62 | } 63 | -------------------------------------------------------------------------------- /contracts/pm/mixins/interfaces/MTicketBrokerCore.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | abstract contract MTicketBrokerCore { 5 | struct Ticket { 6 | address recipient; // Address of ticket recipient 7 | address sender; // Address of ticket sender 8 | uint256 faceValue; // Face value of ticket paid to recipient if ticket wins 9 | uint256 winProb; // Probability ticket will win represented as winProb / (2^256 - 1) 10 | uint256 senderNonce; // Sender's monotonically increasing counter for each ticket 11 | bytes32 recipientRandHash; // keccak256 hash commitment to recipient's random value 12 | bytes auxData; // Auxilary data included in ticket used for additional validation 13 | } 14 | 15 | // Emitted when funds are added to a sender's deposit 16 | event DepositFunded(address indexed sender, uint256 amount); 17 | // Emitted when a winning ticket is redeemed 18 | event WinningTicketRedeemed( 19 | address indexed sender, 20 | address indexed recipient, 21 | uint256 faceValue, 22 | uint256 winProb, 23 | uint256 senderNonce, 24 | uint256 recipientRand, 25 | bytes auxData 26 | ); 27 | // Emitted when a funds transfer for a winning ticket redemption is executed 28 | event WinningTicketTransfer(address indexed sender, address indexed recipient, uint256 amount); 29 | // Emitted when a sender requests an unlock 30 | event Unlock(address indexed sender, uint256 startRound, uint256 endRound); 31 | // Emitted when a sender cancels an unlock 32 | event UnlockCancelled(address indexed sender); 33 | // Emitted when a sender withdraws its deposit & reserve 34 | event Withdrawal(address indexed sender, uint256 deposit, uint256 reserve); 35 | } 36 | -------------------------------------------------------------------------------- /contracts/pm/mixins/interfaces/MTicketProcessor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | abstract contract MTicketProcessor { 5 | /** 6 | * @dev Process sent funds. 7 | * @param _amount Amount of funds sent 8 | */ 9 | function processFunding(uint256 _amount) internal virtual; 10 | 11 | /** 12 | * @dev Transfer withdrawal funds for a ticket sender 13 | * @param _amount Amount of withdrawal funds 14 | */ 15 | function withdrawTransfer(address payable _sender, uint256 _amount) internal virtual; 16 | 17 | /** 18 | * @dev Transfer funds for a recipient's winning ticket 19 | * @param _recipient Address of recipient 20 | * @param _amount Amount of funds for the winning ticket 21 | * @param _auxData Auxilary data for the winning ticket 22 | */ 23 | function winningTicketTransfer( 24 | address _recipient, 25 | uint256 _amount, 26 | bytes memory _auxData 27 | ) internal virtual; 28 | 29 | /** 30 | * @dev Validates a ticket's auxilary data (succeeds or reverts) 31 | * @param _auxData Auxilary data inclueded in a ticket 32 | */ 33 | function requireValidTicketAuxData(bytes memory _auxData) internal view virtual; 34 | } 35 | -------------------------------------------------------------------------------- /contracts/polling/Poll.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | contract Poll { 5 | // The block at which the poll ends and votes can no longer be submitted. 6 | uint256 public endBlock; 7 | 8 | // Vote is emitted when an account submits a vote with 'choiceID'. 9 | // This event can be indexed to tally all votes for each choiceID 10 | event Vote(address indexed voter, uint256 choiceID); 11 | 12 | modifier isActive() { 13 | require(block.number <= endBlock, "poll is over"); 14 | _; 15 | } 16 | 17 | constructor(uint256 _endBlock) { 18 | endBlock = _endBlock; 19 | } 20 | 21 | /** 22 | * @dev Vote for the poll's proposal. 23 | * Reverts if the poll period is over. 24 | * @param _choiceID the ID of the option to vote for 25 | */ 26 | function vote(uint256 _choiceID) external isActive { 27 | emit Vote(msg.sender, _choiceID); 28 | } 29 | 30 | /** 31 | * @dev Destroy the Poll contract after the poll has finished 32 | * Reverts if the poll is still active 33 | */ 34 | function destroy() external { 35 | require(block.number > endBlock, "poll is active"); 36 | selfdestruct(payable(msg.sender)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/polling/PollCreator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./Poll.sol"; 5 | 6 | interface IBondingManager { 7 | function transcoderTotalStake(address _addr) external view returns (uint256); 8 | 9 | function pendingStake(address _addr, uint256 _endRound) external view returns (uint256); 10 | } 11 | 12 | contract PollCreator { 13 | // 33.33% 14 | uint256 public constant QUORUM = 333300; 15 | // 50% 16 | uint256 public constant QUOTA = 500000; 17 | // 10 rounds 18 | uint256 public constant POLL_PERIOD = 10 * 5760; 19 | uint256 public constant POLL_CREATION_COST = 100 * 1 ether; 20 | 21 | IBondingManager public bondingManager; 22 | 23 | event PollCreated(address indexed poll, bytes proposal, uint256 endBlock, uint256 quorum, uint256 quota); 24 | 25 | constructor(address _bondingManagerAddr) { 26 | bondingManager = IBondingManager(_bondingManagerAddr); 27 | } 28 | 29 | /** 30 | * @notice Create a poll if caller has POLL_CREATION_COST LPT stake (own stake or stake delegated to it). 31 | * @param _proposal The IPFS multihash for the proposal. 32 | */ 33 | function createPoll(bytes calldata _proposal) external { 34 | require( 35 | // pendingStake() ignores the second arg 36 | bondingManager.pendingStake(msg.sender, 0) >= POLL_CREATION_COST || 37 | bondingManager.transcoderTotalStake(msg.sender) >= POLL_CREATION_COST, 38 | "PollCreator#createPoll: INSUFFICIENT_STAKE" 39 | ); 40 | 41 | uint256 endBlock = block.number + POLL_PERIOD; 42 | Poll poll = new Poll(endBlock); 43 | 44 | emit PollCreated(address(poll), _proposal, endBlock, QUORUM, QUOTA); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/rounds/AdjustableRoundsManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./RoundsManager.sol"; 5 | 6 | contract AdjustableRoundsManager is RoundsManager { 7 | uint256 public num; 8 | bytes32 public hash; 9 | 10 | constructor(address _controller) RoundsManager(_controller) {} 11 | 12 | function setBlockNum(uint256 _num) external { 13 | num = _num; 14 | } 15 | 16 | function setBlockHash(bytes32 _hash) external { 17 | hash = _hash; 18 | } 19 | 20 | function mineBlocks(uint256 _blocks) external { 21 | num += _blocks; 22 | } 23 | 24 | function blockNum() public view override returns (uint256) { 25 | return num; 26 | } 27 | 28 | function blockHash(uint256 _block) public view override returns (bytes32) { 29 | require(_block >= blockNum() - 256); 30 | 31 | return hash; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/rounds/IRoundsManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | /** 5 | * @title RoundsManager interface 6 | */ 7 | interface IRoundsManager { 8 | // Events 9 | event NewRound(uint256 indexed round, bytes32 blockHash); 10 | 11 | // Deprecated events 12 | // These event signatures can be used to construct the appropriate topic hashes to filter for past logs corresponding 13 | // to these deprecated events. 14 | // event NewRound(uint256 round) 15 | 16 | // External functions 17 | function initializeRound() external; 18 | 19 | function lipUpgradeRound(uint256 _lip) external view returns (uint256); 20 | 21 | // Public functions 22 | function blockNum() external view returns (uint256); 23 | 24 | function blockHash(uint256 _block) external view returns (bytes32); 25 | 26 | function blockHashForRound(uint256 _round) external view returns (bytes32); 27 | 28 | function currentRound() external view returns (uint256); 29 | 30 | function currentRoundStartBlock() external view returns (uint256); 31 | 32 | function currentRoundInitialized() external view returns (bool); 33 | 34 | function currentRoundLocked() external view returns (bool); 35 | } 36 | -------------------------------------------------------------------------------- /contracts/snapshots/IMerkleSnapshot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | interface IMerkleSnapshot { 5 | function verify( 6 | bytes32 _id, 7 | bytes32[] calldata _proof, 8 | bytes32 _leaf 9 | ) external view returns (bool); 10 | } 11 | -------------------------------------------------------------------------------- /contracts/snapshots/MerkleSnapshot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../zeppelin/MerkleProof.sol"; 5 | import "../Manager.sol"; 6 | 7 | contract MerkleSnapshot is Manager { 8 | mapping(bytes32 => bytes32) public snapshot; 9 | 10 | constructor(address _controller) Manager(_controller) {} 11 | 12 | function setSnapshot(bytes32 _id, bytes32 _root) external onlyControllerOwner { 13 | snapshot[_id] = _root; 14 | } 15 | 16 | function verify( 17 | bytes32 _id, 18 | bytes32[] calldata _proof, 19 | bytes32 _leaf 20 | ) external view returns (bool) { 21 | return MerkleProof.verify(_proof, snapshot[_id], _leaf); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/test/TestEarningsPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./mocks/EarningsPoolFixture.sol"; 5 | import "./helpers/truffle/Assert.sol"; 6 | 7 | contract TestEarningsPool { 8 | EarningsPoolFixture fixture; 9 | 10 | function beforeEach() public { 11 | fixture = new EarningsPoolFixture(); 12 | fixture.setStake(1000); 13 | fixture.setCommission(500000, 500000); 14 | } 15 | 16 | function test_setCommission() public { 17 | fixture.setCommission(5, 10); 18 | uint256 transcoderRewardCut = fixture.getTranscoderRewardCut(); 19 | uint256 transcoderFeeShare = fixture.getTranscoderFeeShare(); 20 | Assert.equal(transcoderRewardCut, 5, "wrong transcoderRewardCut"); 21 | Assert.equal(transcoderFeeShare, 10, "wrong transcoderFeeShare"); 22 | } 23 | 24 | function test_setStake() public { 25 | fixture.setStake(5000); 26 | uint256 totalStake = fixture.getTotalStake(); 27 | Assert.equal(totalStake, 5000, "wrong totalStake"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contracts/test/TestMathUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../libraries/MathUtils.sol"; 5 | import "./helpers/truffle/Assert.sol"; 6 | 7 | contract TestMathUtils { 8 | function test_validPerc() public { 9 | Assert.equal(MathUtils.validPerc(50), true, "50 should be a valid percentage"); 10 | Assert.equal(MathUtils.validPerc(0), true, "0 should be a valid percentage"); 11 | Assert.equal(MathUtils.validPerc(1000000), true, "the max should be a valid percentage"); 12 | Assert.equal(MathUtils.validPerc(1000001), false, "1 more than the max should not be valid percentage"); 13 | } 14 | 15 | function test_percOf1() public { 16 | Assert.equal(MathUtils.percOf(100, 3, 4), 75, "3/4 of 100 should be 75"); 17 | Assert.equal(MathUtils.percOf(100, 7, 9), 77, "7/9 of 100 should be 77"); 18 | } 19 | 20 | function test_percOf2() public { 21 | Assert.equal(MathUtils.percOf(100, 3), 0, ".0003% of 100 is 0"); 22 | Assert.equal(MathUtils.percOf(100, 100000), 10, "10% of 100 is 10"); 23 | } 24 | 25 | function test_percPoints() public { 26 | Assert.equal(MathUtils.percPoints(3, 4), 750000, "3/4 should convert to valid percentage"); 27 | Assert.equal(MathUtils.percPoints(100, 300), 333333, "100/300 should convert to valid percentage"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contracts/test/TestMathUtilsV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../libraries/MathUtilsV2.sol"; 5 | import "./helpers/truffle/Assert.sol"; 6 | 7 | contract TestMathUtilsV2 { 8 | function test_validPerc() public { 9 | Assert.equal(MathUtils.validPerc(50), true, "50 should be a valid percentage"); 10 | Assert.equal(MathUtils.validPerc(0), true, "0 should be a valid percentage"); 11 | Assert.equal(MathUtils.validPerc(1000000000), true, "the max should be a valid percentage"); 12 | Assert.equal(MathUtils.validPerc(1000000001), false, "1 more than the max should not be valid percentage"); 13 | } 14 | 15 | function test_percOf1() public { 16 | Assert.equal(MathUtils.percOf(100, 3, 4), 75, "3/4 of 100 should be 75"); 17 | Assert.equal(MathUtils.percOf(100, 7, 9), 77, "7/9 of 100 should be 77"); 18 | } 19 | 20 | function test_percOf2() public { 21 | Assert.equal(MathUtils.percOf(100, 3), 0, ".0000003% of 100 is 0"); 22 | Assert.equal(MathUtils.percOf(1000000000, 1), 1, ".0000001% of 1000000000 is 1"); 23 | Assert.equal(MathUtils.percOf(100, 100000000), 10, "10% of 100 is 10"); 24 | } 25 | 26 | function test_percPoints() public { 27 | Assert.equal(MathUtils.percPoints(3, 4), 750000000, "3/4 should convert to valid percentage"); 28 | Assert.equal(MathUtils.percPoints(100, 300), 333333333, "100/300 should convert to valid percentage"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /contracts/test/TestPreciseMathUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../libraries/PreciseMathUtils.sol"; 5 | import "./helpers/truffle/Assert.sol"; 6 | 7 | contract TestPreciseMathUtils { 8 | function test_validPerc() public { 9 | Assert.equal(PreciseMathUtils.validPerc(50), true, "50 should be a valid percentage"); 10 | Assert.equal(PreciseMathUtils.validPerc(0), true, "0 should be a valid percentage"); 11 | Assert.equal(PreciseMathUtils.validPerc(10**27), true, "the max should be a valid percentage"); 12 | Assert.equal( 13 | PreciseMathUtils.validPerc(10**27 + 1), 14 | false, 15 | "1 more than the max should not be valid percentage" 16 | ); 17 | } 18 | 19 | function test_percOf1() public { 20 | Assert.equal(PreciseMathUtils.percOf(100, 3, 4), 75, "3/4 of 100 should be 75"); 21 | Assert.equal(PreciseMathUtils.percOf(100, 7, 9), 77, "7/9 of 100 should be 77"); 22 | } 23 | 24 | function test_percOf2() public { 25 | Assert.equal(PreciseMathUtils.percOf(100, 3), 0, ".0000000000000000000000003% of 100 is 0"); 26 | Assert.equal(PreciseMathUtils.percOf(10**27, 1), 1, ".0000000000000000000000001% of 1000000000 is 1"); 27 | Assert.equal(PreciseMathUtils.percOf(100, 10**27 / 10), 10, "10% of 100 is 10"); 28 | } 29 | 30 | function test_percPoints() public { 31 | Assert.equal( 32 | PreciseMathUtils.percPoints(3, 4), 33 | 750000000000000000000000000, 34 | "3/4 should convert to valid percentage" 35 | ); 36 | Assert.equal( 37 | PreciseMathUtils.percPoints(100, 300), 38 | 333333333333333333333333333, 39 | "100/300 should convert to valid percentage" 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/test/TestSortedArrays.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./helpers/truffle/Assert.sol"; 5 | import "./helpers/RevertProxy.sol"; 6 | import "./mocks/SortedArraysFixture.sol"; 7 | 8 | contract TestSortedArrays { 9 | RevertProxy proxy; 10 | SortedArraysFixture fixture; 11 | 12 | function beforeAll() public { 13 | proxy = new RevertProxy(); 14 | } 15 | 16 | function beforeEach() public { 17 | fixture = new SortedArraysFixture(); 18 | } 19 | 20 | function test_pushSorted_addsElements() public { 21 | fixture.pushSorted(3); 22 | Assert.equal(fixture.length(), 1, "incorrect array length"); 23 | Assert.equal(fixture.array(0), 3, "incorrect value in array"); 24 | 25 | fixture.pushSorted(4); 26 | Assert.equal(fixture.length(), 2, "incorrect array length"); 27 | Assert.equal(fixture.array(0), 3, "incorrect value in array"); 28 | Assert.equal(fixture.array(1), 4, "incorrect value in array"); 29 | } 30 | 31 | function test_pushSorted_avoidsDuplicates() public { 32 | fixture.pushSorted(4); 33 | Assert.equal(fixture.length(), 1, "incorrect array length"); 34 | Assert.equal(fixture.array(0), 4, "incorrect value in array"); 35 | 36 | fixture.pushSorted(4); 37 | Assert.equal(fixture.length(), 1, "incorrect array length"); 38 | } 39 | 40 | function test_pushSorted_revertsOnDecreasing() public { 41 | fixture.pushSorted(4); 42 | Assert.equal(fixture.length(), 1, "incorrect array length"); 43 | Assert.equal(fixture.array(0), 4, "incorrect value in array"); 44 | 45 | SortedArraysFixture(address(proxy)).pushSorted(3); 46 | bool ok = proxy.execute(address(fixture)); 47 | Assert.isFalse(ok, "did not revert"); 48 | } 49 | 50 | function test_findLowerBound_lowerThanElement() public { 51 | fixture.pushSorted(2); 52 | fixture.pushSorted(4); 53 | fixture.pushSorted(7); 54 | fixture.pushSorted(11); 55 | 56 | Assert.equal(fixture.findLowerBound(3), 0, "found incorrect element"); 57 | Assert.equal(fixture.findLowerBound(6), 1, "found incorrect element"); 58 | Assert.equal(fixture.findLowerBound(10), 2, "found incorrect element"); 59 | Assert.equal(fixture.findLowerBound(15), 3, "found incorrect element"); 60 | } 61 | 62 | function test_findLowerBound_exactElement() public { 63 | fixture.pushSorted(3); 64 | fixture.pushSorted(5); 65 | fixture.pushSorted(8); 66 | fixture.pushSorted(13); 67 | 68 | Assert.equal(fixture.findLowerBound(3), 0, "found incorrect element"); 69 | Assert.equal(fixture.findLowerBound(5), 1, "found incorrect element"); 70 | Assert.equal(fixture.findLowerBound(8), 2, "found incorrect element"); 71 | Assert.equal(fixture.findLowerBound(13), 3, "found incorrect element"); 72 | } 73 | 74 | function test_findLowerBound_returnsLengthOnEmpty() public { 75 | Assert.equal(fixture.length(), 0, "incorrect array length"); 76 | Assert.equal(fixture.findLowerBound(3), 0, "incorrect return on empty array"); 77 | } 78 | 79 | function test_findLowerBound_returnsLengthOnNotFound() public { 80 | fixture.pushSorted(8); 81 | fixture.pushSorted(13); 82 | 83 | Assert.equal(fixture.findLowerBound(22), 1, "found incorrect element"); 84 | // looking for a value lower than min should return the array length 85 | Assert.equal(fixture.findLowerBound(5), 2, "incorrect return on not found"); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /contracts/test/TestSortedDoublyLLRemove.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./mocks/SortedDoublyLLFixture.sol"; 5 | import "./helpers/RevertProxy.sol"; 6 | import "./helpers/truffle/Assert.sol"; 7 | 8 | contract TestSortedDoublyLLRemove { 9 | address[] ids = [address(1), address(2), address(3), address(4), address(5), address(6)]; 10 | uint256[] keys = [uint256(13), uint256(11), uint256(9), uint256(7), uint256(5), uint256(3)]; 11 | 12 | SortedDoublyLLFixture fixture; 13 | RevertProxy proxy; 14 | 15 | function beforeAll() public { 16 | proxy = new RevertProxy(); 17 | } 18 | 19 | function beforeEach() public { 20 | fixture = new SortedDoublyLLFixture(); 21 | fixture.setMaxSize(10); 22 | } 23 | 24 | function test_remove() public { 25 | fixture.insert(ids[0], keys[0], address(0), address(0)); 26 | fixture.insert(ids[1], keys[1], ids[0], address(0)); 27 | fixture.insert(ids[2], keys[2], ids[1], address(0)); 28 | 29 | fixture.remove(ids[1]); 30 | Assert.equal(fixture.contains(ids[1]), false, "should not contain node"); 31 | Assert.equal(fixture.getSize(), 2, "wrong size"); 32 | Assert.equal(fixture.getNext(ids[0]), ids[2], "wrong next"); 33 | Assert.equal(fixture.getPrev(ids[2]), ids[0], "wrong prev"); 34 | } 35 | 36 | function test_remove_singleNode() public { 37 | fixture.insert(ids[0], keys[0], address(0), address(0)); 38 | 39 | fixture.remove(ids[0]); 40 | Assert.equal(fixture.contains(ids[0]), false, "should not contain node"); 41 | Assert.equal(fixture.getSize(), 0, "wrong size"); 42 | Assert.equal(fixture.getFirst(), address(0), "wrong head"); 43 | Assert.equal(fixture.getLast(), address(0), "wrong tail"); 44 | } 45 | 46 | function test_remove_head() public { 47 | fixture.insert(ids[0], keys[0], address(0), address(0)); 48 | fixture.insert(ids[1], keys[1], ids[0], address(0)); 49 | 50 | fixture.remove(ids[0]); 51 | Assert.equal(fixture.contains(ids[0]), false, "should not contain node"); 52 | Assert.equal(fixture.getSize(), 1, "wrong size"); 53 | Assert.equal(fixture.getFirst(), ids[1], "wrong head"); 54 | Assert.equal(fixture.getPrev(ids[1]), address(0), "wrong prev"); 55 | } 56 | 57 | function test_remove_tail() public { 58 | fixture.insert(ids[0], keys[0], address(0), address(0)); 59 | fixture.insert(ids[1], keys[1], ids[0], address(0)); 60 | 61 | fixture.remove(ids[1]); 62 | Assert.equal(fixture.contains(ids[1]), false, "should not contain node"); 63 | Assert.equal(fixture.getSize(), 1, "wrong size"); 64 | Assert.equal(fixture.getLast(), ids[0], "wrong prev"); 65 | Assert.equal(fixture.getNext(ids[0]), address(0), "wrong next"); 66 | } 67 | 68 | function test_remove_notInList() public { 69 | fixture.insert(ids[0], keys[0], address(0), address(0)); 70 | 71 | SortedDoublyLLFixture(address(proxy)).remove(ids[1]); 72 | bool result = proxy.execute(address(fixture)); 73 | Assert.isFalse(result, "did not revert"); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /contracts/test/TestSortedDoublyLLUpdateKey.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./mocks/SortedDoublyLLFixture.sol"; 5 | import "./helpers/RevertProxy.sol"; 6 | import "./helpers/truffle/Assert.sol"; 7 | 8 | contract TestSortedDoublyLLUpdateKey { 9 | address[] ids = [address(1), address(2), address(3), address(4), address(5), address(6)]; 10 | uint256[] keys = [uint256(13), uint256(11), uint256(9), uint256(7), uint256(5), uint256(3)]; 11 | 12 | SortedDoublyLLFixture fixture; 13 | RevertProxy proxy; 14 | 15 | function beforeAll() public { 16 | proxy = new RevertProxy(); 17 | } 18 | 19 | function beforeEach() public { 20 | fixture = new SortedDoublyLLFixture(); 21 | fixture.setMaxSize(10); 22 | } 23 | 24 | function test_updateKey_missingId() public { 25 | SortedDoublyLLFixture(address(proxy)).updateKey(ids[3], 5, address(0), address(0)); 26 | bool result = proxy.execute(address(fixture)); 27 | Assert.isFalse(result, "did not revert"); 28 | } 29 | 30 | function test_updateKey_increaseNoHint() public { 31 | fixture.insert(ids[0], keys[0], address(0), address(0)); 32 | fixture.insert(ids[1], keys[1], ids[0], address(0)); 33 | fixture.insert(ids[2], keys[2], ids[1], address(0)); 34 | fixture.insert(ids[3], keys[3], ids[2], address(0)); 35 | fixture.insert(ids[4], keys[4], ids[3], address(0)); 36 | fixture.insert(ids[5], keys[5], ids[4], address(0)); 37 | 38 | uint256 newKey = keys[3] + 3; 39 | fixture.updateKey(ids[3], newKey, address(0), address(0)); 40 | Assert.equal(fixture.getKey(ids[3]), newKey, "wrong key"); 41 | Assert.equal(fixture.getNext(ids[3]), ids[2], "wrong next"); 42 | Assert.equal(fixture.getPrev(ids[3]), ids[1], "wrong prev"); 43 | Assert.equal(fixture.getNext(ids[1]), ids[3], "wrong next"); 44 | Assert.equal(fixture.getPrev(ids[2]), ids[3], "wrong prev"); 45 | } 46 | 47 | function test_updateKey_decreaseNoHint() public { 48 | fixture.insert(ids[0], keys[0], address(0), address(0)); 49 | fixture.insert(ids[1], keys[1], ids[0], address(0)); 50 | fixture.insert(ids[2], keys[2], ids[1], address(0)); 51 | fixture.insert(ids[3], keys[3], ids[2], address(0)); 52 | fixture.insert(ids[4], keys[4], ids[3], address(0)); 53 | fixture.insert(ids[5], keys[5], ids[4], address(0)); 54 | 55 | uint256 newKey = keys[3] - 3; 56 | fixture.updateKey(ids[3], newKey, address(0), address(0)); 57 | Assert.equal(fixture.getKey(ids[3]), newKey, "wrong key"); 58 | Assert.equal(fixture.getNext(ids[3]), ids[5], "wrong next"); 59 | Assert.equal(fixture.getPrev(ids[3]), ids[4], "wrong prev"); 60 | Assert.equal(fixture.getNext(ids[4]), ids[3], "wrong next"); 61 | Assert.equal(fixture.getPrev(ids[5]), ids[3], "wrong prev"); 62 | } 63 | 64 | function test_updateKey_zeroNewKey() public { 65 | fixture.insert(ids[0], keys[0], address(0), address(0)); 66 | fixture.insert(ids[1], keys[1], ids[0], address(0)); 67 | fixture.insert(ids[2], keys[2], ids[1], address(0)); 68 | 69 | uint256 newKey = 0; 70 | fixture.updateKey(ids[2], newKey, address(0), address(0)); 71 | Assert.isFalse(fixture.contains(ids[2]), "list should not contain id after updating with newKey = 0"); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /contracts/test/helpers/RevertProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | contract RevertProxy { 5 | bytes data; 6 | 7 | fallback() external { 8 | data = msg.data; 9 | } 10 | 11 | // solium-disable security/no-low-level-calls 12 | function execute(address _target) external returns (bool) { 13 | (bool ok, ) = _target.call(data); 14 | return ok; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/test/helpers/truffle/AssertGeneral.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | library AssertGeneral { 5 | /* 6 | Event: TestEvent 7 | 8 | Fired when an assertion is made. 9 | 10 | Params: 11 | result (bool) - Whether or not the assertion holds. 12 | message (string) - A message to display if the assertion does not hold. 13 | */ 14 | event TestEvent(bool indexed result, string message); 15 | 16 | // ************************************** general ************************************** 17 | 18 | /* 19 | Function: fail() 20 | 21 | Mark the test as failed. 22 | 23 | Params: 24 | message (string) - A message associated with the failure. 25 | 26 | Returns: 27 | result (bool) - false. 28 | */ 29 | function fail(string memory message) public returns (bool result) { 30 | _report(false, message); 31 | return false; 32 | } 33 | 34 | /******************************** internal ********************************/ 35 | 36 | /* 37 | Function: _report 38 | 39 | Internal function for triggering . 40 | 41 | Params: 42 | result (bool) - The test result (true or false). 43 | message (string) - The message that is sent if the assertion fails. 44 | */ 45 | function _report(bool result, string memory message) internal { 46 | if (result) emit TestEvent(true, ""); 47 | else emit TestEvent(false, message); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /contracts/test/mocks/AlphaJobsManagerMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | contract AlphaJobsManagerMock { 5 | struct Broadcaster { 6 | uint256 deposit; 7 | uint256 withdrawBlock; 8 | } 9 | 10 | mapping(address => Broadcaster) internal mockBroadcasters; 11 | 12 | function setBroadcaster( 13 | address _addr, 14 | uint256 _deposit, 15 | uint256 _withdrawBlock 16 | ) external { 17 | mockBroadcasters[_addr].deposit = _deposit; 18 | mockBroadcasters[_addr].withdrawBlock = _withdrawBlock; 19 | } 20 | 21 | function broadcasters(address _addr) public view returns (uint256 deposit, uint256 withdrawBlock) { 22 | deposit = mockBroadcasters[_addr].deposit; 23 | withdrawBlock = mockBroadcasters[_addr].withdrawBlock; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/test/mocks/BondingVotesERC5805Harness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../../bonding/BondingVotes.sol"; 5 | import "./GenericMock.sol"; 6 | 7 | /** 8 | * @dev This is a tets utility for unit tests on the ERC5805 functions of the BondingVotes contract. It overrides the 9 | * functions that should be used to derive the values returned by the ERC5805 functions and checks against those. 10 | */ 11 | contract BondingVotesERC5805Harness is BondingVotes { 12 | constructor(address _controller) BondingVotes(_controller) {} 13 | 14 | /** 15 | * @dev Mocked version that returns transformed version of the input for testing. 16 | * @return amount lowest 4 bytes of address + _round 17 | * @return delegateAddress (_account << 4) | _round. 18 | */ 19 | function getVotesAndDelegateAtRoundStart(address _account, uint256 _round) 20 | public 21 | pure 22 | override 23 | returns (uint256 amount, address delegateAddress) 24 | { 25 | uint160 intAddr = uint160(_account); 26 | 27 | amount = (intAddr & 0xffffffff) + _round; 28 | delegateAddress = address((intAddr << 4) | uint160(_round)); 29 | } 30 | 31 | function getTotalActiveStakeAt(uint256 _round) public pure override returns (uint256) { 32 | return 4 * _round; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/test/mocks/BondingVotesMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./GenericMock.sol"; 5 | 6 | contract BondingVotesMock is GenericMock { 7 | event CheckpointBondingState( 8 | address account, 9 | uint256 startRound, 10 | uint256 bondedAmount, 11 | address delegateAddress, 12 | uint256 delegatedAmount, 13 | uint256 lastClaimRound, 14 | uint256 lastRewardRound 15 | ); 16 | event CheckpointTotalActiveStake(uint256 totalStake, uint256 round); 17 | 18 | function checkpointBondingState( 19 | address _account, 20 | uint256 _startRound, 21 | uint256 _bondedAmount, 22 | address _delegateAddress, 23 | uint256 _delegatedAmount, 24 | uint256 _lastClaimRound, 25 | uint256 _lastRewardRound 26 | ) external { 27 | emit CheckpointBondingState( 28 | _account, 29 | _startRound, 30 | _bondedAmount, 31 | _delegateAddress, 32 | _delegatedAmount, 33 | _lastClaimRound, 34 | _lastRewardRound 35 | ); 36 | } 37 | 38 | function checkpointTotalActiveStake(uint256 _totalStake, uint256 _round) external { 39 | emit CheckpointTotalActiveStake(_totalStake, _round); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contracts/test/mocks/EarningsPoolFixture.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../../libraries/MathUtils.sol"; 5 | import "../../bonding/libraries/EarningsPool.sol"; 6 | import "../../bonding/libraries/EarningsPoolLIP36.sol"; 7 | 8 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 9 | 10 | contract EarningsPoolFixture { 11 | using SafeMath for uint256; 12 | using EarningsPool for EarningsPool.Data; 13 | using EarningsPoolLIP36 for EarningsPool.Data; 14 | 15 | EarningsPool.Data prevPool; 16 | EarningsPool.Data pool; 17 | 18 | function setCommission(uint256 _rewardCut, uint256 _feeShare) public { 19 | pool.setCommission(_rewardCut, _feeShare); 20 | } 21 | 22 | function setStake(uint256 _stake) public { 23 | pool.setStake(_stake); 24 | } 25 | 26 | function updateCumulativeFeeFactor(uint256 _fees) public { 27 | pool.updateCumulativeFeeFactor(prevPool, _fees); 28 | } 29 | 30 | function updateCumulativeRewardFactor(uint256 _rewards) public { 31 | pool.updateCumulativeRewardFactor(prevPool, _rewards); 32 | } 33 | 34 | function setPrevPoolEarningsFactors(uint256 _cumulativeFeeFactor, uint256 _cumulativeRewardFactor) public { 35 | prevPool.cumulativeFeeFactor = _cumulativeFeeFactor; 36 | prevPool.cumulativeRewardFactor = _cumulativeRewardFactor; 37 | } 38 | 39 | function setEarningsFactors(uint256 _cumulativeFeeFactor, uint256 _cumulativeRewardFactor) public { 40 | pool.cumulativeFeeFactor = _cumulativeFeeFactor; 41 | pool.cumulativeRewardFactor = _cumulativeRewardFactor; 42 | } 43 | 44 | function getTranscoderRewardCut() public view returns (uint256) { 45 | return pool.transcoderRewardCut; 46 | } 47 | 48 | function getTranscoderFeeShare() public view returns (uint256) { 49 | return pool.transcoderFeeShare; 50 | } 51 | 52 | function getTotalStake() public view returns (uint256) { 53 | return pool.totalStake; 54 | } 55 | 56 | function getCumulativeRewardFactor() public view returns (uint256) { 57 | return pool.cumulativeRewardFactor; 58 | } 59 | 60 | function getCumulativeFeeFactor() public view returns (uint256) { 61 | return pool.cumulativeFeeFactor; 62 | } 63 | 64 | function delegatorCumulativeStake(uint256 _stake) public view returns (uint256) { 65 | return EarningsPoolLIP36.delegatorCumulativeStake(prevPool, pool, _stake); 66 | } 67 | 68 | function delegatorCumulativeFees(uint256 _stake, uint256 _fees) public view returns (uint256) { 69 | return EarningsPoolLIP36.delegatorCumulativeFees(prevPool, pool, _stake, _fees); 70 | } 71 | 72 | function delegatorCumulativeStakeAndFees(uint256 _stake, uint256 _fees) 73 | public 74 | view 75 | returns (uint256 cStake, uint256 cFees) 76 | { 77 | return EarningsPoolLIP36.delegatorCumulativeStakeAndFees(prevPool, pool, _stake, _fees); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /contracts/test/mocks/GovenorInterfacesFixture.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "@openzeppelin/contracts-upgradeable/governance/IGovernorUpgradeable.sol"; 5 | import "@openzeppelin/contracts-upgradeable/governance/extensions/IGovernorTimelockUpgradeable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155ReceiverUpgradeable.sol"; 7 | 8 | /** 9 | * @dev This is a helper contract to return the expected interface values that the LivepeerGovenor interface should 10 | * support. This only exists in Solidity since generating these interfaces in JS is kinda of a pain. 11 | */ 12 | contract GovernorInterfacesFixture { 13 | function TimelockUpgradeableInterface() external pure returns (bytes4) { 14 | return type(IGovernorTimelockUpgradeable).interfaceId; 15 | } 16 | 17 | /** 18 | * @dev ID calculation logic copied from {GovernorUpgradeable-supportsInterface}. 19 | */ 20 | function GovernorInterfaces() external pure returns (bytes4[] memory) { 21 | IGovernorUpgradeable governor; 22 | // 23 | bytes4 governorCancelId = governor.cancel.selector ^ governor.proposalProposer.selector; 24 | 25 | bytes4 governorParamsId = governor.castVoteWithReasonAndParams.selector ^ 26 | governor.castVoteWithReasonAndParamsBySig.selector ^ 27 | governor.getVotesWithParams.selector; 28 | 29 | // The original interface id in v4.3. 30 | bytes4 governor43Id = type(IGovernorUpgradeable).interfaceId ^ 31 | type(IERC6372Upgradeable).interfaceId ^ 32 | governorCancelId ^ 33 | governorParamsId; 34 | 35 | // An updated interface id in v4.6, with params added. 36 | bytes4 governor46Id = type(IGovernorUpgradeable).interfaceId ^ 37 | type(IERC6372Upgradeable).interfaceId ^ 38 | governorCancelId; 39 | 40 | // For the updated interface id in v4.9, we use governorCancelId directly. 41 | // 42 | 43 | // replace the interface checks with return the expected interface ids 44 | bytes4[] memory ids = new bytes4[](4); 45 | ids[0] = governor43Id; 46 | ids[1] = governor46Id; 47 | ids[2] = governorCancelId; 48 | ids[3] = type(IERC1155ReceiverUpgradeable).interfaceId; 49 | return ids; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /contracts/test/mocks/GovernorCountingOverridableHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 5 | import "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesUpgradeable.sol"; 7 | import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol"; 8 | import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorSettingsUpgradeable.sol"; 9 | 10 | import "../../treasury/GovernorCountingOverridable.sol"; 11 | 12 | /** 13 | * @dev This is a concrete contract to test the GovernorCountingOverridable extension. It implements the minimum 14 | * necessary to get a working Governor to test the extension. 15 | */ 16 | contract GovernorCountingOverridableHarness is 17 | Initializable, 18 | GovernorUpgradeable, 19 | GovernorSettingsUpgradeable, 20 | GovernorVotesUpgradeable, 21 | GovernorCountingOverridable 22 | { 23 | // use non-standard values for these to test if it's really used 24 | uint256 constant QUOTA = 420000; // 42% 25 | uint256 constant QUORUM = 370000; // 37% 26 | 27 | IVotes internal iVotes; // 🍎 28 | 29 | function initialize(IVotes _votes) public initializer { 30 | iVotes = _votes; 31 | 32 | __Governor_init("GovernorCountingOverridableConcrete"); 33 | __GovernorSettings_init( 34 | 0, /* no voting delay */ 35 | 100, /* 100 blocks voting period */ 36 | 0 /* no minimum proposal threshold */ 37 | ); 38 | 39 | __GovernorVotes_init(iVotes); 40 | __GovernorCountingOverridable_init(QUOTA); 41 | } 42 | 43 | function votes() public view override returns (IVotes) { 44 | return iVotes; 45 | } 46 | 47 | function quorum(uint256 timepoint) public view virtual override returns (uint256) { 48 | uint256 totalSupply = iVotes.getPastTotalSupply(timepoint); 49 | return MathUtils.percOf(totalSupply, QUORUM); 50 | } 51 | 52 | /** 53 | * @dev Expose internal _quorumReached function for testing. 54 | */ 55 | function quorumReached(uint256 proposalId) public view returns (bool) { 56 | return super._quorumReached(proposalId); 57 | } 58 | 59 | /** 60 | * @dev Expose internal _voteSucceeded function for testing. 61 | */ 62 | function voteSucceeded(uint256 proposalId) public view returns (bool) { 63 | return super._voteSucceeded(proposalId); 64 | } 65 | 66 | function proposalThreshold() 67 | public 68 | view 69 | override(GovernorUpgradeable, GovernorSettingsUpgradeable) 70 | returns (uint256) 71 | { 72 | return super.proposalThreshold(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /contracts/test/mocks/LivepeerGovernorUpgradeMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../../treasury/LivepeerGovernor.sol"; 5 | 6 | contract LivepeerGovernorUpgradeMock is LivepeerGovernor { 7 | uint256 public customField; 8 | 9 | constructor(address _controller) LivepeerGovernor(_controller) {} 10 | 11 | function setCustomField(uint256 _customField) external { 12 | customField = _customField; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/test/mocks/ManagerFixture.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../../Manager.sol"; 5 | 6 | contract ManagerFixture is Manager { 7 | constructor(address controller) Manager(controller) {} 8 | 9 | function checkSchrodingerCat() public view whenSystemPaused returns (string memory) { 10 | return "alive"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/test/mocks/ManagerProxyTargetMockV1.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../../ManagerProxyTarget.sol"; 5 | 6 | contract ManagerProxyTargetMockV1 is ManagerProxyTarget { 7 | uint256 public initValue; 8 | uint8 public uint8Value; 9 | uint64 public uint64Value; 10 | uint256 public uint256Value; 11 | bytes32 public bytes32Value; 12 | address public addressValue; 13 | string public stringValue; 14 | bytes public bytesValue; 15 | uint256 public tupleValue1; 16 | uint256 public tupleValue2; 17 | bytes32 public tupleValue3; 18 | 19 | constructor(address _controller) Manager(_controller) {} 20 | 21 | receive() external payable {} 22 | 23 | function setUint8(uint8 _value) external { 24 | uint8Value = _value; 25 | } 26 | 27 | function setUint64(uint64 _value) external { 28 | uint64Value = _value; 29 | } 30 | 31 | function setUint256(uint256 _value) external { 32 | uint256Value = _value; 33 | } 34 | 35 | function setBytes32(bytes32 _value) external { 36 | bytes32Value = _value; 37 | } 38 | 39 | function setAddress(address _value) external { 40 | addressValue = _value; 41 | } 42 | 43 | function setString(string calldata _value) external { 44 | stringValue = _value; 45 | } 46 | 47 | function setBytes(bytes calldata _value) external { 48 | bytesValue = _value; 49 | } 50 | 51 | function setTuple( 52 | uint256 _value1, 53 | uint256 _value2, 54 | bytes32 _value3 55 | ) external { 56 | tupleValue1 = _value1; 57 | tupleValue2 = _value2; 58 | tupleValue3 = _value3; 59 | } 60 | 61 | function getTuple() 62 | external 63 | view 64 | returns ( 65 | uint256, 66 | uint256, 67 | bytes32 68 | ) 69 | { 70 | return (tupleValue1, tupleValue2, tupleValue3); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /contracts/test/mocks/ManagerProxyTargetMockV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../../ManagerProxyTarget.sol"; 5 | 6 | contract ManagerProxyTargetMockV2 is ManagerProxyTarget { 7 | uint256 public initValue; 8 | uint8 public uint8Value; 9 | uint64 public uint64Value; 10 | uint256 public uint256Value; 11 | bytes32 public bytes32Value; 12 | address public addressValue; 13 | 14 | constructor(address _controller) Manager(_controller) {} 15 | 16 | function setUint8(uint8 _value) external { 17 | uint8Value = _value + 5; 18 | } 19 | 20 | function setUint64(uint64 _value) external { 21 | uint64Value = _value + 5; 22 | } 23 | 24 | function setUint256(uint256 _value) external { 25 | uint256Value = _value + 5; 26 | } 27 | 28 | function setBytes32(bytes32 _value) external { 29 | bytes32Value = keccak256(abi.encodePacked(_value)); 30 | } 31 | 32 | function setAddress(address _value) external { 33 | addressValue = _value; // to supress compilation warnings 34 | addressValue = address(0); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/test/mocks/ManagerProxyTargetMockV3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../../ManagerProxyTarget.sol"; 5 | 6 | contract ManagerProxyTargetMockV3 is ManagerProxyTarget { 7 | uint256 public initValue; 8 | uint8 public uint8Value; 9 | uint64 public uint64Value; 10 | uint256 public uint256Value; 11 | bytes32 public bytes32Value; 12 | address public addressValue; 13 | mapping(uint256 => uint256) public kvMap; 14 | 15 | constructor(address _controller) Manager(_controller) {} 16 | 17 | function setUint8(uint8 _value) external { 18 | uint8Value = _value; 19 | } 20 | 21 | function setUint64(uint64 _value) external { 22 | uint64Value = _value; 23 | } 24 | 25 | function setUint256(uint256 _value) external { 26 | uint256Value = _value; 27 | } 28 | 29 | function setBytes32(bytes32 _value) external { 30 | bytes32Value = _value; 31 | } 32 | 33 | function setAddress(address _value) external { 34 | addressValue = _value; 35 | } 36 | 37 | function setKv(uint256 _key, uint256 _value) external { 38 | kvMap[_key] = _value; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/test/mocks/MinterMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./GenericMock.sol"; 5 | 6 | contract MinterMock is GenericMock { 7 | event TrustedWithdrawETH(address to, uint256 amount); 8 | event TrustedTransferTokens(address to, uint256 amount); 9 | 10 | function trustedWithdrawETH(address _to, uint256 _amount) external { 11 | emit TrustedWithdrawETH(_to, _amount); 12 | } 13 | 14 | function trustedTransferTokens(address _to, uint256 _amount) external { 15 | emit TrustedTransferTokens(_to, _amount); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/test/mocks/SetUint256.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | contract SetUint256 { 5 | uint256 public i; 6 | bool shouldFail; 7 | 8 | function setUint256(uint256 _i) public payable { 9 | if (shouldFail) { 10 | revert("I should fail"); 11 | } 12 | i = _i; 13 | } 14 | 15 | function setShouldFail(bool _shouldFail) public { 16 | shouldFail = _shouldFail; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/test/mocks/SortedArraysFixture.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../../bonding/libraries/SortedArrays.sol"; 5 | 6 | contract SortedArraysFixture { 7 | uint256[] public array; 8 | 9 | function findLowerBound(uint256 val) external view returns (uint256) { 10 | return SortedArrays.findLowerBound(array, val); 11 | } 12 | 13 | function pushSorted(uint256 val) external { 14 | SortedArrays.pushSorted(array, val); 15 | } 16 | 17 | function length() external view returns (uint256) { 18 | return array.length; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/test/mocks/SortedDoublyLLFixture.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../../libraries/SortedDoublyLL.sol"; 5 | 6 | contract SortedDoublyLLFixture { 7 | using SortedDoublyLL for SortedDoublyLL.Data; 8 | 9 | SortedDoublyLL.Data list; 10 | 11 | function setMaxSize(uint256 _size) public { 12 | list.setMaxSize(_size); 13 | } 14 | 15 | function insert( 16 | address _id, 17 | uint256 _key, 18 | address _prevId, 19 | address _nextId 20 | ) public { 21 | list.insert(_id, _key, _prevId, _nextId); 22 | } 23 | 24 | function remove(address _id) public { 25 | list.remove(_id); 26 | } 27 | 28 | function updateKey( 29 | address _id, 30 | uint256 _newKey, 31 | address _prevId, 32 | address _nextId 33 | ) public { 34 | list.updateKey(_id, _newKey, _prevId, _nextId); 35 | } 36 | 37 | function contains(address _id) public view returns (bool) { 38 | return list.contains(_id); 39 | } 40 | 41 | function getSize() public view returns (uint256) { 42 | return list.getSize(); 43 | } 44 | 45 | function getMaxSize() public view returns (uint256) { 46 | return list.maxSize; 47 | } 48 | 49 | function getKey(address _id) public view returns (uint256) { 50 | return list.getKey(_id); 51 | } 52 | 53 | function getFirst() public view returns (address) { 54 | return list.getFirst(); 55 | } 56 | 57 | function getLast() public view returns (address) { 58 | return list.getLast(); 59 | } 60 | 61 | function getNext(address _id) public view returns (address) { 62 | return list.getNext(_id); 63 | } 64 | 65 | function getPrev(address _id) public view returns (address) { 66 | return list.getPrev(_id); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /contracts/test/mocks/TicketBrokerExtendedMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | import "../../pm/TicketBroker.sol"; 4 | 5 | contract TickerBrokerExtendedMock is TicketBroker { 6 | constructor(address _controller) TicketBroker(_controller) {} 7 | 8 | function checkResult(bytes calldata _sig, uint256 _recipientRand) external pure returns (uint256) { 9 | return uint256(keccak256(abi.encodePacked(_sig, _recipientRand))); 10 | } 11 | 12 | function validateAndCheckTicketOutcome( 13 | address _sender, 14 | bytes32 _ticketHash, 15 | bytes calldata _sig, 16 | uint256 _recipientRand, 17 | uint256 _winProb 18 | ) external pure returns (bool) { 19 | require(isValidTicketSig(_sender, _sig, _ticketHash), "invalid signature over ticket hash"); 20 | return isWinningTicket(_sig, _recipientRand, _winProb); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/token/ILivepeerToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | 6 | interface ILivepeerToken is IERC20 { 7 | function mint(address _to, uint256 _amount) external; 8 | 9 | function burn(uint256 _amount) external; 10 | } 11 | -------------------------------------------------------------------------------- /contracts/token/IMinter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "../IController.sol"; 5 | 6 | /** 7 | * @title Minter interface 8 | */ 9 | interface IMinter { 10 | // Events 11 | event SetCurrentRewardTokens(uint256 currentMintableTokens, uint256 currentInflation); 12 | 13 | // External functions 14 | function createReward(uint256 _fracNum, uint256 _fracDenom) external returns (uint256); 15 | 16 | function trustedTransferTokens(address _to, uint256 _amount) external; 17 | 18 | function trustedBurnTokens(uint256 _amount) external; 19 | 20 | function trustedWithdrawETH(address payable _to, uint256 _amount) external; 21 | 22 | function depositETH() external payable returns (bool); 23 | 24 | function setCurrentRewardTokens() external; 25 | 26 | function currentMintableTokens() external view returns (uint256); 27 | 28 | function currentMintedTokens() external view returns (uint256); 29 | 30 | // Public functions 31 | function getController() external view returns (IController); 32 | } 33 | -------------------------------------------------------------------------------- /contracts/token/LivepeerToken.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | // solhint-disable-next-line 3 | pragma solidity 0.8.9; 4 | 5 | import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; 6 | import { ERC20, ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; 7 | import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 8 | 9 | // Copy of https://github.com/livepeer/arbitrum-lpt-bridge/blob/main/contracts/L2/token/LivepeerToken.sol 10 | // Tests at https://github.com/livepeer/arbitrum-lpt-bridge/blob/main/test/unit/L2/livepeerToken.test.ts 11 | contract LivepeerToken is AccessControl, ERC20Burnable, ERC20Permit { 12 | bytes32 private immutable MINTER_ROLE = keccak256("MINTER_ROLE"); 13 | bytes32 private immutable BURNER_ROLE = keccak256("BURNER_ROLE"); 14 | 15 | event Mint(address indexed to, uint256 amount); 16 | event Burn(address indexed burner, uint256 amount); 17 | 18 | constructor() ERC20("Livepeer Token", "LPT") ERC20Permit("Livepeer Token") { 19 | _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); 20 | } 21 | 22 | /** 23 | * @notice Function to mint tokens 24 | * @dev Only callable by addreses with MINTER_ROLE 25 | * @param _to The address that will receive the minted tokens. 26 | * @param _amount The amount of tokens to mint. 27 | */ 28 | function mint(address _to, uint256 _amount) external onlyRole(MINTER_ROLE) { 29 | _mint(_to, _amount); 30 | emit Mint(_to, _amount); 31 | } 32 | 33 | /** 34 | * @notice Burns a specific amount of msg.sender's tokens 35 | * @dev Only callable by addresses with BURNER_ROLE 36 | * @param _amount The amount of tokens to be burned 37 | */ 38 | function burn(uint256 _amount) public override onlyRole(BURNER_ROLE) { 39 | super.burn(_amount); 40 | emit Burn(msg.sender, _amount); 41 | } 42 | 43 | /** 44 | * @notice Burns a specific amount of an address' tokens 45 | * @dev Only callable by addresses with BURNER_ROLE. Requires the address to approve the caller to burn the amount 46 | * @param _from Address to burn tokens for 47 | * @param _amount The amount of tokens to be burned 48 | */ 49 | function burnFrom(address _from, uint256 _amount) public override onlyRole(BURNER_ROLE) { 50 | super.burnFrom(_from, _amount); 51 | emit Burn(_from, _amount); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /contracts/token/LivepeerTokenFaucet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./ILivepeerToken.sol"; 5 | 6 | import "../zeppelin/Ownable.sol"; 7 | 8 | /** 9 | * @title Faucet for the Livepeer Token 10 | */ 11 | contract LivepeerTokenFaucet is Ownable { 12 | // Token 13 | ILivepeerToken public token; 14 | 15 | // Amount of token sent to sender for a request 16 | uint256 public requestAmount; 17 | 18 | // Amount of time a sender must wait between requests 19 | uint256 public requestWait; 20 | 21 | // sender => timestamp at which sender can make another request 22 | mapping(address => uint256) public nextValidRequest; 23 | 24 | // Whitelist addresses that can bypass faucet request rate limit 25 | mapping(address => bool) public isWhitelisted; 26 | 27 | // Checks if a request is valid (sender is whitelisted or has waited the rate limit time) 28 | modifier validRequest() { 29 | require(isWhitelisted[msg.sender] || block.timestamp >= nextValidRequest[msg.sender]); 30 | _; 31 | } 32 | 33 | event Request(address indexed to, uint256 amount); 34 | 35 | /** 36 | * @notice LivepeerTokenFacuet constructor 37 | * @param _token Address of LivepeerToken 38 | * @param _requestAmount Amount of token sent to sender for a request 39 | * @param _requestWait Amount of time a sender must wait between request (denominated in hours) 40 | */ 41 | constructor( 42 | address _token, 43 | uint256 _requestAmount, 44 | uint256 _requestWait 45 | ) { 46 | token = ILivepeerToken(_token); 47 | requestAmount = _requestAmount; 48 | requestWait = _requestWait; 49 | } 50 | 51 | /** 52 | * @notice Add an address to the whitelist 53 | * @param _addr Address to be whitelisted 54 | */ 55 | function addToWhitelist(address _addr) external onlyOwner { 56 | isWhitelisted[_addr] = true; 57 | } 58 | 59 | /** 60 | * @notice Remove an address from the whitelist 61 | * @param _addr Address to be removed from whitelist 62 | */ 63 | function removeFromWhitelist(address _addr) external onlyOwner { 64 | isWhitelisted[_addr] = false; 65 | } 66 | 67 | /** 68 | * @notice Request an amount of token to be sent to sender 69 | */ 70 | function request() external validRequest { 71 | if (!isWhitelisted[msg.sender]) { 72 | nextValidRequest[msg.sender] = block.timestamp + requestWait * 1 hours; 73 | } 74 | 75 | token.transfer(msg.sender, requestAmount); 76 | 77 | emit Request(msg.sender, requestAmount); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /contracts/treasury/IVotes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "@openzeppelin/contracts-upgradeable/interfaces/IERC5805Upgradeable.sol"; 5 | 6 | interface IVotes is IERC5805Upgradeable { 7 | function totalSupply() external view returns (uint256); 8 | 9 | function delegatedAt(address account, uint256 timepoint) external returns (address); 10 | 11 | // ERC-20 metadata functions that improve compatibility with tools like Tally 12 | 13 | function name() external view returns (string memory); 14 | 15 | function symbol() external view returns (string memory); 16 | 17 | function decimals() external view returns (uint8); 18 | } 19 | -------------------------------------------------------------------------------- /contracts/treasury/Treasury.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol"; 5 | 6 | /** 7 | * @title Treasury 8 | * @notice Holder of the treasury and executor of proposals for the LivepeerGovernor. 9 | * @dev This was only really needed because TimelockControllerUpgradeable does not expose a public initializer, so we 10 | * need to inherit and expose the initialization function here. 11 | * 12 | * Even though this contract is upgradeable to fit with the rest of the contracts that expect upgradeable instances, it 13 | * is not used with a proxy, so we don't need to disable initializers in the constructor. 14 | */ 15 | contract Treasury is Initializable, TimelockControllerUpgradeable { 16 | function initialize( 17 | uint256 minDelay, 18 | address[] memory proposers, 19 | address[] memory executors, 20 | address admin 21 | ) external initializer { 22 | __TimelockController_init(minDelay, proposers, executors, admin); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/zeppelin/MerkleProof.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | /** 5 | * @dev These functions deal with verification of Merkle trees (hash trees), 6 | */ 7 | library MerkleProof { 8 | /** 9 | * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree 10 | * defined by `root`. For this, a `proof` must be provided, containing 11 | * sibling hashes on the branch from the leaf to the root of the tree. Each 12 | * pair of leaves and each pair of pre-images are assumed to be sorted. 13 | */ 14 | function verify( 15 | bytes32[] memory proof, 16 | bytes32 root, 17 | bytes32 leaf 18 | ) internal pure returns (bool) { 19 | bytes32 computedHash = leaf; 20 | 21 | for (uint256 i = 0; i < proof.length; i++) { 22 | bytes32 proofElement = proof[i]; 23 | 24 | if (computedHash <= proofElement) { 25 | // Hash(current computed hash + current element of the proof) 26 | computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); 27 | } else { 28 | // Hash(current element of the proof + current computed hash) 29 | computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); 30 | } 31 | } 32 | 33 | // Check if the computed hash (root) is equal to the provided root 34 | return computedHash == root; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/zeppelin/Ownable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | /** 5 | * @title Ownable 6 | * @dev The Ownable contract has an owner address, and provides basic authorization control 7 | * functions, this simplifies the implementation of "user permissions". 8 | */ 9 | contract Ownable { 10 | address public owner; 11 | 12 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 13 | 14 | /** 15 | * @dev The Ownable constructor sets the original `owner` of the contract to the sender 16 | * account. 17 | */ 18 | constructor() { 19 | owner = msg.sender; 20 | } 21 | 22 | /** 23 | * @dev Throws if called by any account other than the owner. 24 | */ 25 | modifier onlyOwner() { 26 | require(msg.sender == owner); 27 | _; 28 | } 29 | 30 | /** 31 | * @dev Allows the current owner to transfer control of the contract to a newOwner. 32 | * @param newOwner The address to transfer ownership to. 33 | */ 34 | function transferOwnership(address newOwner) public onlyOwner { 35 | require(newOwner != address(0)); 36 | emit OwnershipTransferred(owner, newOwner); 37 | owner = newOwner; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /contracts/zeppelin/Pausable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.9; 3 | 4 | import "./Ownable.sol"; 5 | 6 | /** 7 | * @title Pausable 8 | * @dev Base contract which allows children to implement an emergency stop mechanism. 9 | */ 10 | contract Pausable is Ownable { 11 | event Pause(); 12 | event Unpause(); 13 | 14 | bool public paused; 15 | 16 | /** 17 | * @dev Modifier to make a function callable only when the contract is not paused. 18 | */ 19 | modifier whenNotPaused() { 20 | require(!paused); 21 | _; 22 | } 23 | 24 | /** 25 | * @dev Modifier to make a function callable only when the contract is paused. 26 | */ 27 | modifier whenPaused() { 28 | require(paused); 29 | _; 30 | } 31 | 32 | /** 33 | * @dev called by the owner to pause, triggers stopped state 34 | */ 35 | function pause() public onlyOwner whenNotPaused { 36 | paused = true; 37 | emit Pause(); 38 | } 39 | 40 | /** 41 | * @dev called by the owner to unpause, returns to normal state 42 | */ 43 | function unpause() public onlyOwner whenPaused { 44 | paused = false; 45 | emit Unpause(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /deploy/deploy_ai_service_registry.ts: -------------------------------------------------------------------------------- 1 | import {HardhatRuntimeEnvironment} from "hardhat/types" 2 | import {DeployFunction} from "hardhat-deploy/types" 3 | 4 | const func: DeployFunction = async function(hre: HardhatRuntimeEnvironment) { 5 | const {deployments, getNamedAccounts} = hre 6 | const {deploy} = deployments 7 | 8 | const {deployer} = await getNamedAccounts() 9 | 10 | const controllerDeployment = await deployments.get("Controller") 11 | 12 | const deployResult = await deploy("ServiceRegistry", { 13 | from: deployer, 14 | args: [controllerDeployment.address], 15 | log: true 16 | }) 17 | await deployments.save("AIServiceRegistry", deployResult) 18 | } 19 | 20 | func.tags = ["AI_SERVICE_REGISTRY"] 21 | export default func 22 | -------------------------------------------------------------------------------- /deploy/deploy_arbitrum_lpt_dummies.ts: -------------------------------------------------------------------------------- 1 | import {HardhatRuntimeEnvironment} from "hardhat/types" 2 | import {DeployFunction} from "hardhat-deploy/types" 3 | import {contractId} from "../utils/helpers" 4 | 5 | const func: DeployFunction = async function(hre: HardhatRuntimeEnvironment) { 6 | const {deployments, getNamedAccounts, ethers} = hre 7 | const {deploy, get} = deployments 8 | 9 | const {deployer} = await getNamedAccounts() 10 | 11 | const deployResult = await deploy("DummyL2LPTDataCache", { 12 | from: deployer, 13 | log: true 14 | }) 15 | 16 | const controllerDeployment = await get("Controller") 17 | const controller = await ethers.getContractAt( 18 | "Controller", 19 | controllerDeployment.address 20 | ) 21 | 22 | const id = contractId("L2LPTDataCache") 23 | await controller.setContractInfo( 24 | id, 25 | deployResult.address, 26 | "0x1111111111111111111111111111111111111111" 27 | ) 28 | } 29 | 30 | func.tags = ["ARBITRUM_LPT_DUMMIES"] 31 | export default func 32 | -------------------------------------------------------------------------------- /deploy/deploy_bonding_manager.ts: -------------------------------------------------------------------------------- 1 | import {HardhatRuntimeEnvironment} from "hardhat/types" 2 | import {DeployFunction} from "hardhat-deploy/types" 3 | 4 | const func: DeployFunction = async function(hre: HardhatRuntimeEnvironment) { 5 | const {deployments, getNamedAccounts} = hre 6 | const {deploy} = deployments 7 | 8 | const {deployer} = await getNamedAccounts() 9 | 10 | const controllerDeployment = await deployments.get("Controller") 11 | const llDeployment = await deployments.get("SortedDoublyLL") 12 | 13 | const deployResult = await deploy("BondingManager", { 14 | from: deployer, 15 | args: [controllerDeployment.address], 16 | libraries: { 17 | SortedDoublyLL: llDeployment.address 18 | }, 19 | log: true 20 | }) 21 | await deployments.save("BondingManagerTarget", deployResult) 22 | } 23 | 24 | func.tags = ["BONDING_MANAGER"] 25 | export default func 26 | -------------------------------------------------------------------------------- /deploy/deploy_bonding_votes.ts: -------------------------------------------------------------------------------- 1 | import {HardhatRuntimeEnvironment} from "hardhat/types" 2 | import {DeployFunction} from "hardhat-deploy/types" 3 | 4 | import ContractDeployer from "../utils/deployer" 5 | 6 | const PROD_NETWORKS = ["mainnet", "arbitrumMainnet"] 7 | 8 | const isProdNetwork = (name: string): boolean => { 9 | return PROD_NETWORKS.indexOf(name) > -1 10 | } 11 | 12 | const func: DeployFunction = async function(hre: HardhatRuntimeEnvironment) { 13 | const {deployments, getNamedAccounts} = hre // Get the deployments and getNamedAccounts which are provided by hardhat-deploy 14 | 15 | const {deployer} = await getNamedAccounts() // Fetch named accounts from hardhat.config.ts 16 | 17 | const contractDeployer = new ContractDeployer(deployer, deployments) 18 | const controller = await contractDeployer.fetchDeployedController() 19 | 20 | const deploy = isProdNetwork(hre.network.name) ? 21 | contractDeployer.deploy.bind(contractDeployer) : 22 | contractDeployer.deployAndRegister.bind(contractDeployer) 23 | 24 | await deploy({ 25 | contract: "BondingVotes", 26 | name: "BondingVotesTarget", 27 | args: [controller.address], 28 | proxy: false // deploying only the target 29 | }) 30 | } 31 | 32 | func.tags = ["BONDING_VOTES"] 33 | export default func 34 | -------------------------------------------------------------------------------- /deploy/deploy_dummy_gateway.ts: -------------------------------------------------------------------------------- 1 | import {HardhatRuntimeEnvironment} from "hardhat/types" 2 | import {DeployFunction} from "hardhat-deploy/types" 3 | 4 | const func: DeployFunction = async function(hre: HardhatRuntimeEnvironment) { 5 | const {deployments, getNamedAccounts} = hre 6 | const {deploy} = deployments 7 | 8 | const {deployer} = await getNamedAccounts() 9 | 10 | await deploy("DummyGateway", { 11 | from: deployer, 12 | log: true 13 | }) 14 | } 15 | 16 | func.tags = ["DUMMY_GATEWAY"] 17 | export default func 18 | -------------------------------------------------------------------------------- /deploy/deploy_minter.ts: -------------------------------------------------------------------------------- 1 | import {HardhatRuntimeEnvironment} from "hardhat/types" 2 | import {DeployFunction} from "hardhat-deploy/types" 3 | import {ethers} from "hardhat" 4 | import {Minter} from "../typechain" 5 | 6 | const func: DeployFunction = async function(hre: HardhatRuntimeEnvironment) { 7 | const {deployments, getNamedAccounts} = hre // Get the deployments and getNamedAccounts which are provided by hardhat-deploy 8 | const {deploy} = deployments // the deployments object itself contains the deploy function 9 | 10 | const {deployer} = await getNamedAccounts() // Fetch named accounts from hardhat.config.ts 11 | 12 | const controllerDeployment = await deployments.get("Controller") 13 | const minterDeployment = await deployments.get("Minter") 14 | 15 | const minter: Minter = (await ethers.getContractAt( 16 | "Minter", 17 | minterDeployment.address 18 | )) as Minter 19 | 20 | const inflation = await minter.inflation() 21 | const inflationChange = await minter.inflationChange() 22 | const targetBondingRate = await minter.targetBondingRate() 23 | 24 | await deploy("Minter", { 25 | from: deployer, 26 | args: [ 27 | controllerDeployment.address, 28 | inflation, 29 | inflationChange, 30 | targetBondingRate 31 | ] 32 | }) 33 | } 34 | 35 | func.tags = ["Minter"] 36 | export default func 37 | -------------------------------------------------------------------------------- /deploy/deploy_poll.ts: -------------------------------------------------------------------------------- 1 | import {HardhatRuntimeEnvironment} from "hardhat/types" 2 | import {DeployFunction} from "hardhat-deploy/types" 3 | 4 | import ContractDeployer from "../utils/deployer" 5 | 6 | const func: DeployFunction = async function(hre: HardhatRuntimeEnvironment) { 7 | const {deployments, getNamedAccounts} = hre // Get the deployments and getNamedAccounts which are provided by hardhat-deploy 8 | const {get} = deployments // the deployments object itself contains the deploy function 9 | 10 | const {deployer} = await getNamedAccounts() // Fetch named accounts from hardhat.config.ts 11 | 12 | const contractDeployer = new ContractDeployer(deployer, deployments) 13 | 14 | const bondingManager = await get("BondingManager") 15 | 16 | await contractDeployer.deploy({ 17 | contract: "PollCreator", 18 | name: "PollCreator", 19 | args: [bondingManager.address] 20 | }) 21 | } 22 | 23 | func.dependencies = ["Contracts"] 24 | func.tags = ["Poll"] 25 | export default func 26 | -------------------------------------------------------------------------------- /deploy/deploy_ticket_broker.ts: -------------------------------------------------------------------------------- 1 | import {HardhatRuntimeEnvironment} from "hardhat/types" 2 | import {DeployFunction} from "hardhat-deploy/types" 3 | 4 | const func: DeployFunction = async function(hre: HardhatRuntimeEnvironment) { 5 | const {deployments, getNamedAccounts} = hre 6 | const {deploy} = deployments 7 | 8 | const {deployer} = await getNamedAccounts() 9 | 10 | const controllerDeployment = await deployments.get("Controller") 11 | 12 | const deployResult = await deploy("TicketBroker", { 13 | from: deployer, 14 | args: [controllerDeployment.address], 15 | log: true 16 | }) 17 | await deployments.save("TicketBrokerTarget", deployResult) 18 | } 19 | 20 | func.tags = ["TICKET_BROKER"] 21 | export default func 22 | -------------------------------------------------------------------------------- /deployments/arbitrumMainnet/.chainId: -------------------------------------------------------------------------------- 1 | 42161 -------------------------------------------------------------------------------- /deployments/arbitrumMainnet/solcInputs/6ef8695a2f240c3b9460c170f7b2aff1.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "Solidity", 3 | "sources": { 4 | "contracts/polling/Poll.sol": { 5 | "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\ncontract Poll {\n // The block at which the poll ends and votes can no longer be submitted.\n uint256 public endBlock;\n\n // Vote is emitted when an account submits a vote with 'choiceID'.\n // This event can be indexed to tally all votes for each choiceID\n event Vote(address indexed voter, uint256 choiceID);\n\n modifier isActive() {\n require(block.number <= endBlock, \"poll is over\");\n _;\n }\n\n constructor(uint256 _endBlock) {\n endBlock = _endBlock;\n }\n\n /**\n * @dev Vote for the poll's proposal.\n * Reverts if the poll period is over.\n * @param _choiceID the ID of the option to vote for\n */\n function vote(uint256 _choiceID) external isActive {\n emit Vote(msg.sender, _choiceID);\n }\n\n /**\n * @dev Destroy the Poll contract after the poll has finished\n * Reverts if the poll is still active\n */\n function destroy() external {\n require(block.number > endBlock, \"poll is active\");\n selfdestruct(payable(msg.sender));\n }\n}\n" 6 | }, 7 | "contracts/polling/PollCreator.sol": { 8 | "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"./Poll.sol\";\n\ninterface IBondingManager {\n function transcoderTotalStake(address _addr) external view returns (uint256);\n\n function pendingStake(address _addr, uint256 _endRound) external view returns (uint256);\n}\n\ncontract PollCreator {\n // 33.33%\n uint256 public constant QUORUM = 333300;\n // 50%\n uint256 public constant QUOTA = 500000;\n // 10 rounds\n uint256 public constant POLL_PERIOD = 10 * 5760;\n uint256 public constant POLL_CREATION_COST = 100 * 1 ether;\n\n IBondingManager public bondingManager;\n\n event PollCreated(address indexed poll, bytes proposal, uint256 endBlock, uint256 quorum, uint256 quota);\n\n constructor(address _bondingManagerAddr) {\n bondingManager = IBondingManager(_bondingManagerAddr);\n }\n\n /**\n * @notice Create a poll if caller has POLL_CREATION_COST LPT stake (own stake or stake delegated to it).\n * @param _proposal The IPFS multihash for the proposal.\n */\n function createPoll(bytes calldata _proposal) external {\n require(\n // pendingStake() ignores the second arg\n bondingManager.pendingStake(msg.sender, 0) >= POLL_CREATION_COST ||\n bondingManager.transcoderTotalStake(msg.sender) >= POLL_CREATION_COST,\n \"PollCreator#createPoll: INSUFFICIENT_STAKE\"\n );\n\n uint256 endBlock = block.number + POLL_PERIOD;\n Poll poll = new Poll(endBlock);\n\n emit PollCreated(address(poll), _proposal, endBlock, QUORUM, QUOTA);\n }\n}\n" 9 | } 10 | }, 11 | "settings": { 12 | "optimizer": { 13 | "enabled": true, 14 | "runs": 200 15 | }, 16 | "outputSelection": { 17 | "*": { 18 | "*": [ 19 | "abi", 20 | "evm.bytecode", 21 | "evm.deployedBytecode", 22 | "evm.methodIdentifiers", 23 | "metadata", 24 | "devdoc", 25 | "userdoc", 26 | "storageLayout", 27 | "evm.gasEstimates" 28 | ], 29 | "": [ 30 | "ast" 31 | ] 32 | } 33 | }, 34 | "metadata": { 35 | "useLiteralContent": true 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /deployments/arbitrumRinkeby/.chainId: -------------------------------------------------------------------------------- 1 | 421611 -------------------------------------------------------------------------------- /deployments/arbitrumRinkeby/solcInputs/52de547bf900d68228bf4e4cdfe1d28d.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "Solidity", 3 | "sources": { 4 | "contracts/polling/Poll.sol": { 5 | "content": "pragma solidity ^0.5.11;\n\ncontract Poll {\n // The block at which the poll ends and votes can no longer be submitted.\n uint256 public endBlock;\n\n // Vote is emitted when an account submits a vote with 'choiceID'.\n // This event can be indexed to tally all votes for each choiceID\n event Vote(address indexed voter, uint256 choiceID);\n\n modifier isActive() {\n require(block.number <= endBlock, \"poll is over\");\n _;\n }\n\n constructor(uint256 _endBlock) public {\n endBlock = _endBlock;\n }\n\n /**\n * @dev Vote for the poll's proposal.\n * Reverts if the poll period is over.\n * @param _choiceID the ID of the option to vote for\n */\n function vote(uint256 _choiceID) external isActive {\n emit Vote(msg.sender, _choiceID);\n }\n\n /**\n * @dev Destroy the Poll contract after the poll has finished\n * Reverts if the poll is still active\n */\n function destroy() external {\n require(block.number > endBlock, \"poll is active\");\n selfdestruct(msg.sender);\n }\n}\n" 6 | }, 7 | "contracts/polling/PollCreator.sol": { 8 | "content": "pragma solidity ^0.5.11;\n\nimport \"./Poll.sol\";\n\ninterface IBondingManager {\n function transcoderTotalStake(address _addr) external view returns (uint256);\n\n function pendingStake(address _addr, uint256 _endRound) external view returns (uint256);\n}\n\ncontract PollCreator {\n // 33.33%\n uint256 public constant QUORUM = 333300;\n // 50%\n uint256 public constant QUOTA = 500000;\n // 10 rounds\n uint256 public constant POLL_PERIOD = 10 * 5760;\n uint256 public constant POLL_CREATION_COST = 100 * 1 ether;\n\n IBondingManager public bondingManager;\n\n event PollCreated(address indexed poll, bytes proposal, uint256 endBlock, uint256 quorum, uint256 quota);\n\n constructor(address _bondingManagerAddr) public {\n bondingManager = IBondingManager(_bondingManagerAddr);\n }\n\n /**\n * @notice Create a poll if caller has POLL_CREATION_COST LPT stake (own stake or stake delegated to it).\n * @param _proposal The IPFS multihash for the proposal.\n */\n function createPoll(bytes calldata _proposal) external {\n require(\n // pendingStake() ignores the second arg\n bondingManager.pendingStake(msg.sender, 0) >= POLL_CREATION_COST ||\n bondingManager.transcoderTotalStake(msg.sender) >= POLL_CREATION_COST,\n \"PollCreator#createPoll: INSUFFICIENT_STAKE\"\n );\n\n uint256 endBlock = block.number + POLL_PERIOD;\n Poll poll = new Poll(endBlock);\n\n emit PollCreated(address(poll), _proposal, endBlock, QUORUM, QUOTA);\n }\n}\n" 9 | } 10 | }, 11 | "settings": { 12 | "optimizer": { 13 | "enabled": true, 14 | "runs": 200 15 | }, 16 | "outputSelection": { 17 | "*": { 18 | "*": [ 19 | "abi", 20 | "evm.bytecode", 21 | "evm.deployedBytecode", 22 | "evm.methodIdentifiers", 23 | "metadata", 24 | "devdoc", 25 | "userdoc", 26 | "storageLayout", 27 | "evm.gasEstimates" 28 | ], 29 | "": [ 30 | "ast" 31 | ] 32 | } 33 | }, 34 | "metadata": { 35 | "useLiteralContent": true 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /deployments/arbitrumRinkebyDevnet/.chainId: -------------------------------------------------------------------------------- 1 | 421611 -------------------------------------------------------------------------------- /deployments/arbitrumRinkebyDevnet/solcInputs/52de547bf900d68228bf4e4cdfe1d28d.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "Solidity", 3 | "sources": { 4 | "contracts/polling/Poll.sol": { 5 | "content": "pragma solidity ^0.5.11;\n\ncontract Poll {\n // The block at which the poll ends and votes can no longer be submitted.\n uint256 public endBlock;\n\n // Vote is emitted when an account submits a vote with 'choiceID'.\n // This event can be indexed to tally all votes for each choiceID\n event Vote(address indexed voter, uint256 choiceID);\n\n modifier isActive() {\n require(block.number <= endBlock, \"poll is over\");\n _;\n }\n\n constructor(uint256 _endBlock) public {\n endBlock = _endBlock;\n }\n\n /**\n * @dev Vote for the poll's proposal.\n * Reverts if the poll period is over.\n * @param _choiceID the ID of the option to vote for\n */\n function vote(uint256 _choiceID) external isActive {\n emit Vote(msg.sender, _choiceID);\n }\n\n /**\n * @dev Destroy the Poll contract after the poll has finished\n * Reverts if the poll is still active\n */\n function destroy() external {\n require(block.number > endBlock, \"poll is active\");\n selfdestruct(msg.sender);\n }\n}\n" 6 | }, 7 | "contracts/polling/PollCreator.sol": { 8 | "content": "pragma solidity ^0.5.11;\n\nimport \"./Poll.sol\";\n\ninterface IBondingManager {\n function transcoderTotalStake(address _addr) external view returns (uint256);\n\n function pendingStake(address _addr, uint256 _endRound) external view returns (uint256);\n}\n\ncontract PollCreator {\n // 33.33%\n uint256 public constant QUORUM = 333300;\n // 50%\n uint256 public constant QUOTA = 500000;\n // 10 rounds\n uint256 public constant POLL_PERIOD = 10 * 5760;\n uint256 public constant POLL_CREATION_COST = 100 * 1 ether;\n\n IBondingManager public bondingManager;\n\n event PollCreated(address indexed poll, bytes proposal, uint256 endBlock, uint256 quorum, uint256 quota);\n\n constructor(address _bondingManagerAddr) public {\n bondingManager = IBondingManager(_bondingManagerAddr);\n }\n\n /**\n * @notice Create a poll if caller has POLL_CREATION_COST LPT stake (own stake or stake delegated to it).\n * @param _proposal The IPFS multihash for the proposal.\n */\n function createPoll(bytes calldata _proposal) external {\n require(\n // pendingStake() ignores the second arg\n bondingManager.pendingStake(msg.sender, 0) >= POLL_CREATION_COST ||\n bondingManager.transcoderTotalStake(msg.sender) >= POLL_CREATION_COST,\n \"PollCreator#createPoll: INSUFFICIENT_STAKE\"\n );\n\n uint256 endBlock = block.number + POLL_PERIOD;\n Poll poll = new Poll(endBlock);\n\n emit PollCreated(address(poll), _proposal, endBlock, QUORUM, QUOTA);\n }\n}\n" 9 | } 10 | }, 11 | "settings": { 12 | "optimizer": { 13 | "enabled": true, 14 | "runs": 200 15 | }, 16 | "outputSelection": { 17 | "*": { 18 | "*": [ 19 | "abi", 20 | "evm.bytecode", 21 | "evm.deployedBytecode", 22 | "evm.methodIdentifiers", 23 | "metadata", 24 | "devdoc", 25 | "userdoc", 26 | "storageLayout", 27 | "evm.gasEstimates" 28 | ], 29 | "": [ 30 | "ast" 31 | ] 32 | } 33 | }, 34 | "metadata": { 35 | "useLiteralContent": true 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /doc/devnet.md: -------------------------------------------------------------------------------- 1 | # Deploying contracts in a single network devnet 2 | 3 | ## Prerequisites 4 | 5 | - Copy [`.env.sample`](../.env.sample) to a `.env` file in the repo root 6 | - Insert values for all the environment variables 7 | - The ETHERSCAN_API_KEY is optional, but if you want to verify the contracts on "Etherscan" you need to provide it. It 8 | should also be from the Etherscan-like service from the network you are deploying to (e.g. Arbiscan for Arbitrum). 9 | - Pick a testnet to use and make sure its configured in: 10 | - [`hardhat.config.ts`](../hardhat.config.ts#L56) 11 | - [`deploy/migrations.config.ts`](../deploy/migrations.config.ts#L185) 12 | - `LIVE_NETWORKS` config in [`deploy/deploy_contracts.ts`](../deploy/deploy_contracts.ts#L26) 13 | - The name of this testnet will be referred as `` in the commands below 14 | 15 | ## Deployment 16 | 17 | - `yarn deploy --network ` to deploy all the core protocol contracts 18 | - `npx hardhat deploy --tags ARBITRUM_LPT_DUMMIES --network ` to deploy the L2 bridge no-ops 19 | 20 | ## Verification 21 | 22 | To verify all contracts that have been deployed in the network on the corresponding etherscan-like service: 23 | 24 | - `yarn etherscan-verify --network ` 25 | 26 | ## Housekeeping 27 | 28 | Make sure you save or commit the `deployments` folder from your run if you want to guarantee a reference to them. If you 29 | run the deployment commands again they will override most of the previous deployment metadata. 30 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | parity-dev: 4 | image: livepeer/parity-dev:latest 5 | ports: 6 | - "47623:8545" 7 | geth-dev: 8 | image: livepeer/geth-dev:latest 9 | ports: 10 | - "47624:8545" 11 | parity-integration-tests: 12 | container_name: parity-integration-tests 13 | image: livepeer/protocol:latest 14 | command: yarn test:integration -- --network=parityDev 15 | links: 16 | - parity-dev 17 | geth-integration-tests: 18 | container_name: geth-integration-tests 19 | image: livepeer/protocol:latest 20 | command: yarn test:integration -- --network=gethDev 21 | links: 22 | - geth-dev 23 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | profile = { default = { libs = ["node_modules", "lib"] } } 2 | [default] 3 | src = 'src' 4 | out = 'foundry_artifacts' 5 | libs = ['node_modules'] 6 | remappings = [ 7 | '@ensdomains/=node_modules/@ensdomains/', 8 | '@openzeppelin/=node_modules/@openzeppelin/', 9 | 'eth-gas-reporter/=node_modules/eth-gas-reporter/', 10 | 'hardhat-deploy/=node_modules/hardhat-deploy/', 11 | 'hardhat/=node_modules/hardhat/', 12 | 'sol-explore/=node_modules/sol-explore/', 13 | 'ds-test/=lib/ds-test/src/', 14 | 'contracts/=contracts/' 15 | ] 16 | 17 | # See more config options https://github.com/gakonst/foundry/tree/master/config 18 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv" 2 | dotenv.config() 3 | 4 | import "@nomiclabs/hardhat-ethers" 5 | import "@nomiclabs/hardhat-web3" 6 | import "@typechain/hardhat" 7 | import "@nomiclabs/hardhat-waffle" 8 | import "hardhat-gas-reporter" 9 | import "@nomicfoundation/hardhat-verify" 10 | import "hardhat-abi-exporter" 11 | 12 | // deployment plugins 13 | import "hardhat-deploy" 14 | import "@nomiclabs/hardhat-ethers" 15 | 16 | import "solidity-coverage" 17 | import path from "path" 18 | import fs from "fs" 19 | import {HardhatUserConfig} from "hardhat/types/config" 20 | 21 | const PRIVATE_KEY = process.env.PRIVATE_KEY 22 | const INFURA_KEY = process.env.INFURA_KEY 23 | 24 | function loadTasks() { 25 | const tasksPath = path.join(__dirname, "tasks") 26 | fs.readdirSync(tasksPath).forEach(task => { 27 | require(`${tasksPath}/${task}`) 28 | }) 29 | } 30 | 31 | if ( 32 | fs.existsSync(path.join(__dirname, "artifacts")) && 33 | fs.existsSync("./typechain") 34 | ) { 35 | loadTasks() 36 | } 37 | 38 | const config: HardhatUserConfig = { 39 | solidity: { 40 | compilers: [ 41 | { 42 | version: "0.8.9", 43 | settings: { 44 | optimizer: { 45 | enabled: true, 46 | runs: 200 47 | } 48 | } 49 | } 50 | ] 51 | }, 52 | namedAccounts: { 53 | deployer: 0 54 | }, 55 | defaultNetwork: "hardhat", 56 | networks: { 57 | hardhat: { 58 | gas: 12000000, 59 | allowUnlimitedContractSize: true, 60 | blockGasLimit: 12000000, 61 | accounts: { 62 | count: 250 63 | } 64 | }, 65 | mainnet: { 66 | url: `https://mainnet.infura.io/v3/${INFURA_KEY}`, 67 | accounts: PRIVATE_KEY ? [`0x${PRIVATE_KEY}`] : undefined 68 | }, 69 | rinkeby: { 70 | url: `https://rinkeby.infura.io/v3/${INFURA_KEY}`, 71 | accounts: PRIVATE_KEY ? [`0x${PRIVATE_KEY}`] : undefined, 72 | blockGasLimit: 12000000 73 | }, 74 | rinkebyDevnet: { 75 | url: `https://rinkeby.infura.io/v3/${INFURA_KEY}`, 76 | accounts: PRIVATE_KEY ? [`0x${PRIVATE_KEY}`] : undefined 77 | }, 78 | arbitrumMainnet: { 79 | url: `https://arbitrum-mainnet.infura.io/v3/${INFURA_KEY}`, 80 | accounts: PRIVATE_KEY ? [`0x${PRIVATE_KEY}`] : undefined 81 | }, 82 | arbitrumRinkeby: { 83 | url: `https://arbitrum-rinkeby.infura.io/v3/${INFURA_KEY}`, 84 | accounts: PRIVATE_KEY ? [`0x${PRIVATE_KEY}`] : undefined 85 | }, 86 | arbitrumRinkebyDevnet: { 87 | url: `https://arbitrum-rinkeby.infura.io/v3/${INFURA_KEY}`, 88 | accounts: PRIVATE_KEY ? [`0x${PRIVATE_KEY}`] : undefined 89 | }, 90 | localhost: { 91 | url: "http://127.0.0.1:8545" 92 | }, 93 | gethDev: { 94 | url: "http://127.0.0.1:8545", 95 | chainId: 54321 96 | } 97 | }, 98 | gasReporter: { 99 | enabled: process.env.REPORT_GAS ? true : false 100 | }, 101 | etherscan: { 102 | apiKey: process.env.ETHERSCAN_API_KEY, 103 | customChains: [] 104 | }, 105 | abiExporter: { 106 | path: "./abi", 107 | clear: true, 108 | flat: true 109 | } 110 | } 111 | 112 | export default config 113 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @ensdomains/=node_modules/@ensdomains/ 2 | @openzeppelin/=node_modules/@openzeppelin/ 3 | contracts/=contracts/ 4 | ds-test/=lib/ds-test/src/ 5 | eth-gas-reporter/=node_modules/eth-gas-reporter/ 6 | hardhat-deploy/=node_modules/hardhat-deploy/ 7 | hardhat/=node_modules/hardhat/ 8 | sol-explore/=node_modules/sol-explore/ 9 | -------------------------------------------------------------------------------- /scripts/run_integration_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Colors for output 4 | RED='\033[0;31m' 5 | GREEN='\033[0;32m' 6 | NC='\033[0m' 7 | 8 | docker-compose up -d 9 | if [ $? -ne 0 ]; then 10 | printf "${RED}docker-compose failed${NC}\n" 11 | exit -1 12 | fi 13 | 14 | echo "Starting Parity integration tests..." 15 | # Wait for parity-integration-tests 16 | PARITY_TEST_EXIT_CODE=$(docker wait parity-integration-tests) 17 | # Output parity-integration-tests logs 18 | docker logs parity-integration-tests 19 | # Check parity-integration-tests output 20 | if [ -z ${PARITY_TEST_EXIT_CODE+x} ] || [ "$PARITY_TEST_EXIT_CODE" -ne 0 ]; then 21 | printf "${RED}Parity integration tests failed${NC} - Exit Code: $PARITY_TEST_EXIT_CODE\n" 22 | else 23 | printf "${GREEN}Parity integration tests passed${NC}\n" 24 | fi 25 | 26 | echo "Starting Geth integration tests..." 27 | # Wait for geth-integration-tests 28 | GETH_TEST_EXIT_CODE=$(docker wait geth-integration-tests) 29 | # Output geth-integration-tests logs 30 | docker logs geth-integration-tests 31 | # Check geth-integration-tests output 32 | if [ -z ${GETH_TEST_EXIT_CODE+x} ] || [ "$GETH_TEST_EXIT_CODE" -ne 0 ]; then 33 | printf "${RED}Geth integration tests failed${NC} - Exit Code: $GETH_TEST_EXIT_CODE\n" 34 | else 35 | printf "${GREEN}Geth integration tests passed${NC}\n" 36 | fi 37 | 38 | # Clean up 39 | docker-compose down 40 | 41 | # If all tests passed return 0, else return 1 42 | ! (($PARITY_TEST_EXIT_CODE | $GETH_TEST_EXIT_CODE)) 43 | exit $? 44 | -------------------------------------------------------------------------------- /src/test/BondingManagerNullDelegateBondFix.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "ds-test/test.sol"; 4 | import "./base/GovernorBaseTest.sol"; 5 | import "contracts/bonding/BondingManager.sol"; 6 | import "contracts/snapshots/MerkleSnapshot.sol"; 7 | 8 | // forge test -vvv --fork-url --fork-block-number 6768456 --match-contract BondingManagerNullDelegateBondFix 9 | contract BondingManagerNullDelegateBondFix is GovernorBaseTest { 10 | BondingManager public constant BONDING_MANAGER = BondingManager(0x35Bcf3c30594191d53231E4FF333E8A770453e40); 11 | 12 | bytes32 public constant BONDING_MANAGER_TARGET_ID = keccak256("BondingManagerTarget"); 13 | 14 | BondingManager public newBondingManagerTarget; 15 | 16 | function setUp() public { 17 | newBondingManagerTarget = new BondingManager(address(CONTROLLER)); 18 | 19 | (, bytes20 gitCommitHash) = CONTROLLER.getContractInfo(BONDING_MANAGER_TARGET_ID); 20 | 21 | stageAndExecuteOne( 22 | address(CONTROLLER), 23 | 0, 24 | abi.encodeWithSelector( 25 | CONTROLLER.setContractInfo.selector, 26 | BONDING_MANAGER_TARGET_ID, 27 | address(newBondingManagerTarget), 28 | gitCommitHash 29 | ) 30 | ); 31 | } 32 | 33 | function testUpgrade() public { 34 | (, bytes20 gitCommitHash) = fetchContractInfo(BONDING_MANAGER_TARGET_ID); 35 | 36 | // Check that new BondingManagerTarget is registered 37 | (address infoAddr, bytes20 infoGitCommitHash) = fetchContractInfo(BONDING_MANAGER_TARGET_ID); 38 | assertEq(infoAddr, address(newBondingManagerTarget)); 39 | assertEq(infoGitCommitHash, gitCommitHash); 40 | } 41 | 42 | function testNullDelegateBond() public { 43 | // This test should be run with --fork-block-number 6768456 44 | // We are forking right after https://arbiscan.io/address/0xF8E893C7D84E366f7Bc6bc1cdB568Ff8c91bCF57 45 | // This is the corresponding L1 block number 46 | CHEATS.roll(14265594); 47 | 48 | address delegator = 0xF8E893C7D84E366f7Bc6bc1cdB568Ff8c91bCF57; 49 | address delegate = 0x5bE44e23041E93CDF9bCd5A0968524e104e38ae1; 50 | 51 | CHEATS.prank(delegator); 52 | BONDING_MANAGER.bond(0, delegate); 53 | 54 | (, , address delegateAddress, , , , ) = BONDING_MANAGER.getDelegator(delegator); 55 | 56 | assertEq(delegateAddress, delegate); 57 | assertEq(BONDING_MANAGER.transcoderTotalStake(address(0)), 0); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/BondingManagerNullDelegateTransferBondFix.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "ds-test/test.sol"; 4 | import "./base/GovernorBaseTest.sol"; 5 | import "contracts/bonding/BondingManager.sol"; 6 | import "contracts/snapshots/MerkleSnapshot.sol"; 7 | import "./interfaces/IL2Migrator.sol"; 8 | 9 | // forge test -vvv --fork-url --fork-block-number 6768454 --match-contract BondingManagerNullDelegateTransferBondFix 10 | contract BondingManagerNullDelegateTransferBondFix is GovernorBaseTest { 11 | BondingManager public constant BONDING_MANAGER = BondingManager(0x35Bcf3c30594191d53231E4FF333E8A770453e40); 12 | MerkleSnapshot public constant MERKLE_SNAPSHOT = MerkleSnapshot(0x10736ffaCe687658F88a46D042631d182C7757f7); 13 | IL2Migrator public constant L2_MIGRATOR = IL2Migrator(0x148D5b6B4df9530c7C76A810bd1Cdf69EC4c2085); 14 | 15 | bytes32 public constant BONDING_MANAGER_TARGET_ID = keccak256("BondingManagerTarget"); 16 | 17 | BondingManager public newBondingManagerTarget; 18 | 19 | function setUp() public { 20 | newBondingManagerTarget = new BondingManager(address(CONTROLLER)); 21 | 22 | (, bytes20 gitCommitHash) = CONTROLLER.getContractInfo(BONDING_MANAGER_TARGET_ID); 23 | 24 | stageAndExecuteOne( 25 | address(CONTROLLER), 26 | 0, 27 | abi.encodeWithSelector( 28 | CONTROLLER.setContractInfo.selector, 29 | BONDING_MANAGER_TARGET_ID, 30 | address(newBondingManagerTarget), 31 | gitCommitHash 32 | ) 33 | ); 34 | } 35 | 36 | function testUpgrade() public { 37 | (, bytes20 gitCommitHash) = CONTROLLER.getContractInfo(BONDING_MANAGER_TARGET_ID); 38 | 39 | // Check that new BondingManagerTarget is registered 40 | (address infoAddr, bytes20 infoGitCommitHash) = fetchContractInfo(BONDING_MANAGER_TARGET_ID); 41 | assertEq(infoAddr, address(newBondingManagerTarget)); 42 | assertEq(infoGitCommitHash, gitCommitHash); 43 | } 44 | 45 | function testTransferBond() public { 46 | CHEATS.mockCall( 47 | address(MERKLE_SNAPSHOT), 48 | abi.encodeWithSelector(MERKLE_SNAPSHOT.verify.selector), 49 | abi.encode(true) 50 | ); 51 | 52 | // This test should be run with --fork-block-number 6768454 53 | // We are forking right before https://arbiscan.io/address/0xF8E893C7D84E366f7Bc6bc1cdB568Ff8c91bCF57 54 | // This is the corresponding L1 block number 55 | CHEATS.roll(14265594); 56 | 57 | address delegator = 0xF8E893C7D84E366f7Bc6bc1cdB568Ff8c91bCF57; 58 | address delegate = 0x5bE44e23041E93CDF9bCd5A0968524e104e38ae1; 59 | bytes32[] memory proof; 60 | CHEATS.prank(delegator); 61 | L2_MIGRATOR.claimStake(delegate, 500000000000000000000, 0, proof, address(0)); 62 | 63 | (, , address delegateAddress, , , , ) = BONDING_MANAGER.getDelegator(delegator); 64 | 65 | assertEq(delegateAddress, delegate); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/BondingManagerTransferBondFix.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "ds-test/test.sol"; 4 | import "./base/GovernorBaseTest.sol"; 5 | import "contracts/bonding/BondingManager.sol"; 6 | import "contracts/snapshots/MerkleSnapshot.sol"; 7 | import "./interfaces/IL2Migrator.sol"; 8 | import "./interfaces/ICheatCodes.sol"; 9 | 10 | // forge test -vvv --fork-url --fork-block-number 6737758 --match-contract BondingManagerTransferBondFix 11 | contract BondingManagerTransferBondFix is GovernorBaseTest { 12 | BondingManager public constant BONDING_MANAGER = BondingManager(0x35Bcf3c30594191d53231E4FF333E8A770453e40); 13 | MerkleSnapshot public constant MERKLE_SNAPSHOT = MerkleSnapshot(0x10736ffaCe687658F88a46D042631d182C7757f7); 14 | IL2Migrator public constant L2_MIGRATOR = IL2Migrator(0x148D5b6B4df9530c7C76A810bd1Cdf69EC4c2085); 15 | 16 | bytes32 public constant BONDING_MANAGER_TARGET_ID = keccak256("BondingManagerTarget"); 17 | 18 | BondingManager public newBondingManagerTarget; 19 | 20 | function setUp() public { 21 | newBondingManagerTarget = new BondingManager(address(CONTROLLER)); 22 | 23 | (, bytes20 gitCommitHash) = CONTROLLER.getContractInfo(BONDING_MANAGER_TARGET_ID); 24 | 25 | stageAndExecuteOne( 26 | address(CONTROLLER), 27 | 0, 28 | abi.encodeWithSelector( 29 | CONTROLLER.setContractInfo.selector, 30 | BONDING_MANAGER_TARGET_ID, 31 | address(newBondingManagerTarget), 32 | gitCommitHash 33 | ) 34 | ); 35 | } 36 | 37 | function testTransferBond() public { 38 | CHEATS.mockCall( 39 | address(MERKLE_SNAPSHOT), 40 | abi.encodeWithSelector(MERKLE_SNAPSHOT.verify.selector), 41 | abi.encode(true) 42 | ); 43 | 44 | uint256 round = 2476; 45 | uint256 blockNum = round * 5760; 46 | CHEATS.roll(blockNum); 47 | 48 | address delegator = 0xcd8148C45ABFF4b3F01faE5aD31bC96AD6425054; 49 | bytes32[] memory proof; 50 | CHEATS.prank(delegator); 51 | L2_MIGRATOR.claimStake( 52 | 0x525419FF5707190389bfb5C87c375D710F5fCb0E, 53 | 11787420136760339363, 54 | 99994598814723, 55 | proof, 56 | address(0) 57 | ); 58 | 59 | (uint256 bondedAmount, , , , , uint256 lastClaimRound, ) = BONDING_MANAGER.getDelegator(delegator); 60 | uint256 pendingStake = BONDING_MANAGER.pendingStake(delegator, 0); 61 | uint256 pendingFees = BONDING_MANAGER.pendingFees(delegator, 0); 62 | 63 | assertEq(pendingStake, bondedAmount); 64 | assertEq(pendingFees, 0); 65 | assertEq(lastClaimRound, round); 66 | } 67 | 68 | function testUpgrade() public { 69 | (, bytes20 gitCommitHash) = CONTROLLER.getContractInfo(BONDING_MANAGER_TARGET_ID); 70 | 71 | // Check that new BondingManagerTarget is registered 72 | (address infoAddr, bytes20 infoGitCommitHash) = fetchContractInfo(BONDING_MANAGER_TARGET_ID); 73 | assertEq(infoAddr, address(newBondingManagerTarget)); 74 | assertEq(infoGitCommitHash, gitCommitHash); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/BondingVotesFeeLessVotesFix.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "ds-test/test.sol"; 4 | import "./base/GovernorBaseTest.sol"; 5 | import "contracts/Controller.sol"; 6 | import "contracts/bonding/BondingVotes.sol"; 7 | import "contracts/bonding/BondingManager.sol"; 8 | import "./interfaces/ICheatCodes.sol"; 9 | 10 | // forge test --match-contract BondingVotesFeeLessVotesFix --fork-url https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY -vvv --fork-block-number 140314540 11 | contract BondingVotesFeeLessVotesFix is GovernorBaseTest { 12 | bytes public constant arithmeticError = abi.encodeWithSignature("Panic(uint256)", 0x11); 13 | 14 | BondingManager public constant BONDING_MANAGER = BondingManager(0x35Bcf3c30594191d53231E4FF333E8A770453e40); 15 | IBondingVotes public constant BONDING_VOTES = IBondingVotes(0x0B9C254837E72Ebe9Fe04960C43B69782E68169A); 16 | 17 | bytes32 public constant BONDING_VOTES_TARGET_ID = keccak256("BondingVotesTarget"); 18 | 19 | BondingVotes public newBondingVotesTarget; 20 | 21 | address public DELEGATOR = 0xdB18A9353139880d73616e4972a855d66C9B69f0; 22 | 23 | function setUp() public { 24 | newBondingVotesTarget = new BondingVotes(address(CONTROLLER)); 25 | } 26 | 27 | function doUpgrade() internal { 28 | (, gitCommitHash) = CONTROLLER.getContractInfo(BONDING_VOTES_TARGET_ID); 29 | 30 | stageAndExecuteOne( 31 | address(CONTROLLER), 32 | 0, 33 | abi.encodeWithSelector( 34 | CONTROLLER.setContractInfo.selector, 35 | BONDING_VOTES_TARGET_ID, 36 | address(newBondingVotesTarget), 37 | gitCommitHash 38 | ) 39 | ); 40 | 41 | // Check that new BondingVotesTarget is registered 42 | (address infoAddr, bytes20 infoGitCommitHash) = fetchContractInfo(BONDING_VOTES_TARGET_ID); 43 | assertEq(infoAddr, address(newBondingVotesTarget)); 44 | assertEq(infoGitCommitHash, gitCommitHash); 45 | } 46 | 47 | function testBeforeUpgrade() public { 48 | CHEATS.expectRevert(arithmeticError); 49 | BONDING_VOTES.getVotes(DELEGATOR); 50 | } 51 | 52 | function testAfterUpgrade() public { 53 | doUpgrade(); 54 | 55 | uint256 votes = BONDING_VOTES.getVotes(DELEGATOR); 56 | assertTrue(votes > 0); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/TicketBrokerForceCancelUnlockFix.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "ds-test/test.sol"; 4 | import "./base/GovernorBaseTest.sol"; 5 | import "contracts/pm/TicketBroker.sol"; 6 | import "./interfaces/ICheatCodes.sol"; 7 | 8 | // forge test --match-contract TicketBrokerForceCancelUnlockFix --fork-url https://arbitrum-mainnet.infura.io/v3/ -vvv --fork-block-number 121404685 9 | contract TicketBrokerForceCancelUnlockFix is GovernorBaseTest { 10 | TicketBroker public constant TICKET_BROKER = TicketBroker(0xa8bB618B1520E284046F3dFc448851A1Ff26e41B); 11 | 12 | bytes32 public constant TICKET_BROKER_TARGET_ID = keccak256("TicketBrokerTarget"); 13 | 14 | TicketBroker public newTicketBrokerTarget; 15 | 16 | address public thirdParty; 17 | address public broadcaster; 18 | 19 | function setUp() public { 20 | // Setup accounts 21 | thirdParty = newAddr(); 22 | broadcaster = newAddr(); 23 | 24 | uint256 mockBalance = 1000; 25 | 26 | CHEATS.deal(thirdParty, mockBalance); 27 | CHEATS.deal(broadcaster, mockBalance); 28 | 29 | // Broadcaster initiates an unlock 30 | CHEATS.startPrank(broadcaster); 31 | TICKET_BROKER.fundDepositAndReserve{ value: 2 }(1, 1); 32 | TICKET_BROKER.unlock(); 33 | CHEATS.stopPrank(); 34 | 35 | newTicketBrokerTarget = new TicketBroker(address(CONTROLLER)); 36 | 37 | (, gitCommitHash) = CONTROLLER.getContractInfo(TICKET_BROKER_TARGET_ID); 38 | 39 | stageAndExecuteOne( 40 | address(CONTROLLER), 41 | 0, 42 | abi.encodeWithSelector( 43 | CONTROLLER.setContractInfo.selector, 44 | TICKET_BROKER_TARGET_ID, 45 | address(newTicketBrokerTarget), 46 | gitCommitHash 47 | ) 48 | ); 49 | } 50 | 51 | function testUpgrade() public { 52 | // Check that new TicketBrokerTarget is registered 53 | (address infoAddr, bytes20 infoGitCommitHash) = fetchContractInfo(TICKET_BROKER_TARGET_ID); 54 | assertEq(infoAddr, address(newTicketBrokerTarget)); 55 | assertEq(infoGitCommitHash, gitCommitHash); 56 | } 57 | 58 | // A fundDepositAndReserveFor() call by a third party should not reset an unlock request after the upgrade 59 | function testThirdPartyFundDepositAndReserveFor() public { 60 | assertTrue(TICKET_BROKER.isUnlockInProgress(broadcaster)); 61 | 62 | CHEATS.prank(thirdParty); 63 | TICKET_BROKER.fundDepositAndReserveFor(broadcaster, 0, 0); 64 | 65 | assertTrue(TICKET_BROKER.isUnlockInProgress(broadcaster)); 66 | } 67 | 68 | // A fundDepositAndReserveFor() call by the broadcaster should still reset an unlock request after the upgrade 69 | function testSenderFundDepositAndReserveFor() public { 70 | assertTrue(TICKET_BROKER.isUnlockInProgress(broadcaster)); 71 | 72 | CHEATS.prank(broadcaster); 73 | TICKET_BROKER.fundDepositAndReserveFor(broadcaster, 0, 0); 74 | 75 | assertTrue(!TICKET_BROKER.isUnlockInProgress(broadcaster)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/TicketBrokerForceCancelUnlockPoC.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "ds-test/test.sol"; 4 | import "./base/GovernorBaseTest.sol"; 5 | import "contracts/pm/TicketBroker.sol"; 6 | import "./interfaces/ICheatCodes.sol"; 7 | 8 | // forge test --match-contract TicketBrokerForceCancelUnlockPoC --fork-url https://arbitrum-mainnet.infura.io/v3/ -vvv --fork-block-number 121404685 9 | contract TicketBrokerForceCancelUnlockPoC is GovernorBaseTest { 10 | TicketBroker public constant TICKET_BROKER = TicketBroker(0xa8bB618B1520E284046F3dFc448851A1Ff26e41B); 11 | 12 | address public attacker; 13 | address public broadcaster; 14 | 15 | function setUp() public { 16 | // Setup accounts 17 | attacker = newAddr(); 18 | broadcaster = newAddr(); 19 | 20 | uint256 mockBalance = 1000; 21 | 22 | CHEATS.deal(attacker, mockBalance); 23 | CHEATS.deal(broadcaster, mockBalance); 24 | } 25 | 26 | function testFundDepositAndReserveForPoC() public { 27 | // Broadcaster calls fundDepositAndReserve() 28 | CHEATS.startPrank(broadcaster); 29 | TICKET_BROKER.fundDepositAndReserve{ value: 1000 }(500, 500); 30 | 31 | // Broadcaster calls unlock() to initiate unlock period 32 | TICKET_BROKER.unlock(); 33 | CHEATS.stopPrank(); 34 | assertTrue(TICKET_BROKER.isUnlockInProgress(broadcaster)); 35 | 36 | // Attacker calls fundDepositAndReserveFor() to forcefully cancel the broadcaster's unlock period 37 | CHEATS.prank(attacker); 38 | // Note: The attacker does not need to add ETH to the broadcaster's deposit/reserve and only needs to pay gas fees for the attack 39 | TICKET_BROKER.fundDepositAndReserveFor(broadcaster, 0, 0); 40 | assertTrue(!TICKET_BROKER.isUnlockInProgress(broadcaster)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/base/GovernorBaseTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | import "ds-test/test.sol"; 4 | import "contracts/Controller.sol"; 5 | import "../interfaces/ICheatCodes.sol"; 6 | import "../interfaces/IGovernor.sol"; 7 | 8 | contract GovernorBaseTest is DSTest { 9 | ICheatCodes public constant CHEATS = ICheatCodes(HEVM_ADDRESS); 10 | 11 | IGovernor public constant GOVERNOR = IGovernor(0xD9dEd6f9959176F0A04dcf88a0d2306178A736a6); 12 | Controller public constant CONTROLLER = Controller(0xD8E8328501E9645d16Cf49539efC04f734606ee4); 13 | address public constant GOVERNOR_OWNER = 0x04F53A0bb244f015cC97731570BeD26F0229da05; 14 | 15 | bytes20 internal gitCommitHash; 16 | 17 | uint256 public testAccountCtr = 1; 18 | 19 | function newAddr() public returns (address) { 20 | address addr = CHEATS.addr(testAccountCtr); 21 | testAccountCtr++; 22 | return addr; 23 | } 24 | 25 | function stageAndExecuteOne( 26 | address _target, 27 | uint256 _value, 28 | bytes memory _data 29 | ) internal { 30 | address[] memory targets = new address[](1); 31 | uint256[] memory values = new uint256[](1); 32 | bytes[] memory data = new bytes[](1); 33 | targets[0] = _target; 34 | values[0] = _value; 35 | data[0] = _data; 36 | IGovernor.Update memory update = IGovernor.Update({ target: targets, value: values, data: data, nonce: 0 }); 37 | 38 | // Impersonate Governor owner 39 | CHEATS.prank(GOVERNOR_OWNER); 40 | GOVERNOR.stage(update, 0); 41 | GOVERNOR.execute(update); 42 | } 43 | 44 | function stageAndExecuteMany( 45 | address[] memory _target, 46 | uint256[] memory _value, 47 | bytes[] memory _data 48 | ) internal { 49 | IGovernor.Update memory update = IGovernor.Update({ target: _target, value: _value, data: _data, nonce: 0 }); 50 | 51 | // Impersonate Governor owner 52 | CHEATS.startPrank(GOVERNOR_OWNER); 53 | GOVERNOR.stage(update, 0); 54 | GOVERNOR.execute(update); 55 | CHEATS.stopPrank(); 56 | } 57 | 58 | function fetchContractInfo(bytes32 _targetId) internal view returns (address, bytes20) { 59 | (address infoAddr, bytes20 infoGitCommitHash) = CONTROLLER.getContractInfo(_targetId); 60 | return (infoAddr, infoGitCommitHash); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/interfaces/ICheatCodes.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | interface ICheatCodes { 4 | function roll(uint256) external; 5 | 6 | function prank(address) external; 7 | 8 | function startPrank(address) external; 9 | 10 | function stopPrank() external; 11 | 12 | function expectRevert(bytes4 message) external; 13 | 14 | function expectRevert(bytes calldata) external; 15 | 16 | function expectEmit( 17 | bool checkTopic1, 18 | bool checkTopic2, 19 | bool checkTopic3, 20 | bool checkData 21 | ) external; 22 | 23 | function mockCall( 24 | address, 25 | bytes calldata, 26 | bytes calldata 27 | ) external; 28 | 29 | function addr(uint256) external returns (address); 30 | 31 | function deal(address, uint256) external; 32 | 33 | function sign(uint256 key, bytes32 hash) 34 | external 35 | returns ( 36 | uint8 v, 37 | bytes32 r, 38 | bytes32 s 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/test/interfaces/IGovernor.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | interface IGovernor { 4 | struct Update { 5 | address[] target; 6 | uint256[] value; 7 | bytes[] data; 8 | uint256 nonce; 9 | } 10 | 11 | function stage(Update memory _update, uint256 _delay) external; 12 | 13 | function execute(Update memory _update) external payable; 14 | 15 | function cancel(Update memory _update) external; 16 | } 17 | -------------------------------------------------------------------------------- /src/test/interfaces/IL2Migrator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.9; 2 | 3 | interface IL2Migrator { 4 | struct MigrateDelegatorParams { 5 | // Address that is migrating from L1 6 | address l1Addr; 7 | // Address to use on L2 8 | // If null, l1Addr is used on L2 9 | address l2Addr; 10 | // Stake of l1Addr on L1 11 | uint256 stake; 12 | // Delegated stake of l1Addr on L1 13 | uint256 delegatedStake; 14 | // Fees of l1Addr on L1 15 | uint256 fees; 16 | // Delegate of l1Addr on L1 17 | address delegate; 18 | } 19 | 20 | function finalizeMigrateDelegator(MigrateDelegatorParams calldata) external; 21 | 22 | function claimStake( 23 | address, 24 | uint256, 25 | uint256, 26 | bytes32[] calldata, 27 | address 28 | ) external; 29 | } 30 | -------------------------------------------------------------------------------- /tasks/etherscan-verify-deployment.ts: -------------------------------------------------------------------------------- 1 | import {Address} from "hardhat-deploy/types" 2 | import {Etherscan} from "@nomicfoundation/hardhat-verify/etherscan" 3 | import {task} from "hardhat/config" 4 | import https from "https" 5 | import {HardhatRuntimeEnvironment} from "hardhat/types" 6 | 7 | task( 8 | "etherscan-verify-deployments", 9 | "Verifies all contracts in the deployments folder" 10 | ) 11 | .addOptionalVariadicPositionalParam( 12 | "contracts", 13 | "List of contracts to verify" 14 | ) 15 | .setAction(async (taskArgs, hre) => { 16 | const etherscan = await etherscanClient(hre) 17 | let deployments = Object.entries(await hre.deployments.all()) 18 | 19 | console.log(`Read ${deployments.length} deployments from environment`) 20 | 21 | if (taskArgs.contracts?.length) { 22 | deployments = deployments.filter(([name]) => 23 | taskArgs.contracts.includes(name) 24 | ) 25 | const names = deployments.map(t => t[0]).join(", ") 26 | console.log( 27 | `Filtered to ${deployments.length} contracts (${names})` 28 | ) 29 | } 30 | 31 | for (const [name, deployment] of deployments) { 32 | console.log() // newline 33 | 34 | const address = deployment.address 35 | const constructorArguments = deployment.args 36 | 37 | console.log( 38 | `Verifying ${name} at ${address} constructed with (${constructorArguments})` 39 | ) 40 | await hre.run("verify:verify", {address, constructorArguments}) 41 | 42 | if (name.endsWith("Proxy") && name !== "ManagerProxy") { 43 | const targetName = name.replace("Proxy", "Target") 44 | const target = await hre.deployments.get(targetName) 45 | 46 | console.log( 47 | `Verifying as proxy to ${targetName} at ${target.address}` 48 | ) 49 | await verifyProxyContract(etherscan, address, target.address) 50 | } 51 | } 52 | }) 53 | 54 | async function etherscanClient({config, network}: HardhatRuntimeEnvironment) { 55 | const apiKey = config.etherscan.apiKey 56 | const chainConfig = await Etherscan.getCurrentChainConfig( 57 | network.name, 58 | network.provider, 59 | [] 60 | ) 61 | return Etherscan.fromChainConfig(apiKey, chainConfig) 62 | } 63 | 64 | function verifyProxyContract( 65 | etherscan: Etherscan, 66 | proxyAddress: Address, 67 | targetAddress: Address 68 | ) { 69 | const url = new URL(etherscan.apiUrl) 70 | if (url.protocol !== "https:") { 71 | throw new Error("Etherscan API URL must use HTTPS") 72 | } 73 | 74 | const options = { 75 | hostname: url.hostname, 76 | path: 77 | url.pathname + 78 | `?module=contract&action=verifyproxycontract&address=${proxyAddress}` + 79 | `&expectedimplementation=${targetAddress}&apikey=${etherscan.apiKey}`, 80 | method: "GET" 81 | } 82 | 83 | return new Promise((resolve, reject) => { 84 | const req = https.request(options, res => { 85 | if (res.statusCode === 200) { 86 | return resolve() 87 | } 88 | 89 | reject( 90 | new Error( 91 | `Failed to verify proxy contract: ${res.statusCode} ${res.statusMessage}` 92 | ) 93 | ) 94 | }) 95 | req.on("error", reject) 96 | req.end() 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /tasks/print-contract-address.ts: -------------------------------------------------------------------------------- 1 | import {task} from "hardhat/config" 2 | 3 | task("print-contract-address", "Print a deployed contract address") 4 | .addParam("contract", "Contract name") 5 | .setAction(async (taskArgs, hre) => { 6 | const {deployments} = hre 7 | const contractDeployment = await deployments.get(taskArgs.contract) 8 | console.log(contractDeployment.address) 9 | }) 10 | -------------------------------------------------------------------------------- /tasks/register-gateway-with-router.ts: -------------------------------------------------------------------------------- 1 | import {task} from "hardhat/config" 2 | import { 3 | arbitrumBridgeContracts, 4 | getGasPriceBid, 5 | getMaxGas, 6 | getMaxSubmissionPrice, 7 | waitForTx, 8 | waitToRelayTxsToL2 9 | } from "../utils/arbitrum" 10 | 11 | task( 12 | "register-gateway-with-router", 13 | "Register gateway for LPT with GatewayRouter" 14 | ) 15 | .addParam("arbproviderurl", "Arbitrum provider URL") 16 | .addParam("gateway", "Gateway address") 17 | .setAction(async (taskArgs, hre) => { 18 | const {deployments, getNamedAccounts, ethers} = hre 19 | const {deployer} = await getNamedAccounts() 20 | 21 | const arbProvider = new ethers.providers.JsonRpcProvider( 22 | taskArgs.arbproviderurl 23 | ) 24 | 25 | const tokenDeployment = await deployments.get("ArbitrumLivepeerToken") 26 | const token = await ethers.getContractAt( 27 | "ArbitrumLivepeerToken", 28 | tokenDeployment.address 29 | ) 30 | 31 | const setGatewayABI = [ 32 | "function setGateway(address[],address[]) external" 33 | ] 34 | const setGatewayCalldata = new ethers.utils.Interface( 35 | setGatewayABI 36 | ).encodeFunctionData("setGateway", [ 37 | [token.address], 38 | [taskArgs.gateway] 39 | ]) 40 | const gasPriceBid = await getGasPriceBid(arbProvider) 41 | const maxSubmissionCost = await getMaxSubmissionPrice( 42 | arbProvider, 43 | setGatewayCalldata 44 | ) 45 | const maxGas = await getMaxGas( 46 | arbProvider, 47 | arbitrumBridgeContracts[hre.network.name].l1GatewayRouter, 48 | arbitrumBridgeContracts[hre.network.name].l2GatewayRouter, 49 | deployer, 50 | maxSubmissionCost, 51 | gasPriceBid, 52 | setGatewayCalldata 53 | ) 54 | const ethValue = maxSubmissionCost.add(gasPriceBid.mul(maxGas)) 55 | 56 | await waitToRelayTxsToL2( 57 | waitForTx( 58 | token.registerGatewayWithRouter( 59 | taskArgs.gateway, 60 | maxGas, 61 | gasPriceBid, 62 | maxSubmissionCost, 63 | deployer, 64 | {value: ethValue} 65 | ) 66 | ), 67 | arbitrumBridgeContracts[hre.network.name].inbox, 68 | ethers.provider, 69 | arbProvider 70 | ) 71 | }) 72 | -------------------------------------------------------------------------------- /tasks/set-contract-info.ts: -------------------------------------------------------------------------------- 1 | import {task} from "hardhat/config" 2 | import {contractId} from "../utils/helpers" 3 | 4 | task("set-contract-info", "Set contract info in the Controller") 5 | .addParam("name", "Contract name") 6 | .addParam("address", "Contract address") 7 | .addParam("gitcommithash", "Git commit hash") 8 | .setAction(async (taskArgs, hre) => { 9 | const {deployments, ethers} = hre 10 | const controllerDeployment = await deployments.get("Controller") 11 | const controller = await ethers.getContractAt( 12 | "Controller", 13 | controllerDeployment.address 14 | ) 15 | 16 | const id = contractId(taskArgs.name) 17 | await controller.setContractInfo( 18 | id, 19 | taskArgs.address, 20 | taskArgs.gitcommithash 21 | ) 22 | 23 | const info = await controller.getContractInfo(id) 24 | 25 | console.log( 26 | `${taskArgs.name} registered in Controller address=${info[0]} gitCommitHash=${info[1]}` 27 | ) 28 | }) 29 | -------------------------------------------------------------------------------- /tasks/set-round-length.ts: -------------------------------------------------------------------------------- 1 | import {task} from "hardhat/config" 2 | import {contractId} from "../utils/helpers" 3 | 4 | task("set-round-length", "Set round length in the RoundsManager") 5 | .addParam("roundlength", "Round length in blocks") 6 | .setAction(async (taskArgs, hre) => { 7 | const {deployments, ethers} = hre 8 | const controllerDeployment = await deployments.get("Controller") 9 | const controller = await ethers.getContractAt( 10 | "Controller", 11 | controllerDeployment.address 12 | ) 13 | 14 | const id = contractId("RoundsManager") 15 | const info = await controller.getContractInfo(id) 16 | const addr = info[0] 17 | 18 | const roundsManager = await ethers.getContractAt("RoundsManager", addr) 19 | 20 | await (await roundsManager.setRoundLength(taskArgs.roundlength)).wait() 21 | 22 | console.log( 23 | `RoundsManager roundLength=${await roundsManager.roundLength()}` 24 | ) 25 | }) 26 | -------------------------------------------------------------------------------- /tasks/treasury-renounce-admin-role.ts: -------------------------------------------------------------------------------- 1 | import {task} from "hardhat/config" 2 | import {Treasury} from "../typechain" 3 | 4 | task( 5 | "treasury-renounce-admin-role", 6 | "Renounces the admin role from the deployer once everything is good to go" 7 | ).setAction(async (taskArgs, hre) => { 8 | const {ethers, deployments} = hre 9 | 10 | const {deployer} = await hre.getNamedAccounts() 11 | 12 | const treasury = await deployments.get("Treasury") 13 | const Treasury: Treasury = await ethers.getContractAt( 14 | "Treasury", 15 | treasury.address 16 | ) 17 | 18 | const adminRole = await Treasury.TIMELOCK_ADMIN_ROLE() 19 | let hasAdminRole = await Treasury.hasRole(adminRole, deployer) 20 | if (!hasAdminRole) { 21 | console.log("Deployer does not have admin role") 22 | return 23 | } 24 | 25 | console.log("Renouncing admin role") 26 | await Treasury.renounceRole(adminRole, deployer).then(tx => tx.wait()) 27 | 28 | hasAdminRole = await Treasury.hasRole(adminRole, deployer) 29 | if (hasAdminRole) { 30 | throw new Error("Deployer still has admin role") 31 | } 32 | 33 | console.log("Success!") 34 | }) 35 | -------------------------------------------------------------------------------- /tasks/unpause.ts: -------------------------------------------------------------------------------- 1 | import {task} from "hardhat/config" 2 | 3 | task("unpause", "Unpause the Controller").setAction(async (_, hre) => { 4 | const {deployments, ethers} = hre 5 | const controllerDeployment = await deployments.get("Controller") 6 | const controller = await ethers.getContractAt( 7 | "Controller", 8 | controllerDeployment.address 9 | ) 10 | 11 | const paused = await controller.paused() 12 | if (!paused) { 13 | throw new Error(`Controller ${controller.address} paused=${paused}`) 14 | } 15 | 16 | await controller.unpause() 17 | 18 | console.log( 19 | `Controller ${controller.address} paused=${await controller.paused()}` 20 | ) 21 | }) 22 | -------------------------------------------------------------------------------- /test/helpers/calcTxCost.js: -------------------------------------------------------------------------------- 1 | import {BigNumber} from "ethers" 2 | 3 | export default async txRes => { 4 | const receipt = await txRes.wait() 5 | const tx = await web3.eth.getTransaction(txRes.hash) 6 | const gasPrice = BigNumber.from(tx.gasPrice) 7 | const gasUsed = BigNumber.from(receipt.cumulativeGasUsed.toString()) 8 | return gasPrice.mul(gasUsed) 9 | } 10 | -------------------------------------------------------------------------------- /test/helpers/executeLIP36Upgrade.js: -------------------------------------------------------------------------------- 1 | import {contractId} from "../../utils/helpers" 2 | import {ethers} from "hardhat" 3 | 4 | module.exports = async function( 5 | controller, 6 | roundsManager, 7 | bondingManagerProxyAddress 8 | ) { 9 | // See Deployment section of https://github.com/livepeer/LIPs/blob/master/LIPs/LIP-36.md 10 | 11 | // Define LIP-36 round 12 | const lip36Round = await roundsManager.currentRound() 13 | 14 | // Deploy a new RoundsManager implementation contract 15 | // Note: In this test, we use the same implementation contract as the one currently deployed because 16 | // this repo does not contain the old implementation contract. In practice, the deployed implementation contract 17 | // would be different than the new implementation contract and we would be using the RoundsManager instead of the AdjustableRoundsManager 18 | const roundsManagerTarget = await ( 19 | await ethers.getContractFactory("AdjustableRoundsManager") 20 | ).deploy(controller.address) 21 | 22 | // Deploy a new BondingManager implementation contract 23 | const ll = await ( 24 | await ethers.getContractFactory("SortedDoublyLL") 25 | ).deploy() 26 | const bondingManagerTarget = await ( 27 | await ethers.getContractFactory("BondingManager", { 28 | libraries: { 29 | SortedDoublyLL: ll.address 30 | } 31 | }) 32 | ).deploy(controller.address) 33 | 34 | // Register the new RoundsManager implementation contract 35 | await controller.setContractInfo( 36 | contractId("RoundsManagerTarget"), 37 | roundsManagerTarget.address, 38 | "0x3031323334353637383930313233343536373839" 39 | ) 40 | 41 | // Set LIP upgrade round 42 | await roundsManager.setLIPUpgradeRound(36, lip36Round) 43 | 44 | // Register the new BondingManager implementation contract 45 | await controller.setContractInfo( 46 | contractId("BondingManagerTarget"), 47 | bondingManagerTarget.address, 48 | "0x3031323334353637383930313233343536373839" 49 | ) 50 | 51 | return await ethers.getContractAt( 52 | "BondingManager", 53 | bondingManagerProxyAddress 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /test/helpers/expectFail.js: -------------------------------------------------------------------------------- 1 | export default async (promise, reason) => { 2 | try { 3 | await promise 4 | } catch (error) { 5 | assert.isTrue( 6 | error.message.includes(reason), 7 | `Reverted, but with a different reason: ${error.message}` 8 | ) 9 | return 10 | } 11 | 12 | assert.fail("Expected revert did not occur") 13 | } 14 | -------------------------------------------------------------------------------- /test/helpers/expectThrow.js: -------------------------------------------------------------------------------- 1 | export default async promise => { 2 | try { 3 | await promise 4 | } catch (error) { 5 | // TODO: Check jump destination to destinguish between a throw 6 | // and an actual invalid jump. 7 | const invalidOpcode = error.message.search("invalid opcode") >= 0 8 | // TODO: When we contract A calls contract B, and B throws, instead 9 | // of an 'invalid jump', we get an 'out of gas' error. How do 10 | // we distinguish this from an actual out of gas event? (The 11 | // testrpc log actually show an 'invalid jump' event.) 12 | const outOfGas = error.message.search("out of gas") >= 0 13 | const revert = error.message.search("revert") >= 0 14 | assert( 15 | invalidOpcode || outOfGas || revert, 16 | "Expected throw, got '" + error + "' instead" 17 | ) 18 | return 19 | } 20 | assert.fail("Expected throw not received") 21 | } 22 | -------------------------------------------------------------------------------- /test/helpers/governorEnums.js: -------------------------------------------------------------------------------- 1 | export const VoteType = { 2 | Against: 0, 3 | For: 1, 4 | Abstain: 2 5 | } 6 | 7 | export const ProposalState = { 8 | Pending: 0, 9 | Active: 1, 10 | Canceled: 2, 11 | Defeated: 3, 12 | Succeeded: 4, 13 | Queued: 5, 14 | Expired: 6, 15 | Executed: 7 16 | } 17 | -------------------------------------------------------------------------------- /test/helpers/math.js: -------------------------------------------------------------------------------- 1 | import {constants} from "../../utils/constants" 2 | import {BigNumber} from "ethers" 3 | // Returns a / b scaled by PERC_DIVISOR 4 | // See percPoints() in contracts/libraries/MathUtils.sol 5 | const percPoints = (a, b) => { 6 | return _percPoints(a, b, BigNumber.from(constants.PERC_DIVISOR)) 7 | } 8 | 9 | // Returns a * (b / c) scaled by PERC_DIVISOR 10 | // See percOf() in contracts/libraries/MathUtils.sol 11 | const percOf = (a, b, c = BigNumber.from(constants.PERC_DIVISOR)) => { 12 | return _percOf(a, b, c, BigNumber.from(constants.PERC_DIVISOR)) 13 | } 14 | 15 | const precise = { 16 | percPoints: (a, b) => { 17 | return _percPoints(a, b, constants.PERC_DIVISOR_PRECISE) 18 | }, 19 | percOf: (a, b, c = constants.PERC_DIVISOR_PRECISE) => { 20 | return _percOf(a, b, c, constants.PERC_DIVISOR_PRECISE) 21 | } 22 | } 23 | 24 | const v2 = { 25 | percPoints: (a, b) => { 26 | return _percPoints(a, b, constants.PERC_DIVISOR_V2) 27 | }, 28 | percOf: (a, b, c = constants.PERC_DIVISOR_V2) => { 29 | return _percOf(a, b, c, constants.PERC_DIVISOR_V2) 30 | } 31 | } 32 | 33 | const _percPoints = (a, b, percDivisor) => { 34 | return a.mul(percDivisor).div(b) 35 | } 36 | 37 | const _percOf = (a, b, c, percDivisor) => { 38 | return a.mul(_percPoints(b, c, percDivisor)).div(percDivisor) 39 | } 40 | 41 | module.exports = { 42 | percPoints, 43 | percOf, 44 | precise, 45 | v2 46 | } 47 | -------------------------------------------------------------------------------- /test/helpers/setupIntegrationTest.ts: -------------------------------------------------------------------------------- 1 | import {deployments} from "hardhat" 2 | import {GenericMock__factory} from "../../typechain" 3 | import {contractId} from "../../utils/helpers" 4 | 5 | const setupIntegrationTest = deployments.createFixture( 6 | async ( 7 | {deployments, getNamedAccounts, ethers}, 8 | opts?: { tags: string[] } 9 | ) => { 10 | const tags = opts?.tags ?? ["Contracts"] 11 | const fixture = await deployments.fixture(tags) 12 | const {deployer} = await getNamedAccounts() 13 | const signer = await ethers.getSigner(deployer) 14 | 15 | const controller = await ethers.getContractAt( 16 | "Controller", 17 | fixture.Controller.address 18 | ) 19 | 20 | const GenericMock: GenericMock__factory = 21 | await ethers.getContractFactory("GenericMock") 22 | const mock = await GenericMock.deploy() 23 | 24 | const info = await controller.getContractInfo( 25 | contractId("BondingManager") 26 | ) 27 | const gitCommitHash = info[1] 28 | await controller 29 | .connect(signer) 30 | .setContractInfo( 31 | contractId("L2LPTDataCache"), 32 | mock.address, 33 | gitCommitHash 34 | ) 35 | 36 | return fixture 37 | } 38 | ) 39 | 40 | export default setupIntegrationTest 41 | -------------------------------------------------------------------------------- /test/helpers/signMsg.js: -------------------------------------------------------------------------------- 1 | export const getLongSigV = signature => { 2 | return parseInt(signature.slice(signature.length - 2, signature.length), 16) 3 | } 4 | 5 | export const getEIP2098V = signature => { 6 | // uint8 v = uint8((uint256(vs) >> 255) + 27); 7 | const sigToBytes = web3.utils.hexToBytes(signature) 8 | const v = (sigToBytes[32] >> 7) + 27 9 | return v 10 | } 11 | 12 | export const fixSig = sig => { 13 | // The recover() in ECDSA.sol from openzeppelin-solidity requires signatures to have a v-value that is 27/28 14 | // ETH clients that implement eth_sign will return a signature with a v-value that is 27/28 or 0/1 (geth returns 27/28 and ganache returns 0/1) 15 | // In order to support all ETH clients that implement eth_sign, we can fix the signature by ensuring that the v-value is 27/28 16 | let v = getLongSigV(sig) 17 | if (v < 27) { 18 | v += 27 19 | } 20 | 21 | return sig.slice(0, sig.length - 2) + v.toString(16) 22 | } 23 | 24 | export const web3Sign = async (msg, account) => { 25 | return web3.eth.sign(msg, account) 26 | } 27 | 28 | export default async (msg, account) => { 29 | return fixSig(await web3Sign(msg, account)) 30 | } 31 | 32 | export const flipV = sig => { 33 | let v = parseInt(sig.slice(sig.length - 2, sig.length), 16) 34 | if (v === 27) { 35 | v = 28 36 | } else if (v === 28) { 37 | v = 27 38 | } else { 39 | throw new Error(`unrecognized V value ${v}`) 40 | } 41 | const result = sig.slice(0, sig.length - 2).concat(v.toString(16)) 42 | return result 43 | } 44 | 45 | // from openzeppelin [https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5b28259dacf47fc208e03611eb3ba8eeaed63cc0/test/utils/cryptography/ECDSA.test.js#L12-L33] 46 | export function to2098Format(signature) { 47 | const long = web3.utils.hexToBytes(signature) 48 | 49 | if (long.length !== 65) { 50 | throw new Error("invalid signature length (expected long format)") 51 | } 52 | if (long[32] >> 7 === 1) { 53 | throw new Error("invalid signature 's' value") 54 | } 55 | const short = long.slice(0, 64) 56 | short[32] |= long[64] % 27 << 7 // set the first bit of the 32nd byte to the v parity bit 57 | return web3.utils.bytesToHex(short) 58 | } 59 | 60 | // from openzeppelin [https://github.com/OpenZeppelin/openzeppelin-contracts/blob/5b28259dacf47fc208e03611eb3ba8eeaed63cc0/test/utils/cryptography/ECDSA.test.js#L12-L33] 61 | export function from2098Format(signature) { 62 | const short = web3.utils.hexToBytes(signature) 63 | if (short.length !== 64) { 64 | throw new Error("invalid signature length (expected short format)") 65 | } 66 | short.push((short[32] >> 7) + 27) 67 | short[32] &= (1 << 7) - 1 // zero out the first bit of 1 the 32nd byte 68 | return web3.utils.bytesToHex(short) 69 | } 70 | -------------------------------------------------------------------------------- /test/helpers/ticket.js: -------------------------------------------------------------------------------- 1 | // TODO: Eventually avoid conditionally loading from hardhat 2 | // using global web3 variable injected by truffle 3 | 4 | import {web3} from "hardhat" 5 | import {constants} from "../../utils/constants" 6 | 7 | const DUMMY_TICKET_CREATION_ROUND = 10 8 | const DUMMY_TICKET_CREATION_ROUND_BLOCK_HASH = web3.utils.keccak256("foo") 9 | 10 | const createTicket = ticketObj => { 11 | ticketObj = ticketObj ? ticketObj : {} 12 | 13 | return { 14 | recipient: isSet(ticketObj.recipient) ? 15 | ticketObj.recipient : 16 | constants.NULL_ADDRESS, 17 | sender: isSet(ticketObj.sender) ? 18 | ticketObj.sender : 19 | constants.NULL_ADDRESS, 20 | faceValue: isSet(ticketObj.faceValue) ? ticketObj.faceValue : 0, 21 | winProb: isSet(ticketObj.winProb) ? ticketObj.winProb : 0, 22 | senderNonce: isSet(ticketObj.senderNonce) ? ticketObj.senderNonce : 0, 23 | recipientRandHash: isSet(ticketObj.recipientRandHash) ? 24 | ticketObj.recipientRandHash : 25 | constants.NULL_BYTES, 26 | auxData: isSet(ticketObj.auxData) ? ticketObj.auxData : defaultAuxData() 27 | } 28 | } 29 | 30 | const createWinningTicket = ( 31 | recipient, 32 | sender, 33 | recipientRand, 34 | faceValue = 0, 35 | auxData = defaultAuxData() 36 | ) => { 37 | const recipientRandHash = web3.utils.soliditySha3(recipientRand) 38 | const ticketObj = { 39 | recipient, 40 | sender, 41 | faceValue, 42 | winProb: constants.MAX_UINT256.toString(), 43 | recipientRandHash, 44 | auxData 45 | } 46 | 47 | return createTicket(ticketObj) 48 | } 49 | 50 | const getTicketHash = ticketObj => { 51 | return web3.utils.soliditySha3( 52 | ticketObj.recipient, 53 | ticketObj.sender, 54 | ticketObj.faceValue, 55 | ticketObj.winProb, 56 | ticketObj.senderNonce, 57 | ticketObj.recipientRandHash, 58 | ticketObj.auxData 59 | ) 60 | } 61 | 62 | const defaultAuxData = () => 63 | createAuxData( 64 | DUMMY_TICKET_CREATION_ROUND, 65 | DUMMY_TICKET_CREATION_ROUND_BLOCK_HASH 66 | ) 67 | 68 | const createAuxData = (creationRound, blockHash) => { 69 | return web3.eth.abi.encodeParameters( 70 | ["uint256", "bytes32"], 71 | [creationRound, blockHash] 72 | ) 73 | } 74 | 75 | const isSet = v => { 76 | return typeof v != undefined && v != null 77 | } 78 | 79 | module.exports = { 80 | createAuxData, 81 | createTicket, 82 | createWinningTicket, 83 | getTicketHash, 84 | DUMMY_TICKET_CREATION_ROUND, 85 | DUMMY_TICKET_CREATION_ROUND_BLOCK_HASH 86 | } 87 | -------------------------------------------------------------------------------- /test/integration/BroadcasterWithdrawalFlow.js: -------------------------------------------------------------------------------- 1 | import calcTxCost from "../helpers/calcTxCost" 2 | 3 | import {ethers} from "hardhat" 4 | import setupIntegrationTest from "../helpers/setupIntegrationTest" 5 | 6 | import chai, {expect} from "chai" 7 | import {solidity} from "ethereum-waffle" 8 | chai.use(solidity) 9 | 10 | describe("BroadcasterWithdrawalFlow", () => { 11 | let signers 12 | let broadcaster 13 | 14 | let broker 15 | let minter 16 | let roundsManager 17 | 18 | const unlockPeriod = 100 19 | 20 | before(async () => { 21 | signers = await ethers.getSigners() 22 | broadcaster = signers[0].address 23 | const fixture = await setupIntegrationTest() 24 | broker = await ethers.getContractAt( 25 | "TicketBroker", 26 | fixture.TicketBroker.address 27 | ) 28 | minter = await ethers.getContractAt("Minter", fixture.Minter.address) 29 | roundsManager = await ethers.getContractAt( 30 | "AdjustableRoundsManager", 31 | fixture.AdjustableRoundsManager.address 32 | ) 33 | const controller = await ethers.getContractAt( 34 | "Controller", 35 | fixture.Controller.address 36 | ) 37 | 38 | await controller.unpause() 39 | 40 | // The reason for this intervention is that fast-forwarding the current default 41 | // unlockPeriod takes a very long time 42 | await broker.setUnlockPeriod(unlockPeriod) 43 | }) 44 | 45 | it("broadcaster withdraws deposit and penalty escrow", async () => { 46 | const deposit = ethers.utils.parseEther("1") 47 | const reserve = ethers.utils.parseEther("1") 48 | 49 | await broker.fundDeposit({value: deposit}) 50 | await broker.fundReserve({value: reserve}) 51 | const withdrawalAmount = deposit.add(reserve) 52 | 53 | await broker.unlock() 54 | const unlockPeriod = (await broker.unlockPeriod.call()).toNumber() 55 | const currentRound = (await roundsManager.currentRound()).toNumber() 56 | const roundLength = (await roundsManager.roundLength()).toNumber() 57 | await roundsManager.setBlockNum( 58 | currentRound * roundLength + unlockPeriod * roundLength 59 | ) 60 | 61 | const startBroadcasterBalance = await ethers.provider.getBalance( 62 | broadcaster 63 | ) 64 | const startMinterBalance = await ethers.provider.getBalance( 65 | minter.address 66 | ) 67 | 68 | const withdrawResult = await broker.withdraw() 69 | 70 | const endMinterBalance = await ethers.provider.getBalance( 71 | minter.address 72 | ) 73 | expect(startMinterBalance.sub(endMinterBalance)).to.equal( 74 | withdrawalAmount 75 | ) 76 | 77 | const txCost = await calcTxCost(withdrawResult) 78 | const endBroadcasterBalance = await ethers.provider.getBalance( 79 | broadcaster 80 | ) 81 | expect( 82 | endBroadcasterBalance 83 | .sub(startBroadcasterBalance) 84 | .add(txCost.toString()) 85 | ).to.equal(withdrawalAmount) 86 | }) 87 | }) 88 | -------------------------------------------------------------------------------- /test/unit/EarningsPool.js: -------------------------------------------------------------------------------- 1 | import runSolidityTest from "./helpers/runSolidityTest" 2 | 3 | runSolidityTest("TestEarningsPool", ["AssertUint"]) 4 | runSolidityTest("TestEarningsPoolLIP36", ["AssertUint"]) 5 | -------------------------------------------------------------------------------- /test/unit/LivepeerTokenFaucet.js: -------------------------------------------------------------------------------- 1 | import RPC from "../../utils/rpc" 2 | import chai, {expect, assert} from "chai" 3 | import {solidity} from "ethereum-waffle" 4 | import {ethers} from "hardhat" 5 | chai.use(solidity) 6 | 7 | describe("LivepeerTokenFaucet", () => { 8 | const faucetAmount = 10000000000000 9 | const requestAmount = 100 10 | const requestWait = 2 11 | 12 | let rpc 13 | let token 14 | let faucet 15 | let signers 16 | 17 | before(async () => { 18 | rpc = new RPC(web3) 19 | signers = await ethers.getSigners() 20 | const tokenFac = await ethers.getContractFactory("LivepeerToken") 21 | const faucetFac = await ethers.getContractFactory("LivepeerTokenFaucet") 22 | 23 | token = await tokenFac.deploy() 24 | faucet = await faucetFac.deploy( 25 | token.address, 26 | requestAmount, 27 | requestWait 28 | ) 29 | 30 | await token.grantRole( 31 | ethers.utils.solidityKeccak256(["string"], ["MINTER_ROLE"]), 32 | signers[0].address 33 | ) 34 | await token.mint(faucet.address, faucetAmount) 35 | }) 36 | 37 | describe("non-whitelisted sender requests", () => { 38 | it("sends request amount to sender", async () => { 39 | await faucet.connect(signers[1]).request() 40 | 41 | assert.equal( 42 | await token.balanceOf(signers[1].address), 43 | requestAmount, 44 | "token balance incorrect" 45 | ) 46 | }) 47 | 48 | it("fails if sender does not wait through request time", async () => { 49 | await expect(faucet.connect(signers[1]).request()).to.be.reverted 50 | }) 51 | 52 | it("sends request amount to sender again after request time", async () => { 53 | await rpc.increaseTime(2 * 60 * 60) 54 | await faucet.connect(signers[1]).request() 55 | 56 | assert.equal( 57 | await token.balanceOf(signers[1].address), 58 | requestAmount * 2, 59 | "token balance incorrect" 60 | ) 61 | }) 62 | }) 63 | 64 | describe("whitelisted sender requests", () => { 65 | it("owner whitelists an address", async () => { 66 | await faucet.addToWhitelist(signers[2].address) 67 | 68 | assert.equal( 69 | await faucet.isWhitelisted(signers[2].address), 70 | true, 71 | "address is not whitelisted" 72 | ) 73 | }) 74 | 75 | it("sender requests twice without waiting", async () => { 76 | await faucet.connect(signers[2]).request() 77 | await faucet.connect(signers[2]).request() 78 | 79 | assert.equal( 80 | await token.balanceOf(signers[2].address), 81 | requestAmount * 2, 82 | "token balance incorrect" 83 | ) 84 | }) 85 | 86 | it("owner removes address from whitelist", async () => { 87 | await faucet.removeFromWhitelist(signers[2].address) 88 | 89 | assert.equal( 90 | await faucet.isWhitelisted(signers[2].address), 91 | false, 92 | "address is whitelisted" 93 | ) 94 | }) 95 | 96 | it("fails if sender requests twice without waiting", async () => { 97 | await faucet.connect(signers[2]).request() 98 | await expect(faucet.connect(signers[2]).request()).to.be.reverted 99 | }) 100 | }) 101 | }) 102 | -------------------------------------------------------------------------------- /test/unit/Manager.js: -------------------------------------------------------------------------------- 1 | import Fixture from "./helpers/Fixture" 2 | 3 | import {web3, ethers} from "hardhat" 4 | 5 | import chai, {expect, assert} from "chai" 6 | import {solidity} from "ethereum-waffle" 7 | chai.use(solidity) 8 | 9 | describe("Manager", () => { 10 | let fixture 11 | let manager 12 | 13 | before(async () => { 14 | fixture = new Fixture(web3) 15 | await fixture.deploy() 16 | await fixture.deployAndRegister( 17 | await ethers.getContractFactory("ManagerFixture"), 18 | "ManagerFixture", 19 | fixture.controller.address 20 | ) 21 | 22 | const managerFixtureFac = await ethers.getContractFactory( 23 | "ManagerFixture" 24 | ) 25 | const managerFixture = await managerFixtureFac.deploy( 26 | fixture.controller.address 27 | ) 28 | manager = await ethers.getContractAt( 29 | "ManagerFixture", 30 | managerFixture.address 31 | ) 32 | }) 33 | 34 | beforeEach(async () => { 35 | await fixture.setUp() 36 | }) 37 | 38 | afterEach(async () => { 39 | await fixture.tearDown() 40 | }) 41 | 42 | // This is the only function not already tested in other tests, so it's the only one tested here 43 | describe("whenSystemPaused", () => { 44 | it("should disallow the call when the system is not paused", async () => { 45 | await expect(manager.checkSchrodingerCat()).to.be.revertedWith( 46 | "system is not paused" 47 | ) 48 | }) 49 | 50 | it("should allow the call when the system is paused", async () => { 51 | await fixture.controller.pause() 52 | const state = await manager.checkSchrodingerCat() 53 | assert.equal(state, "alive") 54 | }) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /test/unit/MathUtils.js: -------------------------------------------------------------------------------- 1 | import runSolidityTest from "./helpers/runSolidityTest" 2 | 3 | runSolidityTest("TestMathUtils", ["AssertBool", "AssertUint"]) 4 | -------------------------------------------------------------------------------- /test/unit/MathUtilsV2.js: -------------------------------------------------------------------------------- 1 | import runSolidityTest from "./helpers/runSolidityTest" 2 | 3 | runSolidityTest("TestMathUtilsV2", ["AssertBool", "AssertUint"]) 4 | -------------------------------------------------------------------------------- /test/unit/MerkleSnapshot.js: -------------------------------------------------------------------------------- 1 | import {keccak256, bufferToHex} from "ethereumjs-util" 2 | import MerkleTree from "../../utils/merkleTree" 3 | import Fixture from "./helpers/Fixture" 4 | import {web3, ethers} from "hardhat" 5 | import chai, {expect, assert} from "chai" 6 | import {solidity} from "ethereum-waffle" 7 | chai.use(solidity) 8 | 9 | describe("MerkleSnapshot", () => { 10 | let fixture 11 | let merkleSnapshot 12 | let signers 13 | 14 | before(async () => { 15 | signers = await ethers.getSigners() 16 | fixture = new Fixture(web3) 17 | await fixture.deploy() 18 | const merkleFac = await ethers.getContractFactory("MerkleSnapshot") 19 | merkleSnapshot = await merkleFac.deploy(fixture.controller.address) 20 | }) 21 | 22 | beforeEach(async () => { 23 | await fixture.setUp() 24 | }) 25 | 26 | afterEach(async () => { 27 | await fixture.tearDown() 28 | }) 29 | 30 | describe("setSnapshot", () => { 31 | it("reverts when caller is not controller owner", async () => { 32 | expect( 33 | merkleSnapshot 34 | .connect(signers[1]) 35 | .setSnapshot( 36 | ethers.utils.formatBytes32String("1"), 37 | ethers.utils.formatBytes32String("helloworld") 38 | ) 39 | ).to.be.revertedWith("caller must be Controller owner") 40 | }) 41 | 42 | it("sets a snapshot root for an snapshot ID", async () => { 43 | const id = ethers.utils.formatBytes32String("1") 44 | const root = ethers.utils.formatBytes32String("helloworld") 45 | await merkleSnapshot.setSnapshot(id, root) 46 | assert.equal(await merkleSnapshot.snapshot(id), root) 47 | }) 48 | }) 49 | 50 | describe("verify", () => { 51 | let leaves 52 | let tree 53 | const id = bufferToHex(keccak256("LIP-52")) 54 | before(async () => { 55 | leaves = ["a", "b", "c", "d"] 56 | tree = new MerkleTree(leaves) 57 | 58 | await merkleSnapshot.setSnapshot(id, tree.getHexRoot()) 59 | }) 60 | 61 | it("returns false when a proof is invalid", async () => { 62 | const badLeaves = ["d", "e", "f"] 63 | const badTree = new MerkleTree(badLeaves) 64 | const badProof = badTree.getHexProof(badLeaves[0]) 65 | 66 | const leaf = bufferToHex(keccak256(leaves[0])) 67 | 68 | assert.isFalse(await merkleSnapshot.verify(id, badProof, leaf)) 69 | }) 70 | 71 | it("returns false when leaf is not in the tree", async () => { 72 | const proof = tree.getHexProof(leaves[0]) 73 | const leaf = bufferToHex(keccak256("x")) 74 | 75 | assert.isFalse(await merkleSnapshot.verify(id, proof, leaf)) 76 | }) 77 | 78 | it("returns false when a proof is of invalid length", async () => { 79 | let proof = tree.getHexProof(leaves[0]) 80 | proof = proof.slice(0, proof.length - 1) 81 | const leaf = bufferToHex(keccak256(leaves[0])) 82 | 83 | assert.isFalse(await merkleSnapshot.verify(id, proof, leaf)) 84 | }) 85 | 86 | it("returns true when a proof is valid", async () => { 87 | const proof = tree.getHexProof(leaves[0]) 88 | const leaf = bufferToHex(keccak256(leaves[0])) 89 | assert.isTrue(await merkleSnapshot.verify(id, proof, leaf)) 90 | }) 91 | }) 92 | }) 93 | -------------------------------------------------------------------------------- /test/unit/Poll.js: -------------------------------------------------------------------------------- 1 | import Fixture from "./helpers/Fixture" 2 | 3 | import {web3, ethers} from "hardhat" 4 | 5 | import chai, {expect, assert} from "chai" 6 | import {solidity} from "ethereum-waffle" 7 | chai.use(solidity) 8 | 9 | describe("Poll", () => { 10 | let fixture 11 | let poll 12 | let startBlock 13 | let endBlock 14 | let signers 15 | 16 | before(async () => { 17 | signers = await ethers.getSigners() 18 | fixture = new Fixture(web3) 19 | }) 20 | 21 | beforeEach(async () => { 22 | await fixture.setUp() 23 | startBlock = await fixture.rpc.getBlockNumberAsync() 24 | endBlock = startBlock + 10 25 | poll = await (await ethers.getContractFactory("Poll")).deploy(endBlock) 26 | }) 27 | 28 | afterEach(async () => { 29 | await fixture.tearDown() 30 | }) 31 | 32 | describe("constructor", () => { 33 | it("initialize state: endBlock", async () => { 34 | assert.equal((await poll.endBlock()).toNumber(), endBlock) 35 | }) 36 | }) 37 | 38 | describe("vote", () => { 39 | it("emit \"Vote\" event when poll is active", async () => { 40 | let tx = poll.vote(0) 41 | await expect(tx) 42 | .to.emit(poll, "Vote") 43 | .withArgs(signers[0].address, 0) 44 | tx = poll.connect(signers[1]).vote(1) 45 | await expect(tx) 46 | .to.emit(poll, "Vote") 47 | .withArgs(signers[1].address, 1) 48 | }) 49 | 50 | it("revert when poll is inactive", async () => { 51 | await fixture.rpc.waitUntilBlock(endBlock + 1) 52 | await expect(poll.vote(0)).to.be.revertedWith("poll is over") 53 | }) 54 | }) 55 | 56 | describe("destroy", () => { 57 | it("revert when poll is active", async () => { 58 | await expect(poll.destroy()).to.be.revertedWith("poll is active") 59 | }) 60 | 61 | it("destroy the contract when poll has ended", async () => { 62 | await fixture.rpc.waitUntilBlock(endBlock + 1) 63 | const tx = await poll.destroy() 64 | assert.equal(await web3.eth.getCode(poll.address), "0x") 65 | assert.equal((await tx.wait()).status, 1) 66 | }) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/unit/PreciseMathUtils.js: -------------------------------------------------------------------------------- 1 | import runSolidityTest from "./helpers/runSolidityTest" 2 | 3 | runSolidityTest("TestPreciseMathUtils", ["AssertBool", "AssertUint"]) 4 | -------------------------------------------------------------------------------- /test/unit/ServiceRegistry.js: -------------------------------------------------------------------------------- 1 | import Fixture from "./helpers/Fixture" 2 | import {web3, ethers} from "hardhat" 3 | 4 | import chai, {expect, assert} from "chai" 5 | import {solidity} from "ethereum-waffle" 6 | chai.use(solidity) 7 | 8 | describe("ServiceRegistry", () => { 9 | let fixture 10 | let registry 11 | let signers 12 | let controller 13 | before(async () => { 14 | signers = await ethers.getSigners() 15 | fixture = new Fixture(web3) 16 | // Use dummy Controller in these unit tests 17 | // We are testing the logic of ServiceRegistry directly so we do not 18 | // interact with the contract via a proxy 19 | // Thus, we do not need an actual Controller for the tests 20 | controller = signers[0].address 21 | 22 | registry = await ( 23 | await ethers.getContractFactory("ServiceRegistry") 24 | ).deploy(controller) 25 | }) 26 | 27 | beforeEach(async () => { 28 | await fixture.setUp() 29 | }) 30 | 31 | afterEach(async () => { 32 | await fixture.tearDown() 33 | }) 34 | 35 | describe("constructor", () => { 36 | it("invokes base Manager contract constructor", async () => { 37 | assert.equal( 38 | await registry.controller(), 39 | controller, 40 | "wrong Controller address" 41 | ) 42 | }) 43 | }) 44 | 45 | describe("setServiceURI", () => { 46 | it("stores service URI endpoint for caller", async () => { 47 | await registry.setServiceURI("foo") 48 | await registry.connect(signers[1]).setServiceURI("bar") 49 | 50 | assert.equal( 51 | await registry.getServiceURI(signers[0].address), 52 | "foo", 53 | "wrong service URI stored for caller 1" 54 | ) 55 | assert.equal( 56 | await registry.getServiceURI(signers[1].address), 57 | "bar", 58 | "wrong service URI stored for caller 2" 59 | ) 60 | }) 61 | 62 | it("fires ServiceURIUpdate event", async () => { 63 | const tx = registry.setServiceURI("foo") 64 | await expect(tx) 65 | .to.emit(registry, "ServiceURIUpdate") 66 | .withArgs(signers[0].address, "foo") 67 | }) 68 | }) 69 | 70 | describe("getServiceURI", () => { 71 | it("returns service URI endpoint for provided address", async () => { 72 | await registry.setServiceURI("foo") 73 | await registry.connect(signers[1]).setServiceURI("bar") 74 | 75 | assert.equal( 76 | await registry.getServiceURI(signers[0].address), 77 | "foo", 78 | "wrong service URI stored for caller 1" 79 | ) 80 | assert.equal( 81 | await registry.getServiceURI(signers[1].address), 82 | "bar", 83 | "wrong service URI stored for caller 2" 84 | ) 85 | }) 86 | 87 | it("returns empty string for address without stored service URI endpoint", async () => { 88 | assert.equal( 89 | await registry.getServiceURI(signers[5].address), 90 | "", 91 | "should return empty string for address without service URI" 92 | ) 93 | }) 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /test/unit/SortedArrays.js: -------------------------------------------------------------------------------- 1 | import runSolidityTest from "./helpers/runSolidityTest" 2 | 3 | runSolidityTest( 4 | "TestSortedArrays", 5 | ["AssertUint", "AssertBool"], 6 | undefined, 7 | true 8 | ) 9 | -------------------------------------------------------------------------------- /test/unit/SortedDoublyLL.js: -------------------------------------------------------------------------------- 1 | import runSolidityTest from "./helpers/runSolidityTest" 2 | 3 | runSolidityTest("TestSortedDoublyLLFindWithHints", [ 4 | "SortedDoublyLL", 5 | "AssertAddress", 6 | "AssertUint" 7 | ]) 8 | runSolidityTest("TestSortedDoublyLLFindWithHints2", [ 9 | "SortedDoublyLL", 10 | "AssertAddress", 11 | "AssertUint" 12 | ]) 13 | runSolidityTest("TestSortedDoublyLLInsert", [ 14 | "SortedDoublyLL", 15 | "AssertAddress", 16 | "AssertUint", 17 | "AssertBool" 18 | ]) 19 | runSolidityTest("TestSortedDoublyLLRemove", [ 20 | "SortedDoublyLL", 21 | "AssertAddress", 22 | "AssertUint", 23 | "AssertBool" 24 | ]) 25 | runSolidityTest("TestSortedDoublyLLUpdateKey", [ 26 | "SortedDoublyLL", 27 | "AssertAddress", 28 | "AssertUint", 29 | "AssertBool" 30 | ]) 31 | -------------------------------------------------------------------------------- /test/unit/helpers/Fixture.js: -------------------------------------------------------------------------------- 1 | import RPC from "../../../utils/rpc" 2 | import {contractId} from "../../../utils/helpers" 3 | import {ethers} from "hardhat" 4 | 5 | export default class Fixture { 6 | constructor(web3) { 7 | this.rpc = new RPC(web3) 8 | this.commitHash = "0x3031323334353637383930313233343536373839" 9 | } 10 | 11 | async deploy() { 12 | const controllerFactory = await ethers.getContractFactory("Controller") 13 | 14 | this.controller = await controllerFactory.deploy() 15 | 16 | await this.deployMocks() 17 | await this.controller.unpause() 18 | } 19 | 20 | async deployMocks() { 21 | const GenericMock = await ethers.getContractFactory("GenericMock") 22 | const MinterMock = await ethers.getContractFactory("MinterMock") 23 | const BondingManagerMock = await ethers.getContractFactory( 24 | "BondingManagerMock" 25 | ) 26 | const BondingVotesMock = await ethers.getContractFactory( 27 | "BondingVotesMock" 28 | ) 29 | 30 | this.token = await this.deployAndRegister(GenericMock, "LivepeerToken") 31 | this.minter = await this.deployAndRegister(MinterMock, "Minter") 32 | this.treasury = await this.deployAndRegister(GenericMock, "Treasury") 33 | this.bondingManager = await this.deployAndRegister( 34 | BondingManagerMock, 35 | "BondingManager" 36 | ) 37 | this.bondingVotes = await this.deployAndRegister( 38 | BondingVotesMock, 39 | "BondingVotes" 40 | ) 41 | this.roundsManager = await this.deployAndRegister( 42 | GenericMock, 43 | "RoundsManager" 44 | ) 45 | this.jobsManager = await this.deployAndRegister( 46 | GenericMock, 47 | "JobsManager" 48 | ) 49 | this.ticketBroker = await this.deployAndRegister( 50 | GenericMock, 51 | "TicketBroker" 52 | ) 53 | this.merkleSnapshot = await this.deployAndRegister( 54 | GenericMock, 55 | "MerkleSnapshot" 56 | ) 57 | // Register TicketBroker with JobsManager contract ID because in a production system the Minter likely will not be upgraded to be 58 | // aware of the TicketBroker contract ID and it will only be aware of the JobsManager contract ID 59 | await this.register("JobsManager", this.ticketBroker.address) 60 | this.verifier = await this.deployAndRegister(GenericMock, "Verifier") 61 | this.l2LPTDataCache = await this.deployAndRegister( 62 | GenericMock, 63 | "L2LPTDataCache" 64 | ) 65 | } 66 | 67 | async register(name, addr) { 68 | // Use dummy Git commit hash 69 | await this.controller.setContractInfo( 70 | contractId(name), 71 | addr, 72 | this.commitHash 73 | ) 74 | } 75 | 76 | async deployAndRegister(contractFactory, name, ...args) { 77 | const contract = await contractFactory.deploy(...args) 78 | await contract.deployed() 79 | await this.register(name, contract.address) 80 | return contract 81 | } 82 | 83 | async setUp() { 84 | this.currentSnapshotId = await this.rpc.snapshot() 85 | } 86 | 87 | async tearDown() { 88 | await this.rpc.revert(this.currentSnapshotId) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /test/unit/helpers/expectCheckpoints.ts: -------------------------------------------------------------------------------- 1 | import {assert} from "chai" 2 | import {ethers} from "ethers" 3 | import Fixture from "./Fixture" 4 | 5 | type Checkpoint = { 6 | account: string 7 | startRound: number 8 | bondedAmount: number 9 | delegateAddress: string 10 | delegatedAmount: number 11 | lastClaimRound: number 12 | lastRewardRound: number 13 | } 14 | 15 | export default async function expectCheckpoints( 16 | fixture: Fixture, 17 | tx: ethers.providers.TransactionReceipt, 18 | ...checkpoints: Checkpoint[] 19 | ) { 20 | const filter = fixture.bondingVotes.filters.CheckpointBondingState() 21 | const events = await fixture.bondingVotes.queryFilter( 22 | filter, 23 | tx.blockNumber, 24 | tx.blockNumber 25 | ) 26 | 27 | assert.equal(events.length, checkpoints.length, "Checkpoint count") 28 | 29 | for (let i = 0; i < checkpoints.length; i++) { 30 | const expected = checkpoints[i] 31 | const {args} = events[i] 32 | const actual: Checkpoint = { 33 | account: args[0].toString(), 34 | startRound: args[1].toNumber(), 35 | bondedAmount: args[2].toNumber(), 36 | delegateAddress: args[3].toString(), 37 | delegatedAmount: args[4].toNumber(), 38 | lastClaimRound: args[5].toNumber(), 39 | lastRewardRound: args[6].toNumber() 40 | } 41 | 42 | for (const keyStr of Object.keys(expected)) { 43 | const key = keyStr as keyof Checkpoint // ts workaround 44 | assert.equal( 45 | actual[key], 46 | expected[key], 47 | `Checkpoint #${i + 1} ${key}` 48 | ) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "target": "es5", 5 | "module": "commonjs", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "outDir": "dist", 9 | "resolveJsonModule": true 10 | }, 11 | "include": ["./scripts", "./test", "./deploy", "./utils", "./tasks"], 12 | "files": ["./hardhat.config.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /utils/arbitrum/abis/ArbRetryableTx.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "inputs": [ 5 | { 6 | "internalType": "uint256", 7 | "name": "calldataSize", 8 | "type": "uint256" 9 | } 10 | ], 11 | "name": "getSubmissionPrice", 12 | "outputs": [ 13 | { 14 | "internalType": "uint256", 15 | "name": "", 16 | "type": "uint256" 17 | }, 18 | { 19 | "internalType": "uint256", 20 | "name": "", 21 | "type": "uint256" 22 | } 23 | ], 24 | "stateMutability": "view", 25 | "type": "function" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /utils/arbitrum/contracts.ts: -------------------------------------------------------------------------------- 1 | import {ethers} from "ethers" 2 | 3 | export const arbitrumBridgeContracts: any = { 4 | mainnet: { 5 | l1GatewayRouter: "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef", 6 | l2GatewayRouter: "0x5288c571Fd7aD117beA99bF60FE0846C4E84F933", 7 | inbox: "0x4c6f947Ae67F572afa4ae0730947DE7C874F95Ef", 8 | outbox: "0x760723CD2e632826c38Fef8CD438A4CC7E7E1A40" 9 | }, 10 | rinkeby: { 11 | l1GatewayRouter: "0x70C143928eCfFaf9F5b406f7f4fC28Dc43d68380", 12 | l2GatewayRouter: "0x9413AD42910c1eA60c737dB5f58d1C504498a3cD", 13 | inbox: "0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e", 14 | outbox: "0x2360A33905dc1c72b12d975d975F42BaBdcef9F3" 15 | } 16 | } 17 | 18 | export const arbitrumL2CoreContracts = { 19 | arbRetryableTx: "0x000000000000000000000000000000000000006E", 20 | nodeInterface: "0x00000000000000000000000000000000000000C8" 21 | } 22 | 23 | export function getArbitrumCoreContracts(l2: ethers.providers.BaseProvider) { 24 | return { 25 | arbRetryableTx: new ethers.Contract( 26 | arbitrumL2CoreContracts.arbRetryableTx, 27 | require("./abis/ArbRetryableTx.json").abi, 28 | l2 29 | ), 30 | nodeInterface: new ethers.Contract( 31 | arbitrumL2CoreContracts.nodeInterface, 32 | require("./abis/NodeInterface.json").abi, 33 | l2 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /utils/arbitrum/gas.ts: -------------------------------------------------------------------------------- 1 | import {BigNumber, ethers} from "ethers" 2 | import {getArbitrumCoreContracts} from "./contracts" 3 | 4 | export async function getGasPriceBid(l2: ethers.providers.BaseProvider): Promise { 5 | return await l2.getGasPrice() 6 | } 7 | 8 | export async function getMaxSubmissionPrice( 9 | l2: ethers.providers.BaseProvider, 10 | calldataOrCalldataLength: string | number 11 | ) { 12 | const calldataLength = 13 | typeof calldataOrCalldataLength === "string" ? calldataOrCalldataLength.length : calldataOrCalldataLength 14 | const [submissionPrice] = await getArbitrumCoreContracts(l2).arbRetryableTx.getSubmissionPrice(calldataLength) 15 | const maxSubmissionPrice = submissionPrice.mul(4) 16 | return maxSubmissionPrice 17 | } 18 | 19 | export async function getMaxGas( 20 | l2: ethers.providers.BaseProvider, 21 | sender: string, 22 | destination: string, 23 | refundDestination: string, 24 | maxSubmissionPrice: BigNumber, 25 | gasPriceBid: BigNumber, 26 | calldata: string 27 | ): Promise { 28 | const [estimatedGas] = await getArbitrumCoreContracts(l2).nodeInterface.estimateRetryableTicket( 29 | sender, 30 | ethers.utils.parseEther("0.05"), 31 | destination, 32 | 0, 33 | maxSubmissionPrice, 34 | refundDestination, 35 | refundDestination, 36 | 0, 37 | gasPriceBid, 38 | calldata 39 | ) 40 | const maxGas = estimatedGas.mul(4) 41 | 42 | return maxGas 43 | } 44 | -------------------------------------------------------------------------------- /utils/arbitrum/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./gas" 2 | export * from "./contracts" 3 | export * from "./messaging" 4 | export * from "./transactions" 5 | -------------------------------------------------------------------------------- /utils/arbitrum/transactions.ts: -------------------------------------------------------------------------------- 1 | import {ContractTransaction, providers} from "ethers" 2 | 3 | export async function waitForTx( 4 | tx: Promise, 5 | _confirmations?: number 6 | ): Promise { 7 | const resolvedTx = await tx 8 | const confirmations = _confirmations ?? chainIdToConfirmationsNeededForFinalization(resolvedTx.chainId) 9 | 10 | // we retry .wait b/c sometimes it fails for the first time 11 | try { 12 | return await resolvedTx.wait(confirmations) 13 | } catch (e) {} 14 | return await resolvedTx.wait(confirmations) 15 | } 16 | 17 | function chainIdToConfirmationsNeededForFinalization(chainId: number): number { 18 | const defaultWhenReorgsPossible = 3 19 | const defaultForInstantFinality = 0 20 | 21 | // covers mainnet and public testnets 22 | if (chainId < 6) { 23 | return defaultWhenReorgsPossible 24 | } else { 25 | return defaultForInstantFinality 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /utils/constants.js: -------------------------------------------------------------------------------- 1 | import {BigNumber, ethers} from "ethers" 2 | 3 | export const constants = { 4 | NULL_ADDRESS: "0x0000000000000000000000000000000000000000", 5 | NULL_BYTES: "0x0000000000000000000000000000000000000000000000000000000000000000", 6 | TOKEN_UNIT: ethers.utils.parseEther("1"), 7 | PERC_DIVISOR: 1000000, 8 | PERC_MULTIPLIER: 10000, 9 | PERC_DIVISOR_PRECISE: BigNumber.from(10).pow(27), 10 | PERC_DIVISOR_V2: BigNumber.from(1000000000), 11 | RESCALE_FACTOR: BigNumber.from(10).pow(21), 12 | MAX_UINT256: ethers.constants.MaxUint256, 13 | DelegatorStatus: { 14 | Pending: 0, 15 | Bonded: 1, 16 | Unbonded: 2 17 | }, 18 | TranscoderStatus: { 19 | NotRegistered: 0, 20 | Registered: 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /utils/helpers.js: -------------------------------------------------------------------------------- 1 | import {keccak256, bufferToHex} from "ethereumjs-util" 2 | import ethAbi from "ethereumjs-abi" 3 | export function contractId(name) { 4 | return ethers.utils.solidityKeccak256(["string"], [name]) 5 | } 6 | 7 | export function functionSig(name) { 8 | return bufferToHex(keccak256(name).slice(0, 4)) 9 | } 10 | 11 | export function eventSig(name) { 12 | return bufferToHex(keccak256(name)) 13 | } 14 | 15 | export function functionEncodedABI(name, params, values) { 16 | return bufferToHex(Buffer.concat([keccak256(name).slice(0, 4), ethAbi.rawEncode(params, values)])) 17 | } 18 | -------------------------------------------------------------------------------- /utils/rpc.js: -------------------------------------------------------------------------------- 1 | export default class RPC { 2 | constructor(web3) { 3 | this.web3 = web3 4 | } 5 | 6 | sendAsync(method, arg) { 7 | const req = { 8 | jsonrpc: "2.0", 9 | method: method, 10 | id: new Date().getTime() 11 | } 12 | 13 | if (arg) req.params = arg 14 | 15 | return new Promise((resolve, reject) => { 16 | return this.web3.currentProvider.send(req, (err, result) => { 17 | if (err) { 18 | reject(err) 19 | } else if (result && result.error) { 20 | reject(new Error("RPC Error: " + (result.error.message || result.error))) 21 | } else { 22 | resolve(result) 23 | } 24 | }) 25 | }) 26 | } 27 | 28 | // Change block time using TestRPC call evm_setTimestamp 29 | // https://github.com/numerai/contract/blob/master/test/numeraire.js 30 | increaseTime(time) { 31 | return this.sendAsync("evm_increaseTime", [time]) 32 | } 33 | 34 | mine() { 35 | return this.sendAsync("evm_mine") 36 | } 37 | 38 | snapshot() { 39 | return this.sendAsync("evm_snapshot") 40 | .then(res => res.result) 41 | } 42 | 43 | revert(snapshotId) { 44 | return this.sendAsync("evm_revert", [snapshotId]) 45 | } 46 | 47 | async wait(blocks = 1, seconds = 20) { 48 | const currentBlock = await this.getBlockNumberAsync() 49 | const targetBlock = currentBlock + blocks 50 | await this.waitUntilBlock(targetBlock, seconds) 51 | } 52 | 53 | async waitUntilBlock(targetBlock, seconds = 20) { 54 | let currentBlock = await this.getBlockNumberAsync() 55 | 56 | while (currentBlock < targetBlock) { 57 | await this.increaseTime(seconds) 58 | await this.mine() 59 | currentBlock++ 60 | } 61 | } 62 | 63 | async waitUntilNextBlockMultiple(blockMultiple, multiples = 1, seconds = 20) { 64 | const currentBlock = await this.getBlockNumberAsync() 65 | const additionalBlocks = (multiples - 1) * blockMultiple 66 | await this.waitUntilBlock(this.nextBlockMultiple(currentBlock, blockMultiple) + additionalBlocks) 67 | } 68 | 69 | getBlockNumberAsync() { 70 | return new Promise((resolve, reject) => { 71 | return this.web3.eth.getBlockNumber((err, blockNum) => { 72 | if (err) { 73 | reject(err) 74 | } else { 75 | resolve(blockNum) 76 | } 77 | }) 78 | }) 79 | } 80 | 81 | nextBlockMultiple(currentBlockNum, blockMultiple) { 82 | if (blockMultiple === 0) { 83 | return currentBlockNum 84 | } 85 | 86 | const remainder = currentBlockNum % blockMultiple 87 | 88 | if (remainder === 0) { 89 | return currentBlockNum 90 | } 91 | 92 | return currentBlockNum + blockMultiple - remainder 93 | } 94 | } 95 | --------------------------------------------------------------------------------