├── .circleci └── config.yml ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE └── PULL_REQUEST_TEMPLATE ├── .gitignore ├── .npmignore ├── .solcover.js ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── contracts ├── Bindings.sol ├── DarknodePayment │ ├── ClaimRewards.sol │ ├── ClaimlessRewards.sol │ ├── DarknodePayment.sol │ ├── DarknodePaymentMigrator.sol │ ├── DarknodePaymentStore.sol │ ├── DarknodeRegistryForwarder.sol │ └── ValidString.sol ├── DarknodeRegistry │ ├── DarknodeRegistry.sol │ ├── DarknodeRegistryStore.sol │ └── GetOperatorDarknodes.sol ├── DarknodeSlasher │ └── DarknodeSlasher.sol ├── Governance │ ├── Claimable.sol │ └── RenProxyAdmin.sol ├── Protocol │ └── Protocol.sol ├── RenToken │ └── RenToken.sol ├── libraries │ ├── CanReclaimTokens.sol │ ├── Compare.sol │ ├── ERC20WithFees.sol │ ├── LinkedList.sol │ ├── String.sol │ └── Validate.sol ├── migrations │ └── Migrations.sol └── test │ ├── Claimer.sol │ ├── CompareTest.sol │ ├── CycleChanger.sol │ ├── ERC20WithFeesTest.sol │ ├── ForceSend.sol │ ├── LinkedListTest.sol │ ├── StringTest.sol │ ├── ValidateTest.sol │ └── tokens │ ├── ImpreciseToken.sol │ ├── NonCompliantToken.sol │ ├── NormalToken.sol │ ├── PaymentToken.sol │ ├── ReturnsFalseToken.sol │ ├── SelfDestructingToken.sol │ └── TokenWithFees.sol ├── migrations ├── 1_darknodes.js ├── encode.js └── networks.js ├── package.json ├── patches ├── @openzeppelin+contracts+2.5.1.patch └── @openzeppelin+contracts-ethereum-package+2.5.0.patch ├── templates ├── contract.hbs ├── helpers.js └── prelude.sample.hbs ├── test ├── ClaimlessRewards │ ├── ClaimlessRewards.ts │ └── steps.ts ├── Compare.ts ├── DarknodePayment.ts ├── DarknodeRegistry.ts ├── DarknodeSlasher.ts ├── ERC20WithFees.ts ├── LinkedList.ts ├── Protocol.ts ├── String.ts ├── Validate.ts └── helper │ ├── logs.ts │ └── testUtils.ts ├── truffle.js ├── tsconfig.json ├── tslint.json ├── types └── chai │ └── index.d.ts └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2.1 6 | 7 | executors: 8 | default: 9 | docker: 10 | - image: circleci/node:12.22 11 | working_directory: ~/darknode-sol 12 | 13 | commands: 14 | install_deps: 15 | description: "Install dependencies" 16 | steps: 17 | # Download and cache dependencies 18 | - restore_cache: 19 | name: Restore node_modules 20 | keys: 21 | - v2-dependencies-{{ checksum "yarn.lock" }} 22 | - run: 23 | name: Install Dependencies 24 | command: yarn install 25 | - save_cache: 26 | name: Save node_modules 27 | paths: 28 | - node_modules 29 | key: v2-dependencies-{{ checksum "yarn.lock" }} 30 | 31 | jobs: 32 | build: 33 | executor: default 34 | steps: 35 | - checkout 36 | - install_deps 37 | - run: 38 | name: Run tests 39 | command: NODE_OPTIONS=--max_old_space_size=4096 yarn run coverage && yarn run coveralls 40 | 41 | workflows: 42 | build: 43 | jobs: 44 | - build 45 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | **Description** 2 | 3 | Describe the issue in as much detail as you can. 4 | 5 | **Expected Behavior** 6 | 7 | What is the expected behavior, compared to the behavior you are experiencing? 8 | 9 | **Steps to reproduce** 10 | 11 | Describe the steps to reproduce this issue. 12 | 13 | **Version** 14 | 15 | What version of the software is this issue relevant to? 16 | 17 | **Operating system** 18 | 19 | What operating system (and which version) are you using? 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | **Description** 2 | 3 | What is being addressed by this PR? 4 | 5 | **Motivation** 6 | 7 | What is the motivation for this change? 8 | 9 | **Design** 10 | 11 | - How is the change going to be implemented? (Design) 12 | - What packages that will be affected by this change? (Related work) 13 | - What PRs will be affected by this change? (Related work) 14 | 15 | **Unresolved Issues** 16 | 17 | What is not addressed by this PR? -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node / NPM / Yarn 2 | node_modules 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Build / test artifacts 8 | coverage 9 | dist 10 | coverageEnv 11 | coverage.json 12 | .coveralls.yml 13 | .coverage_artifacts 14 | .coverage_contracts 15 | *output.log 16 | 17 | # Configuration files 18 | *.env 19 | .env* 20 | 21 | # OS files 22 | .DS_Store 23 | 24 | # Solidity 25 | .node-xmlhttprequest-sync-* 26 | scTopics 27 | allFiredEvents 28 | in_complete_tests 29 | build/development 30 | local_* 31 | bindings.go 32 | types 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Node / NPM / Yarn 2 | node_modules 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Build / test artifacts 8 | coverage 9 | dist 10 | coverageEnv 11 | coverage.json 12 | .coveralls.yml 13 | .coverage_artifacts 14 | .coverage_contracts 15 | *output.log 16 | 17 | # Configuration files 18 | *.env 19 | .env* 20 | 21 | # OS files 22 | .DS_Store 23 | 24 | # Solidity 25 | .node-xmlhttprequest-sync-* 26 | scTopics 27 | allFiredEvents 28 | in_complete_tests 29 | build/development 30 | local_* 31 | bindings.go 32 | types 33 | 34 | 35 | # npmignore - content above this line is automatically generated and modifications may be omitted 36 | # see npmjs.com/npmignore for more details. 37 | test 38 | .circleci 39 | .github 40 | .vscode 41 | .gitattributes 42 | .solcover.js 43 | tsconfig.json 44 | tslint.json -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | copyPackages: ["@openzeppelin/contracts-ethereum-package"], // needed to import from node_modules. 3 | testrpcOptions: "-d --accounts 10 --port 8555", 4 | skipFiles: [ 5 | // REN token. 6 | "RenToken/RenToken.sol", 7 | 8 | // Contract for building bindings. 9 | "test/Bindings.sol", 10 | 11 | // Migration contract. 12 | "migrations/Migrations.sol", 13 | 14 | // Examples 15 | "Gateway/adapters/BasicAdapter.sol", 16 | "Gateway/examples/Vesting.sol", 17 | 18 | // Contracts for assisting the tests. 19 | "test/ERC20WithFeesTest.sol", 20 | "test/tokens/ImpreciseToken.sol", 21 | "test/tokens/SelfDestructingToken.sol", 22 | "test/tokens/NonCompliantToken.sol", 23 | "test/tokens/NormalToken.sol", 24 | "test/tokens/ReturnsFalseToken.sol", 25 | "test/tokens/TokenWithFees.sol", 26 | "test/LinkedListTest.sol", 27 | "test/ValidateTest.sol", 28 | "test/StringTest.sol", 29 | "test/CompareTest.sol", 30 | "test/tokens/PaymentToken.sol", 31 | "test/CycleChanger.sol" 32 | ] 33 | }; 34 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "solidity.packageDefaultDependenciesContractsDirectory": "" 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `⚖️ darknode-sol` 2 | 3 | ## Ethereum smart contracts for managing darknodes 4 | 5 | [![CircleCI](https://circleci.com/gh/renproject/darknode-sol.svg?style=shield)](https://circleci.com/gh/renproject/darknode-sol) 6 | [![Coverage Status](https://coveralls.io/repos/github/renproject/darknode-sol/badge.svg?branch=master)](https://coveralls.io/github/renproject/darknode-sol?branch=master) 7 | 8 | Ren has two repositories for its Solidity contract: 9 | 10 | - `darknode-sol` (this repository) - contracts on Ethereum for managing darknode registrations. 11 | - [`gateway-sol`](https://github.com/renproject/gateway-sol) - contracts on multiple EVM chains for minting and burning of ren-assets. 12 | 13 | Ren bootstraps off Ethereum to handle the REN token and darknode registrations. 14 | 15 | ## ~ [Documentation](https://renproject.github.io/ren-client-docs/contracts/) ~ 16 | 17 | - For the latest contract addresses, see the [contract addresses](https://renproject.github.io/ren-client-docs/contracts/deployments) page. 18 | - For a summary of each contract, see the [summary of contracts](https://renproject.github.io/ren-client-docs/contracts/summary) page. 19 | 20 |
21 | 22 | Development notes 23 | 24 | ## Tests 25 | 26 | Install the dependencies. 27 | 28 | ``` 29 | yarn install 30 | ``` 31 | 32 | Run the `ganache-cli` or an alternate Ethereum test RPC server on port 8545. The `-d` flag will use a deterministic mnemonic for reproducibility. 33 | 34 | ```sh 35 | yarn ganache-cli -d 36 | ``` 37 | 38 | Run the Truffle test suite. 39 | 40 | ```sh 41 | yarn run test 42 | ``` 43 | 44 | ## Coverage 45 | 46 | Run the Truffle test suite with coverage. 47 | 48 | ```sh 49 | yarn run coverage 50 | ``` 51 | 52 | Open the coverage file. 53 | 54 | ```sh 55 | open ./coverage/index.html 56 | ``` 57 | 58 | ## Deploying 59 | 60 | Add a `.env`, filling in the mnemonic and Infura key: 61 | 62 | ```sh 63 | MNEMONIC_KOVAN="..." 64 | MNEMONIC_MAINNET="..." 65 | INFURA_KEY="..." 66 | ``` 67 | 68 | Deploy to Kovan: 69 | 70 | ```sh 71 | NETWORK=kovan yarn run deploy 72 | ``` 73 | 74 | See `1_darknodes.js` for additional instructions. 75 | 76 | ## Verifying Contract Code 77 | 78 | Add an Etherscan API key to your `.env`: 79 | 80 | ``` 81 | ETHERSCAN_KEY="..." 82 | ``` 83 | 84 | Run the following (replacing the network and contract name): 85 | 86 | ```sh 87 | NETWORK=mainnet yarn run verify Contract1 Contract2 88 | ``` 89 | 90 |
91 | -------------------------------------------------------------------------------- /contracts/Bindings.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "./DarknodePayment/ClaimRewards.sol"; 4 | import "./DarknodePayment/DarknodePayment.sol"; 5 | import "./DarknodePayment/DarknodePaymentStore.sol"; 6 | import "./DarknodeRegistry/DarknodeRegistry.sol"; 7 | import "./DarknodeRegistry/DarknodeRegistryStore.sol"; 8 | import "./DarknodeRegistry/GetOperatorDarknodes.sol"; 9 | import "./DarknodeSlasher/DarknodeSlasher.sol"; 10 | import "./RenToken/RenToken.sol"; 11 | import "./Protocol/Protocol.sol"; 12 | 13 | /// @notice Bindings imports all of the contracts for generating bindings. 14 | /* solium-disable-next-line no-empty-blocks */ 15 | contract Bindings { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /contracts/DarknodePayment/ClaimRewards.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.5.17; 4 | 5 | import "./ValidString.sol"; 6 | 7 | contract ClaimRewards { 8 | uint256 public constant BPS_DENOMINATOR = 10000; 9 | 10 | event LogClaimRewards( 11 | address indexed operatorAddress_, 12 | string assetSymbol_, 13 | string recipientAddress_, 14 | string recipientChain_, 15 | bytes recipientPayload_, 16 | uint256 fractionInBps_, 17 | // Repeated values for indexing. 18 | string indexed assetSymbolIndexed_, 19 | string indexed recipientAddressIndexed_ 20 | ); 21 | 22 | modifier validFractionInBps(uint256 fraction_) { 23 | require( 24 | fraction_ <= BPS_DENOMINATOR, 25 | "ClaimRewards: invalid fractionInBps" 26 | ); 27 | _; 28 | } 29 | 30 | /** 31 | * claimRewardsToChain allows darknode operators to withdraw darknode 32 | * earnings, as an on-chain alternative to the JSON-RPC claim method. 33 | * 34 | * It will the operators total sum of rewards, for all of their nodes. 35 | * 36 | * @param assetSymbol_ The token symbol. 37 | * E.g. "BTC", "DOGE" or "FIL". 38 | * @param recipientAddress_ An address on the asset's native chain, for 39 | * receiving the withdrawn rewards. This should be a string as 40 | * provided by the user - no encoding or decoding required. 41 | * E.g.: "miMi2VET41YV1j6SDNTeZoPBbmH8B4nEx6" for BTC. 42 | * @param recipientChain_ A string indicating which chain the rewards should 43 | * be withdrawn to. It should be the name of the chain as expected by 44 | * RenVM (e.g. "Ethereum" or "Solana"). Support for different chains 45 | * will be rolled out after this contract is deployed, starting with 46 | * "Ethereum", then other host chains (e.g. "Polygon" or "Solana") 47 | * and then lock chains (e.g. "Bitcoin" for "BTC"), also represented 48 | * by an empty string "". 49 | * @param recipientPayload_ An associated payload that can be provided along 50 | * with the recipient chain and address. Should be empty if not 51 | * required. 52 | * @param fractionInBps_ A value between 0 and 10000 (inclusive) that 53 | * indicates the percent to withdraw from each of the operator's 54 | * darknodes. The value should be in BPS, meaning 10000 represents 55 | * 100%, 5000 represents 50%, etc. 56 | */ 57 | function claimRewardsToChain( 58 | string memory assetSymbol_, 59 | string memory recipientAddress_, 60 | string memory recipientChain_, 61 | bytes memory recipientPayload_, 62 | uint256 fractionInBps_ 63 | ) public validFractionInBps(fractionInBps_) { 64 | // Validate asset symbol. 65 | require( 66 | ValidString.isNotEmpty(assetSymbol_), 67 | "ClaimRewards: invalid empty asset" 68 | ); 69 | require( 70 | ValidString.isAlphanumeric(assetSymbol_), 71 | "ClaimRewards: invalid asset" 72 | ); 73 | 74 | // Validate recipient address. 75 | require( 76 | ValidString.isNotEmpty(recipientAddress_), 77 | "ClaimRewards: invalid empty recipient address" 78 | ); 79 | require( 80 | ValidString.isAlphanumeric(recipientAddress_), 81 | "ClaimRewards: invalid recipient address" 82 | ); 83 | 84 | // Validate recipient chain. 85 | // Note that the chain can be empty - which is planned to represent the 86 | // asset's native lock chain. 87 | require( 88 | ValidString.isAlphanumeric(recipientChain_), 89 | "ClaimRewards: invalid recipient chain" 90 | ); 91 | 92 | address operatorAddress = msg.sender; 93 | 94 | // Emit event. 95 | emit LogClaimRewards( 96 | operatorAddress, 97 | assetSymbol_, 98 | recipientAddress_, 99 | recipientChain_, 100 | recipientPayload_, 101 | fractionInBps_, 102 | // Indexed 103 | assetSymbol_, 104 | recipientAddress_ 105 | ); 106 | } 107 | 108 | /** 109 | * `claimRewardsToEthereum` calls `claimRewardsToChain` internally 110 | */ 111 | function claimRewardsToEthereum( 112 | string memory assetSymbol_, 113 | address recipientAddress_, 114 | uint256 fractionInBps_ 115 | ) public { 116 | return 117 | claimRewardsToChain( 118 | assetSymbol_, 119 | addressToString(recipientAddress_), 120 | "Ethereum", 121 | "", 122 | fractionInBps_ 123 | ); 124 | } 125 | 126 | // From https://ethereum.stackexchange.com/questions/8346/convert-address-to-string 127 | function addressToString(address address_) 128 | public 129 | pure 130 | returns (string memory) 131 | { 132 | bytes memory data = abi.encodePacked(address_); 133 | 134 | bytes memory alphabet = "0123456789abcdef"; 135 | 136 | bytes memory str = new bytes(2 + data.length * 2); 137 | str[0] = "0"; 138 | str[1] = "x"; 139 | for (uint256 i = 0; i < data.length; i++) { 140 | str[2 + i * 2] = alphabet[uint256(uint8(data[i] >> 4))]; 141 | str[3 + i * 2] = alphabet[uint256(uint8(data[i] & 0x0f))]; 142 | } 143 | return string(str); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /contracts/DarknodePayment/DarknodePayment.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol"; 6 | 7 | import "../libraries/ERC20WithFees.sol"; 8 | import "../DarknodeRegistry/DarknodeRegistry.sol"; 9 | import "./DarknodePaymentStore.sol"; 10 | 11 | /// @notice DarknodePayment is responsible for paying off darknodes for their 12 | /// computation. 13 | contract DarknodePayment is Claimable { 14 | using SafeMath for uint256; 15 | using SafeERC20 for ERC20; 16 | using ERC20WithFees for ERC20; 17 | 18 | string public VERSION; // Passed in as a constructor parameter. 19 | 20 | /// @notice The special address for Ether. 21 | address public constant ETHEREUM = 22 | 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; 23 | 24 | DarknodeRegistryLogicV1 public darknodeRegistry; // Passed in as a constructor parameter. 25 | 26 | /// @notice DarknodePaymentStore is the storage contract for darknode 27 | /// payments. 28 | DarknodePaymentStore public store; // Passed in as a constructor parameter. 29 | 30 | /// @notice The address that can call changeCycle() 31 | // This defaults to the owner but should be changed to the DarknodeRegistry. 32 | address public cycleChanger; 33 | 34 | uint256 public currentCycle; 35 | uint256 public previousCycle; 36 | 37 | /// @notice The list of tokens that will be registered next cycle. 38 | /// We only update the shareCount at the change of cycle to 39 | /// prevent the number of shares from changing. 40 | address[] public pendingTokens; 41 | 42 | /// @notice The list of tokens which are already registered and rewards can 43 | /// be claimed for. 44 | address[] public registeredTokens; 45 | 46 | /// @notice Mapping from token -> index. Index starts from 1. 0 means not in 47 | /// list. 48 | mapping(address => uint256) public registeredTokenIndex; 49 | 50 | /// @notice Mapping from token -> amount. 51 | /// The amount of rewards allocated for all darknodes to claim into 52 | /// their account. 53 | mapping(address => uint256) public unclaimedRewards; 54 | 55 | /// @notice Mapping from token -> amount. 56 | /// The amount of rewards allocated for each darknode. 57 | mapping(address => uint256) public previousCycleRewardShare; 58 | 59 | /// @notice The time that the current cycle started. 60 | uint256 public cycleStartTime; 61 | 62 | /// @notice The staged payout percentage to the darknodes per cycle. 63 | uint256 public nextCyclePayoutPercent; 64 | 65 | /// @notice The current cycle payout percentage to the darknodes. 66 | uint256 public currentCyclePayoutPercent; 67 | 68 | /// @notice Mapping of darknode -> cycle -> already_claimed. 69 | /// Used to keep track of which darknodes have already claimed their 70 | /// rewards. 71 | mapping(address => mapping(uint256 => bool)) public rewardClaimed; 72 | 73 | /// @notice Emitted when a darknode claims their share of reward. 74 | /// @param _darknode The darknode which claimed. 75 | /// @param _cycle The cycle that the darknode claimed for. 76 | event LogDarknodeClaim(address indexed _darknode, uint256 _cycle); 77 | 78 | /// @notice Emitted when someone pays the DarknodePayment contract. 79 | /// @param _payer The darknode which claimed. 80 | /// @param _amount The cycle that the darknode claimed for. 81 | /// @param _token The address of the token that was transferred. 82 | event LogPaymentReceived( 83 | address indexed _payer, 84 | address indexed _token, 85 | uint256 _amount 86 | ); 87 | 88 | /// @notice Emitted when a darknode calls withdraw. 89 | /// @param _darknodeOperator The address of the darknode's operator. 90 | /// @param _darknodeID The address of the darknode which withdrew. 91 | /// @param _value The amount of DAI withdrawn. 92 | /// @param _token The address of the token that was withdrawn. 93 | event LogDarknodeWithdrew( 94 | address indexed _darknodeOperator, 95 | address indexed _darknodeID, 96 | address indexed _token, 97 | uint256 _value 98 | ); 99 | 100 | /// @notice Emitted when the payout percent changes. 101 | /// @param _newPercent The new percent. 102 | /// @param _oldPercent The old percent. 103 | event LogPayoutPercentChanged(uint256 _newPercent, uint256 _oldPercent); 104 | 105 | /// @notice Emitted when the CycleChanger address changes. 106 | /// @param _newCycleChanger The new CycleChanger. 107 | /// @param _oldCycleChanger The old CycleChanger. 108 | event LogCycleChangerChanged( 109 | address indexed _newCycleChanger, 110 | address indexed _oldCycleChanger 111 | ); 112 | 113 | /// @notice Emitted when a new token is registered. 114 | /// @param _token The token that was registered. 115 | event LogTokenRegistered(address indexed _token); 116 | 117 | /// @notice Emitted when a token is deregistered. 118 | /// @param _token The token that was deregistered. 119 | event LogTokenDeregistered(address indexed _token); 120 | 121 | /// @notice Emitted when the DarknodeRegistry is updated. 122 | /// @param _previousDarknodeRegistry The address of the old registry. 123 | /// @param _nextDarknodeRegistry The address of the new registry. 124 | event LogDarknodeRegistryUpdated( 125 | DarknodeRegistryLogicV1 indexed _previousDarknodeRegistry, 126 | DarknodeRegistryLogicV1 indexed _nextDarknodeRegistry 127 | ); 128 | 129 | /// @notice Restrict a function registered dark nodes to call a function. 130 | modifier onlyDarknode(address _darknode) { 131 | require( 132 | darknodeRegistry.isRegistered(_darknode), 133 | "DarknodePayment: darknode is not registered" 134 | ); 135 | _; 136 | } 137 | 138 | /// @notice Restrict a function to have a valid percentage. 139 | modifier validPercent(uint256 _percent) { 140 | require(_percent <= 100, "DarknodePayment: invalid percentage"); 141 | _; 142 | } 143 | 144 | /// @notice Restrict a function to be called by cycleChanger. 145 | modifier onlyCycleChanger { 146 | require( 147 | msg.sender == cycleChanger, 148 | "DarknodePayment: not cycle changer" 149 | ); 150 | _; 151 | } 152 | 153 | /// @notice The contract constructor. Starts the current cycle using the 154 | /// time of deploy. 155 | /// 156 | /// @param _VERSION A string defining the contract version. 157 | /// @param _darknodeRegistry The address of the DarknodeRegistry contract. 158 | /// @param _darknodePaymentStore The address of the DarknodePaymentStore 159 | /// contract. 160 | constructor( 161 | string memory _VERSION, 162 | DarknodeRegistryLogicV1 _darknodeRegistry, 163 | DarknodePaymentStore _darknodePaymentStore, 164 | uint256 _cyclePayoutPercent 165 | ) public validPercent(_cyclePayoutPercent) { 166 | Claimable.initialize(msg.sender); 167 | VERSION = _VERSION; 168 | darknodeRegistry = _darknodeRegistry; 169 | store = _darknodePaymentStore; 170 | nextCyclePayoutPercent = _cyclePayoutPercent; 171 | // Default the cycleChanger to owner. 172 | cycleChanger = msg.sender; 173 | 174 | // Start the current cycle 175 | (currentCycle, cycleStartTime) = darknodeRegistry.currentEpoch(); 176 | currentCyclePayoutPercent = nextCyclePayoutPercent; 177 | } 178 | 179 | /// @notice Allows the contract owner to update the address of the 180 | /// darknode registry contract. 181 | /// @param _darknodeRegistry The address of the Darknode Registry 182 | /// contract. 183 | function updateDarknodeRegistry(DarknodeRegistryLogicV1 _darknodeRegistry) 184 | external 185 | onlyOwner 186 | { 187 | require( 188 | address(_darknodeRegistry) != address(0x0), 189 | "DarknodePayment: invalid Darknode Registry address" 190 | ); 191 | DarknodeRegistryLogicV1 previousDarknodeRegistry = darknodeRegistry; 192 | darknodeRegistry = _darknodeRegistry; 193 | emit LogDarknodeRegistryUpdated( 194 | previousDarknodeRegistry, 195 | darknodeRegistry 196 | ); 197 | } 198 | 199 | /// @notice Transfers the funds allocated to the darknode to the darknode 200 | /// owner. 201 | /// 202 | /// @param _darknode The address of the darknode. 203 | /// @param _token Which token to transfer. 204 | function withdraw(address _darknode, address _token) public { 205 | address payable darknodeOperator = 206 | darknodeRegistry.getDarknodeOperator(_darknode); 207 | require( 208 | darknodeOperator != address(0x0), 209 | "DarknodePayment: invalid darknode owner" 210 | ); 211 | 212 | uint256 amount = store.darknodeBalances(_darknode, _token); 213 | 214 | // Skip if amount is zero. 215 | if (amount > 0) { 216 | store.transfer(_darknode, _token, amount, darknodeOperator); 217 | emit LogDarknodeWithdrew( 218 | darknodeOperator, 219 | _darknode, 220 | _token, 221 | amount 222 | ); 223 | } 224 | } 225 | 226 | function withdrawMultiple( 227 | address[] calldata _darknodes, 228 | address[] calldata _tokens 229 | ) external { 230 | for (uint256 i = 0; i < _darknodes.length; i++) { 231 | for (uint256 j = 0; j < _tokens.length; j++) { 232 | withdraw(_darknodes[i], _tokens[j]); 233 | } 234 | } 235 | } 236 | 237 | /// @notice Forward all payments to the DarknodePaymentStore. 238 | function() external payable { 239 | address(store).transfer(msg.value); 240 | emit LogPaymentReceived(msg.sender, ETHEREUM, msg.value); 241 | } 242 | 243 | /// @notice The current balance of the contract available as reward for the 244 | /// current cycle. 245 | function currentCycleRewardPool(address _token) 246 | external 247 | view 248 | returns (uint256) 249 | { 250 | uint256 total = 251 | store.availableBalance(_token).sub( 252 | unclaimedRewards[_token], 253 | "DarknodePayment: unclaimed rewards exceed total rewards" 254 | ); 255 | return total.div(100).mul(currentCyclePayoutPercent); 256 | } 257 | 258 | function darknodeBalances(address _darknodeID, address _token) 259 | external 260 | view 261 | returns (uint256) 262 | { 263 | return store.darknodeBalances(_darknodeID, _token); 264 | } 265 | 266 | /// @notice Changes the current cycle. 267 | function changeCycle() external onlyCycleChanger returns (uint256) { 268 | // Snapshot balances for the past cycle. 269 | uint256 arrayLength = registeredTokens.length; 270 | for (uint256 i = 0; i < arrayLength; i++) { 271 | _snapshotBalance(registeredTokens[i]); 272 | } 273 | 274 | // Start a new cycle. 275 | previousCycle = currentCycle; 276 | (currentCycle, cycleStartTime) = darknodeRegistry.currentEpoch(); 277 | currentCyclePayoutPercent = nextCyclePayoutPercent; 278 | 279 | // Update the list of registeredTokens. 280 | _updateTokenList(); 281 | return currentCycle; 282 | } 283 | 284 | /// @notice Deposits token into the contract to be paid to the Darknodes. 285 | /// 286 | /// @param _value The amount of token deposit in the token's smallest unit. 287 | /// @param _token The token address. 288 | function deposit(uint256 _value, address _token) external payable { 289 | uint256 receivedValue; 290 | if (_token == ETHEREUM) { 291 | require( 292 | _value == msg.value, 293 | "DarknodePayment: mismatched deposit value" 294 | ); 295 | receivedValue = msg.value; 296 | address(store).transfer(msg.value); 297 | } else { 298 | require( 299 | msg.value == 0, 300 | "DarknodePayment: unexpected ether transfer" 301 | ); 302 | require( 303 | registeredTokenIndex[_token] != 0, 304 | "DarknodePayment: token not registered" 305 | ); 306 | // Forward the funds to the store. 307 | receivedValue = ERC20(_token).safeTransferFromWithFees( 308 | msg.sender, 309 | address(store), 310 | _value 311 | ); 312 | } 313 | emit LogPaymentReceived(msg.sender, _token, receivedValue); 314 | } 315 | 316 | /// @notice Forwards any tokens that have been sent to the DarknodePayment contract 317 | /// probably by mistake, to the DarknodePaymentStore. 318 | /// 319 | /// @param _token The token address. 320 | function forward(address _token) external { 321 | if (_token == ETHEREUM) { 322 | // Its unlikely that ETH will need to be forwarded, but it is 323 | // possible. For example - if ETH had already been sent to the 324 | // contract's address before it was deployed, or if funds are sent 325 | // to it as part of a contract's self-destruct. 326 | address(store).transfer(address(this).balance); 327 | } else { 328 | ERC20(_token).safeTransfer( 329 | address(store), 330 | ERC20(_token).balanceOf(address(this)) 331 | ); 332 | } 333 | } 334 | 335 | /// @notice Claims the rewards allocated to the darknode last epoch. 336 | /// @param _darknode The address of the darknode to claim. 337 | function claim(address _darknode) external onlyDarknode(_darknode) { 338 | require( 339 | darknodeRegistry.isRegisteredInPreviousEpoch(_darknode), 340 | "DarknodePayment: cannot claim for this epoch" 341 | ); 342 | // Claim share of rewards allocated for last cycle. 343 | _claimDarknodeReward(_darknode); 344 | emit LogDarknodeClaim(_darknode, previousCycle); 345 | } 346 | 347 | /// @notice Adds tokens to be payable. Registration is pending until next 348 | /// cycle. 349 | /// 350 | /// @param _token The address of the token to be registered. 351 | function registerToken(address _token) external onlyOwner { 352 | require( 353 | registeredTokenIndex[_token] == 0, 354 | "DarknodePayment: token already registered" 355 | ); 356 | require( 357 | !tokenPendingRegistration(_token), 358 | "DarknodePayment: token already pending registration" 359 | ); 360 | pendingTokens.push(_token); 361 | } 362 | 363 | function tokenPendingRegistration(address _token) 364 | public 365 | view 366 | returns (bool) 367 | { 368 | uint256 arrayLength = pendingTokens.length; 369 | for (uint256 i = 0; i < arrayLength; i++) { 370 | if (pendingTokens[i] == _token) { 371 | return true; 372 | } 373 | } 374 | return false; 375 | } 376 | 377 | /// @notice Removes a token from the list of supported tokens. 378 | /// Deregistration is pending until next cycle. 379 | /// 380 | /// @param _token The address of the token to be deregistered. 381 | function deregisterToken(address _token) external onlyOwner { 382 | require( 383 | registeredTokenIndex[_token] > 0, 384 | "DarknodePayment: token not registered" 385 | ); 386 | _deregisterToken(_token); 387 | } 388 | 389 | /// @notice Updates the CycleChanger contract address. 390 | /// 391 | /// @param _addr The new CycleChanger contract address. 392 | function updateCycleChanger(address _addr) external onlyOwner { 393 | require( 394 | _addr != address(0), 395 | "DarknodePayment: invalid contract address" 396 | ); 397 | emit LogCycleChangerChanged(_addr, cycleChanger); 398 | cycleChanger = _addr; 399 | } 400 | 401 | /// @notice Updates payout percentage. 402 | /// 403 | /// @param _percent The percentage of payout for darknodes. 404 | function updatePayoutPercentage(uint256 _percent) 405 | external 406 | onlyOwner 407 | validPercent(_percent) 408 | { 409 | uint256 oldPayoutPercent = nextCyclePayoutPercent; 410 | nextCyclePayoutPercent = _percent; 411 | emit LogPayoutPercentChanged(nextCyclePayoutPercent, oldPayoutPercent); 412 | } 413 | 414 | /// @notice Allows the contract owner to initiate an ownership transfer of 415 | /// the DarknodePaymentStore. 416 | /// 417 | /// @param _newOwner The address to transfer the ownership to. 418 | function transferStoreOwnership(DarknodePayment _newOwner) 419 | external 420 | onlyOwner 421 | { 422 | store.transferOwnership(address(_newOwner)); 423 | _newOwner.claimStoreOwnership(); 424 | } 425 | 426 | /// @notice Claims ownership of the store passed in to the constructor. 427 | /// `transferStoreOwnership` must have previously been called when 428 | /// transferring from another DarknodePaymentStore. 429 | function claimStoreOwnership() external { 430 | store.claimOwnership(); 431 | } 432 | 433 | /// @notice Claims the darknode reward for all registered tokens into 434 | /// darknodeBalances in the DarknodePaymentStore. 435 | /// Rewards can only be claimed once per cycle. 436 | /// 437 | /// @param _darknode The address to the darknode to claim rewards for. 438 | function _claimDarknodeReward(address _darknode) private { 439 | require( 440 | !rewardClaimed[_darknode][previousCycle], 441 | "DarknodePayment: reward already claimed" 442 | ); 443 | rewardClaimed[_darknode][previousCycle] = true; 444 | uint256 arrayLength = registeredTokens.length; 445 | for (uint256 i = 0; i < arrayLength; i++) { 446 | address token = registeredTokens[i]; 447 | 448 | // Only increment balance if shares were allocated last cycle 449 | if (previousCycleRewardShare[token] > 0) { 450 | unclaimedRewards[token] = unclaimedRewards[token].sub( 451 | previousCycleRewardShare[token], 452 | "DarknodePayment: share exceeds unclaimed rewards" 453 | ); 454 | store.incrementDarknodeBalance( 455 | _darknode, 456 | token, 457 | previousCycleRewardShare[token] 458 | ); 459 | } 460 | } 461 | } 462 | 463 | /// @notice Snapshots the current balance of the tokens, for all registered 464 | /// tokens. 465 | /// 466 | /// @param _token The address the token to snapshot. 467 | function _snapshotBalance(address _token) private { 468 | uint256 shareCount = darknodeRegistry.numDarknodesPreviousEpoch(); 469 | if (shareCount == 0) { 470 | unclaimedRewards[_token] = 0; 471 | previousCycleRewardShare[_token] = 0; 472 | } else { 473 | // Lock up the current balance for darknode reward allocation 474 | uint256 total = store.availableBalance(_token); 475 | unclaimedRewards[_token] = total.div(100).mul( 476 | currentCyclePayoutPercent 477 | ); 478 | previousCycleRewardShare[_token] = unclaimedRewards[_token].div( 479 | shareCount 480 | ); 481 | } 482 | } 483 | 484 | /// @notice Deregisters a token, removing it from the list of 485 | /// registeredTokens. 486 | /// 487 | /// @param _token The address of the token to deregister. 488 | function _deregisterToken(address _token) private { 489 | address lastToken = 490 | registeredTokens[ 491 | registeredTokens.length.sub( 492 | 1, 493 | "DarknodePayment: no tokens registered" 494 | ) 495 | ]; 496 | uint256 deletedTokenIndex = registeredTokenIndex[_token].sub(1); 497 | // Move the last token to _token's position and update it's index 498 | registeredTokens[deletedTokenIndex] = lastToken; 499 | registeredTokenIndex[lastToken] = registeredTokenIndex[_token]; 500 | // Decreasing the length will clean up the storage for us 501 | // So we don't need to manually delete the element 502 | registeredTokens.pop(); 503 | registeredTokenIndex[_token] = 0; 504 | 505 | emit LogTokenDeregistered(_token); 506 | } 507 | 508 | /// @notice Updates the list of registeredTokens adding tokens that are to be registered. 509 | /// The list of tokens that are pending registration are emptied afterwards. 510 | function _updateTokenList() private { 511 | // Register tokens 512 | uint256 arrayLength = pendingTokens.length; 513 | for (uint256 i = 0; i < arrayLength; i++) { 514 | address token = pendingTokens[i]; 515 | registeredTokens.push(token); 516 | registeredTokenIndex[token] = registeredTokens.length; 517 | emit LogTokenRegistered(token); 518 | } 519 | pendingTokens.length = 0; 520 | } 521 | } 522 | -------------------------------------------------------------------------------- /contracts/DarknodePayment/DarknodePaymentMigrator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol"; 6 | 7 | import "../Governance/Claimable.sol"; 8 | import "./DarknodePaymentStore.sol"; 9 | import "./DarknodePayment.sol"; 10 | 11 | /// @notice The DarknodePaymentMigrator migrates unclaimed funds from the 12 | /// DarknodePayment contract. In a single transaction, it claims the store 13 | /// ownership from the DNP contract, migrates unclaimed fees and then returns 14 | /// the store ownership back to the DNP. 15 | contract DarknodePaymentMigrator is Claimable { 16 | DarknodePayment public dnp; 17 | address[] public tokens; 18 | 19 | constructor(DarknodePayment _dnp, address[] memory _tokens) public { 20 | Claimable.initialize(msg.sender); 21 | dnp = _dnp; 22 | tokens = _tokens; 23 | } 24 | 25 | function setTokens(address[] memory _tokens) public onlyOwner { 26 | tokens = _tokens; 27 | } 28 | 29 | function claimStoreOwnership() external { 30 | require(msg.sender == address(dnp), "Not darknode payment contract"); 31 | DarknodePaymentStore store = dnp.store(); 32 | 33 | store.claimOwnership(); 34 | 35 | for (uint256 i = 0; i < tokens.length; i++) { 36 | address token = tokens[i]; 37 | 38 | uint256 unclaimed = store.availableBalance(token); 39 | 40 | if (unclaimed > 0) { 41 | store.incrementDarknodeBalance(address(0x0), token, unclaimed); 42 | 43 | store.transfer( 44 | address(0x0), 45 | token, 46 | unclaimed, 47 | _payableAddress(owner()) 48 | ); 49 | } 50 | } 51 | 52 | store.transferOwnership(address(dnp)); 53 | dnp.claimStoreOwnership(); 54 | 55 | require( 56 | store.owner() == address(dnp), 57 | "Store ownership not transferred back." 58 | ); 59 | } 60 | 61 | // Cast an address to a payable address 62 | function _payableAddress(address a) 63 | internal 64 | pure 65 | returns (address payable) 66 | { 67 | return address(uint160(address(a))); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /contracts/DarknodePayment/DarknodePaymentStore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol"; 6 | 7 | import "../Governance/Claimable.sol"; 8 | import "../libraries/ERC20WithFees.sol"; 9 | 10 | /// @notice DarknodePaymentStore is responsible for tracking balances which have 11 | /// been allocated to the darknodes. It is also responsible for holding 12 | /// the tokens to be paid out to darknodes. 13 | contract DarknodePaymentStore is Claimable { 14 | using SafeMath for uint256; 15 | using SafeERC20 for ERC20; 16 | using ERC20WithFees for ERC20; 17 | 18 | string public VERSION; // Passed in as a constructor parameter. 19 | 20 | /// @notice The special address for Ether. 21 | address public constant ETHEREUM = 22 | 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; 23 | 24 | /// @notice Mapping of darknode -> token -> balance. 25 | mapping(address => mapping(address => uint256)) public darknodeBalances; 26 | 27 | /// @notice Mapping of token -> lockedAmount. 28 | mapping(address => uint256) public lockedBalances; 29 | 30 | /// @notice The contract constructor. 31 | /// 32 | /// @param _VERSION A string defining the contract version. 33 | constructor(string memory _VERSION) public { 34 | Claimable.initialize(msg.sender); 35 | VERSION = _VERSION; 36 | } 37 | 38 | /// @notice Allow direct ETH payments to be made to the DarknodePaymentStore. 39 | function() external payable {} 40 | 41 | /// @notice Get the total balance of the contract for a particular token. 42 | /// 43 | /// @param _token The token to check balance of. 44 | /// @return The total balance of the contract. 45 | function totalBalance(address _token) public view returns (uint256) { 46 | if (_token == ETHEREUM) { 47 | return address(this).balance; 48 | } else { 49 | return ERC20(_token).balanceOf(address(this)); 50 | } 51 | } 52 | 53 | /// @notice Get the available balance of the contract for a particular token 54 | /// This is the free amount which has not yet been allocated to 55 | /// darknodes. 56 | /// 57 | /// @param _token The token to check balance of. 58 | /// @return The available balance of the contract. 59 | function availableBalance(address _token) public view returns (uint256) { 60 | return 61 | totalBalance(_token).sub( 62 | lockedBalances[_token], 63 | "DarknodePaymentStore: locked balance exceed total balance" 64 | ); 65 | } 66 | 67 | /// @notice Increments the amount of funds allocated to a particular 68 | /// darknode. 69 | /// 70 | /// @param _darknode The address of the darknode to increase balance of. 71 | /// @param _token The token which the balance should be incremented. 72 | /// @param _amount The amount that the balance should be incremented by. 73 | function incrementDarknodeBalance( 74 | address _darknode, 75 | address _token, 76 | uint256 _amount 77 | ) external onlyOwner { 78 | require(_amount > 0, "DarknodePaymentStore: invalid amount"); 79 | require( 80 | availableBalance(_token) >= _amount, 81 | "DarknodePaymentStore: insufficient contract balance" 82 | ); 83 | 84 | darknodeBalances[_darknode][_token] = darknodeBalances[_darknode][ 85 | _token 86 | ] 87 | .add(_amount); 88 | lockedBalances[_token] = lockedBalances[_token].add(_amount); 89 | } 90 | 91 | /// @notice Transfers an amount out of balance to a specified address. 92 | /// 93 | /// @param _darknode The address of the darknode. 94 | /// @param _token Which token to transfer. 95 | /// @param _amount The amount to transfer. 96 | /// @param _recipient The address to withdraw it to. 97 | function transfer( 98 | address _darknode, 99 | address _token, 100 | uint256 _amount, 101 | address payable _recipient 102 | ) external onlyOwner { 103 | require( 104 | darknodeBalances[_darknode][_token] >= _amount, 105 | "DarknodePaymentStore: insufficient darknode balance" 106 | ); 107 | darknodeBalances[_darknode][_token] = darknodeBalances[_darknode][ 108 | _token 109 | ] 110 | .sub( 111 | _amount, 112 | "DarknodePaymentStore: insufficient darknode balance for transfer" 113 | ); 114 | lockedBalances[_token] = lockedBalances[_token].sub( 115 | _amount, 116 | "DarknodePaymentStore: insufficient token balance for transfer" 117 | ); 118 | 119 | if (_token == ETHEREUM) { 120 | _recipient.transfer(_amount); 121 | } else { 122 | ERC20(_token).safeTransfer(_recipient, _amount); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /contracts/DarknodePayment/DarknodeRegistryForwarder.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../DarknodeRegistry/DarknodeRegistry.sol"; 4 | 5 | /// @notice DarknodeRegistryForwarder implements the DNR's methods that are used 6 | /// by the DNP, and it forwards them all to the DNR except 7 | /// `isRegisteredInPreviousEpoch`, for which it returns false in order to make 8 | /// calls to `claim` revert. 9 | contract DarknodeRegistryForwarder { 10 | DarknodeRegistryLogicV1 dnr; 11 | 12 | constructor(DarknodeRegistryLogicV1 _dnr) public { 13 | dnr = _dnr; 14 | } 15 | 16 | /// @notice Returns if a darknode is in the registered state. 17 | function isRegistered(address _darknodeID) public view returns (bool) { 18 | return dnr.isRegistered(_darknodeID); 19 | } 20 | 21 | function currentEpoch() public view returns (uint256, uint256) { 22 | return dnr.currentEpoch(); 23 | } 24 | 25 | function getDarknodeOperator(address _darknodeID) 26 | public 27 | view 28 | returns (address payable) 29 | { 30 | return dnr.getDarknodeOperator(_darknodeID); 31 | } 32 | 33 | function isRegisteredInPreviousEpoch(address _darknodeID) 34 | public 35 | view 36 | returns (bool) 37 | { 38 | // return dnr.isRegisteredInPreviousEpoch(_darknodeID); 39 | return false; 40 | } 41 | 42 | function numDarknodesPreviousEpoch() public view returns (uint256) { 43 | return dnr.numDarknodesPreviousEpoch(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/DarknodePayment/ValidString.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.5.17; 4 | 5 | library ValidString { 6 | function isAlphanumeric(string memory _string) 7 | internal 8 | pure 9 | returns (bool) 10 | { 11 | for (uint256 i = 0; i < bytes(_string).length; i++) { 12 | uint8 char = uint8(bytes(_string)[i]); 13 | if ( 14 | !((char >= 65 && char <= 90) || 15 | (char >= 97 && char <= 122) || 16 | (char >= 48 && char <= 57)) 17 | ) { 18 | return false; 19 | } 20 | } 21 | return true; 22 | } 23 | 24 | function isNotEmpty(string memory _string) internal pure returns (bool) { 25 | return bytes(_string).length > 0; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/DarknodeRegistry/DarknodeRegistryStore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 4 | 5 | import "../Governance/Claimable.sol"; 6 | import "../libraries/LinkedList.sol"; 7 | import "../RenToken/RenToken.sol"; 8 | import "../libraries/CanReclaimTokens.sol"; 9 | 10 | /// @notice This contract stores data and funds for the DarknodeRegistry 11 | /// contract. The data / fund logic and storage have been separated to improve 12 | /// upgradability. 13 | contract DarknodeRegistryStore is Claimable, CanReclaimTokens { 14 | using SafeMath for uint256; 15 | 16 | string public VERSION; // Passed in as a constructor parameter. 17 | 18 | /// @notice Darknodes are stored in the darknode struct. The owner is the 19 | /// address that registered the darknode, the bond is the amount of REN that 20 | /// was transferred during registration, and the public key is the 21 | /// encryption key that should be used when sending sensitive information to 22 | /// the darknode. 23 | struct Darknode { 24 | // The owner of a Darknode is the address that called the register 25 | // function. The owner is the only address that is allowed to 26 | // deregister the Darknode, unless the Darknode is slashed for 27 | // malicious behavior. 28 | address payable owner; 29 | // The bond is the amount of REN submitted as a bond by the Darknode. 30 | // This amount is reduced when the Darknode is slashed for malicious 31 | // behavior. 32 | uint256 bond; 33 | // The block number at which the Darknode is considered registered. 34 | uint256 registeredAt; 35 | // The block number at which the Darknode is considered deregistered. 36 | uint256 deregisteredAt; 37 | // The public key used by this Darknode for encrypting sensitive data 38 | // off chain. It is assumed that the Darknode has access to the 39 | // respective private key, and that there is an agreement on the format 40 | // of the public key. 41 | bytes publicKey; 42 | } 43 | 44 | /// Registry data. 45 | mapping(address => Darknode) private darknodeRegistry; 46 | LinkedList.List private darknodes; 47 | 48 | // RenToken. 49 | RenToken public ren; 50 | 51 | /// @notice The contract constructor. 52 | /// 53 | /// @param _VERSION A string defining the contract version. 54 | /// @param _ren The address of the RenToken contract. 55 | constructor(string memory _VERSION, RenToken _ren) public { 56 | Claimable.initialize(msg.sender); 57 | CanReclaimTokens.initialize(msg.sender); 58 | VERSION = _VERSION; 59 | ren = _ren; 60 | blacklistRecoverableToken(address(ren)); 61 | } 62 | 63 | /// @notice Instantiates a darknode and appends it to the darknodes 64 | /// linked-list. 65 | /// 66 | /// @param _darknodeID The darknode's ID. 67 | /// @param _darknodeOperator The darknode's owner's address. 68 | /// @param _bond The darknode's bond value. 69 | /// @param _publicKey The darknode's public key. 70 | /// @param _registeredAt The time stamp when the darknode is registered. 71 | /// @param _deregisteredAt The time stamp when the darknode is deregistered. 72 | function appendDarknode( 73 | address _darknodeID, 74 | address payable _darknodeOperator, 75 | uint256 _bond, 76 | bytes calldata _publicKey, 77 | uint256 _registeredAt, 78 | uint256 _deregisteredAt 79 | ) external onlyOwner { 80 | Darknode memory darknode = 81 | Darknode({ 82 | owner: _darknodeOperator, 83 | bond: _bond, 84 | publicKey: _publicKey, 85 | registeredAt: _registeredAt, 86 | deregisteredAt: _deregisteredAt 87 | }); 88 | darknodeRegistry[_darknodeID] = darknode; 89 | LinkedList.append(darknodes, _darknodeID); 90 | } 91 | 92 | /// @notice Returns the address of the first darknode in the store. 93 | function begin() external view onlyOwner returns (address) { 94 | return LinkedList.begin(darknodes); 95 | } 96 | 97 | /// @notice Returns the address of the next darknode in the store after the 98 | /// given address. 99 | function next(address darknodeID) 100 | external 101 | view 102 | onlyOwner 103 | returns (address) 104 | { 105 | return LinkedList.next(darknodes, darknodeID); 106 | } 107 | 108 | /// @notice Removes a darknode from the store and transfers its bond to the 109 | /// owner of this contract. 110 | function removeDarknode(address darknodeID) external onlyOwner { 111 | uint256 bond = darknodeRegistry[darknodeID].bond; 112 | delete darknodeRegistry[darknodeID]; 113 | LinkedList.remove(darknodes, darknodeID); 114 | require( 115 | ren.transfer(owner(), bond), 116 | "DarknodeRegistryStore: bond transfer failed" 117 | ); 118 | } 119 | 120 | /// @notice Updates the bond of a darknode. The new bond must be smaller 121 | /// than the previous bond of the darknode. 122 | function updateDarknodeBond(address darknodeID, uint256 decreasedBond) 123 | external 124 | onlyOwner 125 | { 126 | uint256 previousBond = darknodeRegistry[darknodeID].bond; 127 | require( 128 | decreasedBond < previousBond, 129 | "DarknodeRegistryStore: bond not decreased" 130 | ); 131 | darknodeRegistry[darknodeID].bond = decreasedBond; 132 | require( 133 | ren.transfer(owner(), previousBond.sub(decreasedBond)), 134 | "DarknodeRegistryStore: bond transfer failed" 135 | ); 136 | } 137 | 138 | /// @notice Updates the deregistration timestamp of a darknode. 139 | function updateDarknodeDeregisteredAt( 140 | address darknodeID, 141 | uint256 deregisteredAt 142 | ) external onlyOwner { 143 | darknodeRegistry[darknodeID].deregisteredAt = deregisteredAt; 144 | } 145 | 146 | /// @notice Returns the owner of a given darknode. 147 | function darknodeOperator(address darknodeID) 148 | external 149 | view 150 | onlyOwner 151 | returns (address payable) 152 | { 153 | return darknodeRegistry[darknodeID].owner; 154 | } 155 | 156 | /// @notice Returns the bond of a given darknode. 157 | function darknodeBond(address darknodeID) 158 | external 159 | view 160 | onlyOwner 161 | returns (uint256) 162 | { 163 | return darknodeRegistry[darknodeID].bond; 164 | } 165 | 166 | /// @notice Returns the registration time of a given darknode. 167 | function darknodeRegisteredAt(address darknodeID) 168 | external 169 | view 170 | onlyOwner 171 | returns (uint256) 172 | { 173 | return darknodeRegistry[darknodeID].registeredAt; 174 | } 175 | 176 | /// @notice Returns the deregistration time of a given darknode. 177 | function darknodeDeregisteredAt(address darknodeID) 178 | external 179 | view 180 | onlyOwner 181 | returns (uint256) 182 | { 183 | return darknodeRegistry[darknodeID].deregisteredAt; 184 | } 185 | 186 | /// @notice Returns the encryption public key of a given darknode. 187 | function darknodePublicKey(address darknodeID) 188 | external 189 | view 190 | onlyOwner 191 | returns (bytes memory) 192 | { 193 | return darknodeRegistry[darknodeID].publicKey; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /contracts/DarknodeRegistry/GetOperatorDarknodes.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "./DarknodeRegistry.sol"; 4 | 5 | contract GetOperatorDarknodes { 6 | DarknodeRegistryLogicV1 public darknodeRegistry; 7 | 8 | constructor(DarknodeRegistryLogicV1 _darknodeRegistry) public { 9 | darknodeRegistry = _darknodeRegistry; 10 | } 11 | 12 | function getOperatorDarknodes(address _operator) 13 | public 14 | view 15 | returns (address[] memory) 16 | { 17 | uint256 numDarknodes = darknodeRegistry.numDarknodes(); 18 | address[] memory nodesPadded = new address[](numDarknodes); 19 | 20 | address[] memory allNodes = darknodeRegistry.getDarknodes( 21 | address(0), 22 | 0 23 | ); 24 | 25 | uint256 j = 0; 26 | for (uint256 i = 0; i < allNodes.length; i++) { 27 | if ( 28 | darknodeRegistry.getDarknodeOperator(allNodes[i]) == _operator 29 | ) { 30 | nodesPadded[j] = (allNodes[i]); 31 | j++; 32 | } 33 | } 34 | 35 | address[] memory nodes = new address[](j); 36 | for (uint256 i = 0; i < j; i++) { 37 | nodes[i] = nodesPadded[i]; 38 | } 39 | 40 | return nodes; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/DarknodeSlasher/DarknodeSlasher.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../Governance/Claimable.sol"; 4 | import "../libraries/Validate.sol"; 5 | import "../DarknodeRegistry/DarknodeRegistry.sol"; 6 | 7 | /// @notice DarknodeSlasher will become a voting system for darknodes to 8 | /// deregister other misbehaving darknodes. 9 | /// Right now, it is a placeholder. 10 | contract DarknodeSlasher is Claimable { 11 | DarknodeRegistryLogicV1 public darknodeRegistry; 12 | 13 | uint256 public blacklistSlashPercent; 14 | uint256 public maliciousSlashPercent; 15 | uint256 public secretRevealSlashPercent; 16 | 17 | // Malicious Darknodes can be slashed for each height and round 18 | // mapping of height -> round -> guilty address -> slashed 19 | mapping(uint256 => mapping(uint256 => mapping(address => bool))) 20 | public slashed; 21 | 22 | // mapping of darknodes which have revealed their secret 23 | mapping(address => bool) public secretRevealed; 24 | 25 | // mapping of address to whether the darknode has been blacklisted 26 | mapping(address => bool) public blacklisted; 27 | 28 | /// @notice Emitted when the DarknodeRegistry is updated. 29 | /// @param _previousDarknodeRegistry The address of the old registry. 30 | /// @param _nextDarknodeRegistry The address of the new registry. 31 | event LogDarknodeRegistryUpdated( 32 | DarknodeRegistryLogicV1 indexed _previousDarknodeRegistry, 33 | DarknodeRegistryLogicV1 indexed _nextDarknodeRegistry 34 | ); 35 | 36 | /// @notice Restrict a function to have a valid percentage. 37 | modifier validPercent(uint256 _percent) { 38 | require(_percent <= 100, "DarknodeSlasher: invalid percentage"); 39 | _; 40 | } 41 | 42 | constructor(DarknodeRegistryLogicV1 _darknodeRegistry) public { 43 | Claimable.initialize(msg.sender); 44 | darknodeRegistry = _darknodeRegistry; 45 | } 46 | 47 | /// @notice Allows the contract owner to update the address of the 48 | /// darknode registry contract. 49 | /// @param _darknodeRegistry The address of the Darknode Registry 50 | /// contract. 51 | function updateDarknodeRegistry(DarknodeRegistryLogicV1 _darknodeRegistry) 52 | external 53 | onlyOwner 54 | { 55 | require( 56 | address(_darknodeRegistry) != address(0x0), 57 | "DarknodeSlasher: invalid Darknode Registry address" 58 | ); 59 | DarknodeRegistryLogicV1 previousDarknodeRegistry = darknodeRegistry; 60 | darknodeRegistry = _darknodeRegistry; 61 | emit LogDarknodeRegistryUpdated( 62 | previousDarknodeRegistry, 63 | darknodeRegistry 64 | ); 65 | } 66 | 67 | function setBlacklistSlashPercent(uint256 _percentage) 68 | public 69 | validPercent(_percentage) 70 | onlyOwner 71 | { 72 | blacklistSlashPercent = _percentage; 73 | } 74 | 75 | function setMaliciousSlashPercent(uint256 _percentage) 76 | public 77 | validPercent(_percentage) 78 | onlyOwner 79 | { 80 | maliciousSlashPercent = _percentage; 81 | } 82 | 83 | function setSecretRevealSlashPercent(uint256 _percentage) 84 | public 85 | validPercent(_percentage) 86 | onlyOwner 87 | { 88 | secretRevealSlashPercent = _percentage; 89 | } 90 | 91 | function slash( 92 | address _guilty, 93 | address _challenger, 94 | uint256 _percentage 95 | ) external onlyOwner { 96 | darknodeRegistry.slash(_guilty, _challenger, _percentage); 97 | } 98 | 99 | function blacklist(address _guilty) external onlyOwner { 100 | require(!blacklisted[_guilty], "DarknodeSlasher: already blacklisted"); 101 | blacklisted[_guilty] = true; 102 | darknodeRegistry.slash(_guilty, owner(), blacklistSlashPercent); 103 | } 104 | 105 | function slashDuplicatePropose( 106 | uint256 _height, 107 | uint256 _round, 108 | bytes calldata _blockhash1, 109 | uint256 _validRound1, 110 | bytes calldata _signature1, 111 | bytes calldata _blockhash2, 112 | uint256 _validRound2, 113 | bytes calldata _signature2 114 | ) external { 115 | address signer = 116 | Validate.duplicatePropose( 117 | _height, 118 | _round, 119 | _blockhash1, 120 | _validRound1, 121 | _signature1, 122 | _blockhash2, 123 | _validRound2, 124 | _signature2 125 | ); 126 | require( 127 | !slashed[_height][_round][signer], 128 | "DarknodeSlasher: already slashed" 129 | ); 130 | slashed[_height][_round][signer] = true; 131 | darknodeRegistry.slash(signer, msg.sender, maliciousSlashPercent); 132 | } 133 | 134 | function slashDuplicatePrevote( 135 | uint256 _height, 136 | uint256 _round, 137 | bytes calldata _blockhash1, 138 | bytes calldata _signature1, 139 | bytes calldata _blockhash2, 140 | bytes calldata _signature2 141 | ) external { 142 | address signer = 143 | Validate.duplicatePrevote( 144 | _height, 145 | _round, 146 | _blockhash1, 147 | _signature1, 148 | _blockhash2, 149 | _signature2 150 | ); 151 | require( 152 | !slashed[_height][_round][signer], 153 | "DarknodeSlasher: already slashed" 154 | ); 155 | slashed[_height][_round][signer] = true; 156 | darknodeRegistry.slash(signer, msg.sender, maliciousSlashPercent); 157 | } 158 | 159 | function slashDuplicatePrecommit( 160 | uint256 _height, 161 | uint256 _round, 162 | bytes calldata _blockhash1, 163 | bytes calldata _signature1, 164 | bytes calldata _blockhash2, 165 | bytes calldata _signature2 166 | ) external { 167 | address signer = 168 | Validate.duplicatePrecommit( 169 | _height, 170 | _round, 171 | _blockhash1, 172 | _signature1, 173 | _blockhash2, 174 | _signature2 175 | ); 176 | require( 177 | !slashed[_height][_round][signer], 178 | "DarknodeSlasher: already slashed" 179 | ); 180 | slashed[_height][_round][signer] = true; 181 | darknodeRegistry.slash(signer, msg.sender, maliciousSlashPercent); 182 | } 183 | 184 | function slashSecretReveal( 185 | uint256 _a, 186 | uint256 _b, 187 | uint256 _c, 188 | uint256 _d, 189 | uint256 _e, 190 | uint256 _f, 191 | bytes calldata _signature 192 | ) external { 193 | address signer = 194 | Validate.recoverSecret(_a, _b, _c, _d, _e, _f, _signature); 195 | require(!secretRevealed[signer], "DarknodeSlasher: already slashed"); 196 | secretRevealed[signer] = true; 197 | darknodeRegistry.slash(signer, msg.sender, secretRevealSlashPercent); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /contracts/Governance/Claimable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; 4 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 5 | 6 | /** 7 | * @title Claimable 8 | * @dev Extension for the Ownable contract, where the ownership needs to be claimed. 9 | * This allows the new owner to accept the transfer. 10 | */ 11 | contract Claimable is Initializable, Ownable { 12 | address public pendingOwner; 13 | 14 | function initialize(address _nextOwner) public initializer { 15 | Ownable.initialize(_nextOwner); 16 | } 17 | 18 | modifier onlyPendingOwner() { 19 | require( 20 | _msgSender() == pendingOwner, 21 | "Claimable: caller is not the pending owner" 22 | ); 23 | _; 24 | } 25 | 26 | function transferOwnership(address newOwner) public onlyOwner { 27 | require( 28 | newOwner != owner() && newOwner != pendingOwner, 29 | "Claimable: invalid new owner" 30 | ); 31 | pendingOwner = newOwner; 32 | } 33 | 34 | function claimOwnership() public onlyPendingOwner { 35 | _transferOwnership(pendingOwner); 36 | delete pendingOwner; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/Governance/RenProxyAdmin.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/upgrades/contracts/upgradeability/ProxyAdmin.sol"; 4 | 5 | /** 6 | * @title RenProxyAdmin 7 | * @dev Proxies restrict the proxy's owner from calling functions from the 8 | * delegate contract logic. The ProxyAdmin contract allows single account to be 9 | * the governance address of both the proxy and the delegate contract logic. 10 | */ 11 | /* solium-disable-next-line no-empty-blocks */ 12 | contract RenProxyAdmin is ProxyAdmin { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /contracts/Protocol/Protocol.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 4 | import "../Governance/Claimable.sol"; 5 | 6 | /** The Protocol contract is used to look-up other Ren contracts. */ 7 | contract Protocol is Initializable, Claimable { 8 | event LogContractUpdated( 9 | string contractName, 10 | address indexed contractAddress, 11 | string indexed contractNameIndexed 12 | ); 13 | 14 | mapping(string => address) internal contractMap; 15 | 16 | function __Protocol_init(address adminAddress_) public initializer { 17 | Claimable.initialize(adminAddress_); 18 | } 19 | 20 | function addContract(string memory contractName, address contractAddress) 21 | public 22 | onlyOwner 23 | { 24 | require( 25 | contractMap[contractName] == address(0x0), 26 | "Protocol: contract entry already exists" 27 | ); 28 | contractMap[contractName] = contractAddress; 29 | 30 | emit LogContractUpdated(contractName, contractAddress, contractName); 31 | } 32 | 33 | function updateContract(string memory contractName, address contractAddress) 34 | public 35 | onlyOwner 36 | { 37 | contractMap[contractName] = contractAddress; 38 | 39 | emit LogContractUpdated(contractName, contractAddress, contractName); 40 | } 41 | 42 | function getContract(string memory contractName) 43 | public 44 | view 45 | returns (address) 46 | { 47 | return contractMap[contractName]; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /contracts/RenToken/RenToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol"; 5 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Pausable.sol"; 6 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Burnable.sol"; 7 | 8 | contract RenToken is Ownable, ERC20Detailed, ERC20Pausable, ERC20Burnable { 9 | string private constant _name = "REN"; 10 | string private constant _symbol = "REN"; 11 | uint8 private constant _decimals = 18; 12 | 13 | uint256 public constant INITIAL_SUPPLY = 14 | 1000000000 * 10**uint256(_decimals); 15 | 16 | /// @notice The RenToken Constructor. 17 | constructor() public { 18 | ERC20Pausable.initialize(msg.sender); 19 | ERC20Detailed.initialize(_name, _symbol, _decimals); 20 | Ownable.initialize(msg.sender); 21 | _mint(msg.sender, INITIAL_SUPPLY); 22 | } 23 | 24 | function transferTokens(address beneficiary, uint256 amount) 25 | public 26 | onlyOwner 27 | returns (bool) 28 | { 29 | // Note: The deployed version has no revert reason 30 | /* solium-disable-next-line error-reason */ 31 | require(amount > 0); 32 | 33 | _transfer(msg.sender, beneficiary, amount); 34 | emit Transfer(msg.sender, beneficiary, amount); 35 | 36 | return true; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/libraries/CanReclaimTokens.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol"; 6 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 7 | 8 | import "../Governance/Claimable.sol"; 9 | 10 | contract CanReclaimTokens is Claimable { 11 | using SafeERC20 for ERC20; 12 | 13 | mapping(address => bool) private recoverableTokensBlacklist; 14 | 15 | function initialize(address _nextOwner) public initializer { 16 | Claimable.initialize(_nextOwner); 17 | } 18 | 19 | function blacklistRecoverableToken(address _token) public onlyOwner { 20 | recoverableTokensBlacklist[_token] = true; 21 | } 22 | 23 | /// @notice Allow the owner of the contract to recover funds accidentally 24 | /// sent to the contract. To withdraw ETH, the token should be set to `0x0`. 25 | function recoverTokens(address _token) external onlyOwner { 26 | require( 27 | !recoverableTokensBlacklist[_token], 28 | "CanReclaimTokens: token is not recoverable" 29 | ); 30 | 31 | if (_token == address(0x0)) { 32 | msg.sender.transfer(address(this).balance); 33 | } else { 34 | ERC20(_token).safeTransfer( 35 | msg.sender, 36 | ERC20(_token).balanceOf(address(this)) 37 | ); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/libraries/Compare.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | library Compare { 4 | function bytesEqual(bytes memory a, bytes memory b) 5 | internal 6 | pure 7 | returns (bool) 8 | { 9 | if (a.length != b.length) { 10 | return false; 11 | } 12 | for (uint256 i = 0; i < a.length; i++) { 13 | if (a[i] != b[i]) { 14 | return false; 15 | } 16 | } 17 | return true; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/libraries/ERC20WithFees.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol"; 5 | import "@openzeppelin/contracts-ethereum-package/contracts/math/Math.sol"; 6 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 7 | 8 | library ERC20WithFees { 9 | using SafeMath for uint256; 10 | using SafeERC20 for IERC20; 11 | 12 | /// @notice Calls transferFrom on the token, returning the value transferred 13 | /// after fees. 14 | function safeTransferFromWithFees( 15 | IERC20 token, 16 | address from, 17 | address to, 18 | uint256 value 19 | ) internal returns (uint256) { 20 | uint256 balancesBefore = token.balanceOf(to); 21 | token.safeTransferFrom(from, to, value); 22 | uint256 balancesAfter = token.balanceOf(to); 23 | return Math.min(value, balancesAfter.sub(balancesBefore)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/libraries/LinkedList.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | /** 4 | * @notice LinkedList is a library for a circular double linked list. 5 | */ 6 | library LinkedList { 7 | /* 8 | * @notice A permanent NULL node (0x0) in the circular double linked list. 9 | * NULL.next is the head, and NULL.previous is the tail. 10 | */ 11 | address public constant NULL = address(0); 12 | 13 | /** 14 | * @notice A node points to the node before it, and the node after it. If 15 | * node.previous = NULL, then the node is the head of the list. If 16 | * node.next = NULL, then the node is the tail of the list. 17 | */ 18 | struct Node { 19 | bool inList; 20 | address previous; 21 | address next; 22 | } 23 | 24 | /** 25 | * @notice LinkedList uses a mapping from address to nodes. Each address 26 | * uniquely identifies a node, and in this way they are used like pointers. 27 | */ 28 | struct List { 29 | mapping(address => Node) list; 30 | uint256 length; 31 | } 32 | 33 | /** 34 | * @notice Insert a new node before an existing node. 35 | * 36 | * @param self The list being used. 37 | * @param target The existing node in the list. 38 | * @param newNode The next node to insert before the target. 39 | */ 40 | function insertBefore( 41 | List storage self, 42 | address target, 43 | address newNode 44 | ) internal { 45 | require(newNode != address(0), "LinkedList: invalid address"); 46 | require(!isInList(self, newNode), "LinkedList: already in list"); 47 | require( 48 | isInList(self, target) || target == NULL, 49 | "LinkedList: not in list" 50 | ); 51 | 52 | // It is expected that this value is sometimes NULL. 53 | address prev = self.list[target].previous; 54 | 55 | self.list[newNode].next = target; 56 | self.list[newNode].previous = prev; 57 | self.list[target].previous = newNode; 58 | self.list[prev].next = newNode; 59 | 60 | self.list[newNode].inList = true; 61 | 62 | self.length += 1; 63 | } 64 | 65 | /** 66 | * @notice Insert a new node after an existing node. 67 | * 68 | * @param self The list being used. 69 | * @param target The existing node in the list. 70 | * @param newNode The next node to insert after the target. 71 | */ 72 | function insertAfter( 73 | List storage self, 74 | address target, 75 | address newNode 76 | ) internal { 77 | require(newNode != address(0), "LinkedList: invalid address"); 78 | require(!isInList(self, newNode), "LinkedList: already in list"); 79 | require( 80 | isInList(self, target) || target == NULL, 81 | "LinkedList: not in list" 82 | ); 83 | 84 | // It is expected that this value is sometimes NULL. 85 | address n = self.list[target].next; 86 | 87 | self.list[newNode].previous = target; 88 | self.list[newNode].next = n; 89 | self.list[target].next = newNode; 90 | self.list[n].previous = newNode; 91 | 92 | self.list[newNode].inList = true; 93 | 94 | self.length += 1; 95 | } 96 | 97 | /** 98 | * @notice Remove a node from the list, and fix the previous and next 99 | * pointers that are pointing to the removed node. Removing anode that is not 100 | * in the list will do nothing. 101 | * 102 | * @param self The list being using. 103 | * @param node The node in the list to be removed. 104 | */ 105 | function remove(List storage self, address node) internal { 106 | require(isInList(self, node), "LinkedList: not in list"); 107 | 108 | address p = self.list[node].previous; 109 | address n = self.list[node].next; 110 | 111 | self.list[p].next = n; 112 | self.list[n].previous = p; 113 | 114 | // Deleting the node should set this value to false, but we set it here for 115 | // explicitness. 116 | self.list[node].inList = false; 117 | delete self.list[node]; 118 | 119 | self.length -= 1; 120 | } 121 | 122 | /** 123 | * @notice Insert a node at the beginning of the list. 124 | * 125 | * @param self The list being used. 126 | * @param node The node to insert at the beginning of the list. 127 | */ 128 | function prepend(List storage self, address node) internal { 129 | // isInList(node) is checked in insertBefore 130 | 131 | insertBefore(self, begin(self), node); 132 | } 133 | 134 | /** 135 | * @notice Insert a node at the end of the list. 136 | * 137 | * @param self The list being used. 138 | * @param node The node to insert at the end of the list. 139 | */ 140 | function append(List storage self, address node) internal { 141 | // isInList(node) is checked in insertBefore 142 | 143 | insertAfter(self, end(self), node); 144 | } 145 | 146 | function swap( 147 | List storage self, 148 | address left, 149 | address right 150 | ) internal { 151 | // isInList(left) and isInList(right) are checked in remove 152 | 153 | address previousRight = self.list[right].previous; 154 | remove(self, right); 155 | insertAfter(self, left, right); 156 | remove(self, left); 157 | insertAfter(self, previousRight, left); 158 | } 159 | 160 | function isInList(List storage self, address node) 161 | internal 162 | view 163 | returns (bool) 164 | { 165 | return self.list[node].inList; 166 | } 167 | 168 | /** 169 | * @notice Get the node at the beginning of a double linked list. 170 | * 171 | * @param self The list being used. 172 | * 173 | * @return A address identifying the node at the beginning of the double 174 | * linked list. 175 | */ 176 | function begin(List storage self) internal view returns (address) { 177 | return self.list[NULL].next; 178 | } 179 | 180 | /** 181 | * @notice Get the node at the end of a double linked list. 182 | * 183 | * @param self The list being used. 184 | * 185 | * @return A address identifying the node at the end of the double linked 186 | * list. 187 | */ 188 | function end(List storage self) internal view returns (address) { 189 | return self.list[NULL].previous; 190 | } 191 | 192 | function next(List storage self, address node) 193 | internal 194 | view 195 | returns (address) 196 | { 197 | require(isInList(self, node), "LinkedList: not in list"); 198 | return self.list[node].next; 199 | } 200 | 201 | function previous(List storage self, address node) 202 | internal 203 | view 204 | returns (address) 205 | { 206 | require(isInList(self, node), "LinkedList: not in list"); 207 | return self.list[node].previous; 208 | } 209 | 210 | function elements( 211 | List storage self, 212 | address _start, 213 | uint256 _count 214 | ) internal view returns (address[] memory) { 215 | require(_count > 0, "LinkedList: invalid count"); 216 | require( 217 | isInList(self, _start) || _start == address(0), 218 | "LinkedList: not in list" 219 | ); 220 | address[] memory elems = new address[](_count); 221 | 222 | // Begin with the first node in the list 223 | uint256 n = 0; 224 | address nextItem = _start; 225 | if (nextItem == address(0)) { 226 | nextItem = begin(self); 227 | } 228 | 229 | while (n < _count) { 230 | if (nextItem == address(0)) { 231 | break; 232 | } 233 | elems[n] = nextItem; 234 | nextItem = next(self, nextItem); 235 | n += 1; 236 | } 237 | return elems; 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /contracts/libraries/String.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | library String { 4 | /// @notice Convert a uint value to its decimal string representation 5 | // solium-disable-next-line security/no-assign-params 6 | function fromUint(uint256 _i) internal pure returns (string memory) { 7 | if (_i == 0) { 8 | return "0"; 9 | } 10 | uint256 j = _i; 11 | uint256 len; 12 | while (j != 0) { 13 | len++; 14 | j /= 10; 15 | } 16 | bytes memory bstr = new bytes(len); 17 | uint256 k = len - 1; 18 | while (_i != 0) { 19 | bstr[k--] = bytes1(uint8(48 + (_i % 10))); 20 | _i /= 10; 21 | } 22 | return string(bstr); 23 | } 24 | 25 | /// @notice Convert a bytes32 value to its hex string representation. 26 | function fromBytes32(bytes32 _value) internal pure returns (string memory) { 27 | bytes memory alphabet = "0123456789abcdef"; 28 | 29 | bytes memory str = new bytes(32 * 2 + 2); 30 | str[0] = "0"; 31 | str[1] = "x"; 32 | for (uint256 i = 0; i < 32; i++) { 33 | str[2 + i * 2] = alphabet[uint256(uint8(_value[i] >> 4))]; 34 | str[3 + i * 2] = alphabet[uint256(uint8(_value[i] & 0x0f))]; 35 | } 36 | return string(str); 37 | } 38 | 39 | /// @notice Convert an address to its hex string representation. 40 | function fromAddress(address _addr) internal pure returns (string memory) { 41 | bytes32 value = bytes32(uint256(_addr)); 42 | bytes memory alphabet = "0123456789abcdef"; 43 | 44 | bytes memory str = new bytes(20 * 2 + 2); 45 | str[0] = "0"; 46 | str[1] = "x"; 47 | for (uint256 i = 0; i < 20; i++) { 48 | str[2 + i * 2] = alphabet[uint256(uint8(value[i + 12] >> 4))]; 49 | str[3 + i * 2] = alphabet[uint256(uint8(value[i + 12] & 0x0f))]; 50 | } 51 | return string(str); 52 | } 53 | 54 | /// @notice Append eight strings. 55 | function add8( 56 | string memory a, 57 | string memory b, 58 | string memory c, 59 | string memory d, 60 | string memory e, 61 | string memory f, 62 | string memory g, 63 | string memory h 64 | ) internal pure returns (string memory) { 65 | return string(abi.encodePacked(a, b, c, d, e, f, g, h)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contracts/libraries/Validate.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/cryptography/ECDSA.sol"; 4 | 5 | import "../libraries/String.sol"; 6 | import "../libraries/Compare.sol"; 7 | 8 | /// @notice Validate is a library for validating malicious darknode behaviour. 9 | library Validate { 10 | /// @notice Recovers two propose messages and checks if they were signed by 11 | /// the same darknode. If they were different but the height and 12 | /// round were the same, then the darknode was behaving maliciously. 13 | /// @return The address of the signer if and only if propose messages were 14 | /// different. 15 | function duplicatePropose( 16 | uint256 _height, 17 | uint256 _round, 18 | bytes memory _blockhash1, 19 | uint256 _validRound1, 20 | bytes memory _signature1, 21 | bytes memory _blockhash2, 22 | uint256 _validRound2, 23 | bytes memory _signature2 24 | ) internal pure returns (address) { 25 | require( 26 | !Compare.bytesEqual(_signature1, _signature2), 27 | "Validate: same signature" 28 | ); 29 | address signer1 = 30 | recoverPropose( 31 | _height, 32 | _round, 33 | _blockhash1, 34 | _validRound1, 35 | _signature1 36 | ); 37 | address signer2 = 38 | recoverPropose( 39 | _height, 40 | _round, 41 | _blockhash2, 42 | _validRound2, 43 | _signature2 44 | ); 45 | require(signer1 == signer2, "Validate: different signer"); 46 | return signer1; 47 | } 48 | 49 | function recoverPropose( 50 | uint256 _height, 51 | uint256 _round, 52 | bytes memory _blockhash, 53 | uint256 _validRound, 54 | bytes memory _signature 55 | ) internal pure returns (address) { 56 | return 57 | ECDSA.recover( 58 | sha256( 59 | proposeMessage(_height, _round, _blockhash, _validRound) 60 | ), 61 | _signature 62 | ); 63 | } 64 | 65 | function proposeMessage( 66 | uint256 _height, 67 | uint256 _round, 68 | bytes memory _blockhash, 69 | uint256 _validRound 70 | ) internal pure returns (bytes memory) { 71 | return 72 | abi.encodePacked( 73 | "Propose(Height=", 74 | String.fromUint(_height), 75 | ",Round=", 76 | String.fromUint(_round), 77 | ",BlockHash=", 78 | string(_blockhash), 79 | ",ValidRound=", 80 | String.fromUint(_validRound), 81 | ")" 82 | ); 83 | } 84 | 85 | /// @notice Recovers two prevote messages and checks if they were signed by 86 | /// the same darknode. If they were different but the height and 87 | /// round were the same, then the darknode was behaving maliciously. 88 | /// @return The address of the signer if and only if prevote messages were 89 | /// different. 90 | function duplicatePrevote( 91 | uint256 _height, 92 | uint256 _round, 93 | bytes memory _blockhash1, 94 | bytes memory _signature1, 95 | bytes memory _blockhash2, 96 | bytes memory _signature2 97 | ) internal pure returns (address) { 98 | require( 99 | !Compare.bytesEqual(_signature1, _signature2), 100 | "Validate: same signature" 101 | ); 102 | address signer1 = 103 | recoverPrevote(_height, _round, _blockhash1, _signature1); 104 | address signer2 = 105 | recoverPrevote(_height, _round, _blockhash2, _signature2); 106 | require(signer1 == signer2, "Validate: different signer"); 107 | return signer1; 108 | } 109 | 110 | function recoverPrevote( 111 | uint256 _height, 112 | uint256 _round, 113 | bytes memory _blockhash, 114 | bytes memory _signature 115 | ) internal pure returns (address) { 116 | return 117 | ECDSA.recover( 118 | sha256(prevoteMessage(_height, _round, _blockhash)), 119 | _signature 120 | ); 121 | } 122 | 123 | function prevoteMessage( 124 | uint256 _height, 125 | uint256 _round, 126 | bytes memory _blockhash 127 | ) internal pure returns (bytes memory) { 128 | return 129 | abi.encodePacked( 130 | "Prevote(Height=", 131 | String.fromUint(_height), 132 | ",Round=", 133 | String.fromUint(_round), 134 | ",BlockHash=", 135 | string(_blockhash), 136 | ")" 137 | ); 138 | } 139 | 140 | /// @notice Recovers two precommit messages and checks if they were signed 141 | /// by the same darknode. If they were different but the height and 142 | /// round were the same, then the darknode was behaving maliciously. 143 | /// @return The address of the signer if and only if precommit messages were 144 | /// different. 145 | function duplicatePrecommit( 146 | uint256 _height, 147 | uint256 _round, 148 | bytes memory _blockhash1, 149 | bytes memory _signature1, 150 | bytes memory _blockhash2, 151 | bytes memory _signature2 152 | ) internal pure returns (address) { 153 | require( 154 | !Compare.bytesEqual(_signature1, _signature2), 155 | "Validate: same signature" 156 | ); 157 | address signer1 = 158 | recoverPrecommit(_height, _round, _blockhash1, _signature1); 159 | address signer2 = 160 | recoverPrecommit(_height, _round, _blockhash2, _signature2); 161 | require(signer1 == signer2, "Validate: different signer"); 162 | return signer1; 163 | } 164 | 165 | function recoverPrecommit( 166 | uint256 _height, 167 | uint256 _round, 168 | bytes memory _blockhash, 169 | bytes memory _signature 170 | ) internal pure returns (address) { 171 | return 172 | ECDSA.recover( 173 | sha256(precommitMessage(_height, _round, _blockhash)), 174 | _signature 175 | ); 176 | } 177 | 178 | function precommitMessage( 179 | uint256 _height, 180 | uint256 _round, 181 | bytes memory _blockhash 182 | ) internal pure returns (bytes memory) { 183 | return 184 | abi.encodePacked( 185 | "Precommit(Height=", 186 | String.fromUint(_height), 187 | ",Round=", 188 | String.fromUint(_round), 189 | ",BlockHash=", 190 | string(_blockhash), 191 | ")" 192 | ); 193 | } 194 | 195 | function recoverSecret( 196 | uint256 _a, 197 | uint256 _b, 198 | uint256 _c, 199 | uint256 _d, 200 | uint256 _e, 201 | uint256 _f, 202 | bytes memory _signature 203 | ) internal pure returns (address) { 204 | return 205 | ECDSA.recover( 206 | sha256(secretMessage(_a, _b, _c, _d, _e, _f)), 207 | _signature 208 | ); 209 | } 210 | 211 | function secretMessage( 212 | uint256 _a, 213 | uint256 _b, 214 | uint256 _c, 215 | uint256 _d, 216 | uint256 _e, 217 | uint256 _f 218 | ) internal pure returns (bytes memory) { 219 | return 220 | abi.encodePacked( 221 | "Secret(", 222 | "ShamirShare(", 223 | String.fromUint(_a), 224 | ",", 225 | String.fromUint(_b), 226 | ",S256N(", 227 | String.fromUint(_c), 228 | "),", 229 | "S256PrivKey(", 230 | "S256N(", 231 | String.fromUint(_d), 232 | "),", 233 | "S256P(", 234 | String.fromUint(_e), 235 | "),", 236 | "S256P(", 237 | String.fromUint(_f), 238 | ")", 239 | ")", 240 | ")", 241 | ")" 242 | ); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /contracts/migrations/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint256 public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) { 9 | _; 10 | } 11 | } 12 | 13 | constructor() public { 14 | owner = msg.sender; 15 | } 16 | 17 | function setCompleted(uint256 completed) public restricted { 18 | last_completed_migration = completed; 19 | } 20 | 21 | function upgrade(address new_address) public restricted { 22 | Migrations upgraded = Migrations(new_address); 23 | upgraded.setCompleted(last_completed_migration); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/test/Claimer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../Governance/Claimable.sol"; 4 | 5 | contract Claimer { 6 | Claimable child; 7 | 8 | constructor(Claimable _child) public { 9 | child = _child; 10 | } 11 | 12 | function transferStoreOwnership(address _newOwner) external { 13 | child.transferOwnership(_newOwner); 14 | } 15 | 16 | function claimStoreOwnership() external { 17 | child.claimOwnership(); 18 | } 19 | 20 | function claimTokenOwnership() public { 21 | child.claimOwnership(); 22 | } 23 | 24 | function transferTokenOwnership(address _newOwner) public { 25 | child.transferOwnership(address(_newOwner)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/test/CompareTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import {Compare} from "../libraries/Compare.sol"; 4 | 5 | /// @dev CompareTest exposes the internal functions of Compare.sol. 6 | contract CompareTest { 7 | function bytesEqual(bytes memory a, bytes memory b) 8 | public 9 | pure 10 | returns (bool) 11 | { 12 | return Compare.bytesEqual(a, b); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/test/CycleChanger.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../DarknodePayment/DarknodePayment.sol"; 4 | 5 | /// @notice CycleChanger attempts to change the cycle twice in the same block. 6 | contract CycleChanger { 7 | DarknodePayment public darknodePayment; // Passed in as a constructor parameter. 8 | 9 | /// @notice The contract constructor. 10 | /// @param _darknodePayment The address of the DarknodePaymentStore contract. 11 | constructor(DarknodePayment _darknodePayment) public { 12 | darknodePayment = _darknodePayment; 13 | } 14 | 15 | function changeCycle() public { 16 | darknodePayment.changeCycle(); 17 | darknodePayment.changeCycle(); 18 | } 19 | 20 | function time() public view returns (uint256) { 21 | return block.timestamp; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/test/ERC20WithFeesTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol"; 6 | 7 | import "../libraries/ERC20WithFees.sol"; 8 | 9 | contract ERC20WithFeesTest { 10 | using SafeMath for uint256; 11 | using SafeERC20 for ERC20; 12 | using ERC20WithFees for ERC20; 13 | 14 | // Stores its own balance amount 15 | mapping(address => uint256) public balances; 16 | 17 | function deposit(address _token, uint256 _value) external { 18 | balances[_token] = ERC20(_token).balanceOf(address(this)); 19 | 20 | uint256 newValue = 21 | ERC20(_token).safeTransferFromWithFees( 22 | msg.sender, 23 | address(this), 24 | _value 25 | ); 26 | balances[_token] = balances[_token].add(newValue); 27 | require( 28 | ERC20(_token).balanceOf(address(this)) == balances[_token], 29 | "ERC20WithFeesTest: incorrect balance in deposit" 30 | ); 31 | } 32 | 33 | function withdraw(address _token, uint256 _value) external { 34 | balances[_token] = ERC20(_token).balanceOf(address(this)); 35 | 36 | ERC20(_token).safeTransfer(msg.sender, _value); 37 | balances[_token] = balances[_token].sub(_value); 38 | require( 39 | ERC20(_token).balanceOf(address(this)) == balances[_token], 40 | "ERC20WithFeesTest: incorrect balance in withdraw" 41 | ); 42 | } 43 | 44 | function approve(address _token, uint256 _value) external { 45 | ERC20(_token).safeApprove(msg.sender, _value); 46 | } 47 | 48 | function naiveDeposit(address _token, uint256 _value) external { 49 | balances[_token] = ERC20(_token).balanceOf(address(this)); 50 | 51 | ERC20(_token).safeTransferFrom(msg.sender, address(this), _value); 52 | balances[_token] = balances[_token].add(_value); 53 | require( 54 | ERC20(_token).balanceOf(address(this)) == balances[_token], 55 | "ERC20WithFeesTest: incorrect balance in deposit" 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /contracts/test/ForceSend.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | contract ForceSend { 4 | function send(address payable recipient) public payable { 5 | selfdestruct(recipient); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /contracts/test/LinkedListTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../libraries/LinkedList.sol"; 4 | 5 | /// @notice A token that exposes the LinkedList library for testing. 6 | contract LinkedListTest { 7 | using LinkedList for LinkedList.List; 8 | 9 | LinkedList.List private ll; 10 | 11 | function isInList(address node) public view returns (bool) { 12 | return ll.isInList(node); 13 | } 14 | 15 | function next(address node) public view returns (address) { 16 | return ll.next(node); 17 | } 18 | 19 | function previous(address node) public view returns (address) { 20 | return ll.previous(node); 21 | } 22 | 23 | function begin() public view returns (address) { 24 | return ll.begin(); 25 | } 26 | 27 | function end() public view returns (address) { 28 | return ll.end(); 29 | } 30 | 31 | function insertBefore(address target, address newNode) public { 32 | ll.insertBefore(target, newNode); 33 | } 34 | 35 | function insertAfter(address target, address newNode) public { 36 | ll.insertAfter(target, newNode); 37 | } 38 | 39 | function remove(address node) public { 40 | ll.remove(node); 41 | } 42 | 43 | function prepend(address newNode) public { 44 | ll.prepend(newNode); 45 | } 46 | 47 | function append(address newNode) public { 48 | ll.append(newNode); 49 | } 50 | 51 | function swap(address node1, address node2) public { 52 | ll.swap(node1, node2); 53 | } 54 | 55 | function elements(address _start, uint256 _count) 56 | public 57 | view 58 | returns (address[] memory) 59 | { 60 | return ll.elements(_start, _count); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /contracts/test/StringTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import {String} from "../libraries/String.sol"; 4 | 5 | /// @dev StringTest exposes the internal functions of String.sol. 6 | contract StringTest { 7 | function fromUint(uint256 _i) public pure returns (string memory) { 8 | return String.fromUint(_i); 9 | } 10 | 11 | function fromBytes32(bytes32 _value) public pure returns (string memory) { 12 | return String.fromBytes32(_value); 13 | } 14 | 15 | function fromAddress(address _addr) public pure returns (string memory) { 16 | return String.fromAddress(_addr); 17 | } 18 | 19 | function add4( 20 | string memory a, 21 | string memory b, 22 | string memory c, 23 | string memory d 24 | ) public pure returns (string memory) { 25 | return String.add8(a, b, c, d, "", "", "", ""); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/test/ValidateTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../libraries/Validate.sol"; 4 | 5 | /// @notice Validate is a library for validating malicious darknode behaviour. 6 | contract ValidateTest { 7 | function duplicatePropose( 8 | uint256 _height, 9 | uint256 _round, 10 | bytes memory _blockhash1, 11 | uint256 _validRound1, 12 | bytes memory _signature1, 13 | bytes memory _blockhash2, 14 | uint256 _validRound2, 15 | bytes memory _signature2 16 | ) public pure returns (address) { 17 | return 18 | Validate.duplicatePropose( 19 | _height, 20 | _round, 21 | _blockhash1, 22 | _validRound1, 23 | _signature1, 24 | _blockhash2, 25 | _validRound2, 26 | _signature2 27 | ); 28 | } 29 | 30 | function recoverPropose( 31 | uint256 _height, 32 | uint256 _round, 33 | bytes memory _blockhash, 34 | uint256 _validRound, 35 | bytes memory _signature 36 | ) public pure returns (address) { 37 | return 38 | Validate.recoverPropose( 39 | _height, 40 | _round, 41 | _blockhash, 42 | _validRound, 43 | _signature 44 | ); 45 | } 46 | 47 | function duplicatePrevote( 48 | uint256 _height, 49 | uint256 _round, 50 | bytes memory _blockhash1, 51 | bytes memory _signature1, 52 | bytes memory _blockhash2, 53 | bytes memory _signature2 54 | ) public pure returns (address) { 55 | return 56 | Validate.duplicatePrevote( 57 | _height, 58 | _round, 59 | _blockhash1, 60 | _signature1, 61 | _blockhash2, 62 | _signature2 63 | ); 64 | } 65 | 66 | function recoverPrevote( 67 | uint256 _height, 68 | uint256 _round, 69 | bytes memory _blockhash, 70 | bytes memory _signature 71 | ) public pure returns (address) { 72 | return Validate.recoverPrevote(_height, _round, _blockhash, _signature); 73 | } 74 | 75 | function duplicatePrecommit( 76 | uint256 _height, 77 | uint256 _round, 78 | bytes memory _blockhash1, 79 | bytes memory _signature1, 80 | bytes memory _blockhash2, 81 | bytes memory _signature2 82 | ) public pure returns (address) { 83 | return 84 | Validate.duplicatePrecommit( 85 | _height, 86 | _round, 87 | _blockhash1, 88 | _signature1, 89 | _blockhash2, 90 | _signature2 91 | ); 92 | } 93 | 94 | function recoverPrecommit( 95 | uint256 _height, 96 | uint256 _round, 97 | bytes memory _blockhash, 98 | bytes memory _signature 99 | ) public pure returns (address) { 100 | return 101 | Validate.recoverPrecommit(_height, _round, _blockhash, _signature); 102 | } 103 | 104 | function proposeMessage( 105 | uint256 _height, 106 | uint256 _round, 107 | bytes memory _blockhash, 108 | uint256 _validRound 109 | ) public pure returns (bytes memory) { 110 | return 111 | Validate.proposeMessage(_height, _round, _blockhash, _validRound); 112 | } 113 | 114 | function prevoteMessage( 115 | uint256 _height, 116 | uint256 _round, 117 | bytes memory _blockhash 118 | ) public pure returns (bytes memory) { 119 | return Validate.prevoteMessage(_height, _round, _blockhash); 120 | } 121 | 122 | function precommitMessage( 123 | uint256 _height, 124 | uint256 _round, 125 | bytes memory _blockhash 126 | ) public pure returns (bytes memory) { 127 | return Validate.precommitMessage(_height, _round, _blockhash); 128 | } 129 | 130 | function recoverSecret( 131 | uint256 _a, 132 | uint256 _b, 133 | uint256 _c, 134 | uint256 _d, 135 | uint256 _e, 136 | uint256 _f, 137 | bytes memory _signature 138 | ) public pure returns (address) { 139 | return Validate.recoverSecret(_a, _b, _c, _d, _e, _f, _signature); 140 | } 141 | 142 | function secretMessage( 143 | uint256 _a, 144 | uint256 _b, 145 | uint256 _c, 146 | uint256 _d, 147 | uint256 _e, 148 | uint256 _f 149 | ) public pure returns (bytes memory) { 150 | return Validate.secretMessage(_a, _b, _c, _d, _e, _f); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /contracts/test/tokens/ImpreciseToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol"; 5 | 6 | /// @notice A test ERC20 token with 12 decimals. 7 | contract ImpreciseToken is ERC20, ERC20Detailed { 8 | string private constant _name = "Imprecise Token"; // solium-disable-line uppercase 9 | string private constant _symbol = "IPT"; // solium-disable-line uppercase 10 | uint8 private constant _decimals = 9; // solium-disable-line uppercase 11 | 12 | uint256 public constant INITIAL_SUPPLY = 13 | 1000000000 * (10**uint256(_decimals)); 14 | 15 | /** 16 | * @dev Constructor that gives msg.sender all of existing tokens. 17 | */ 18 | /// @notice The RenToken Constructor. 19 | constructor() public { 20 | ERC20Detailed.initialize(_name, _symbol, _decimals); 21 | _mint(msg.sender, INITIAL_SUPPLY); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/test/tokens/NonCompliantToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 4 | 5 | contract NCT_ERC20Basic { 6 | uint256 public totalSupply; 7 | 8 | function balanceOf(address who) public view returns (uint256); 9 | 10 | function transfer(address to, uint256 value) public; 11 | 12 | event Transfer(address indexed from, address indexed to, uint256 value); 13 | } 14 | 15 | contract NCT_BasicToken is NCT_ERC20Basic { 16 | using SafeMath for uint256; 17 | 18 | mapping(address => uint256) balances; 19 | 20 | modifier onlyPayloadSize(uint256 size) { 21 | if (msg.data.length < size + 4) { 22 | revert(); 23 | } 24 | _; 25 | } 26 | 27 | function transfer(address _to, uint256 _value) 28 | public 29 | onlyPayloadSize(2 * 32) 30 | { 31 | balances[msg.sender] = balances[msg.sender].sub(_value); 32 | balances[_to] = balances[_to].add(_value); 33 | emit Transfer(msg.sender, _to, _value); 34 | } 35 | 36 | function balanceOf(address _owner) public view returns (uint256 balance) { 37 | return balances[_owner]; 38 | } 39 | } 40 | 41 | contract NCT_ERC20 is NCT_ERC20Basic { 42 | function allowance(address owner, address spender) 43 | public 44 | view 45 | returns (uint256); 46 | 47 | function transferFrom( 48 | address from, 49 | address to, 50 | uint256 value 51 | ) public; 52 | 53 | function approve(address spender, uint256 value) public; 54 | 55 | event Approval( 56 | address indexed owner, 57 | address indexed spender, 58 | uint256 value 59 | ); 60 | } 61 | 62 | contract NCT_StandardToken is NCT_BasicToken, NCT_ERC20 { 63 | mapping(address => mapping(address => uint256)) allowed; 64 | 65 | function transferFrom( 66 | address _from, 67 | address _to, 68 | uint256 _value 69 | ) public onlyPayloadSize(3 * 32) { 70 | uint256 _allowance = allowed[_from][msg.sender]; 71 | 72 | balances[_to] = balances[_to].add(_value); 73 | balances[_from] = balances[_from].sub(_value); 74 | allowed[_from][msg.sender] = _allowance.sub(_value); 75 | emit Transfer(_from, _to, _value); 76 | } 77 | 78 | function approve(address _spender, uint256 _value) public { 79 | if ((_value != 0) && (allowed[msg.sender][_spender] != 0)) revert(); 80 | 81 | allowed[msg.sender][_spender] = _value; 82 | emit Approval(msg.sender, _spender, _value); 83 | } 84 | 85 | function allowance(address _owner, address _spender) 86 | public 87 | view 88 | returns (uint256 remaining) 89 | { 90 | return allowed[_owner][_spender]; 91 | } 92 | } 93 | 94 | contract NonCompliantToken is NCT_StandardToken { 95 | string public constant name = "Non Compliant Token"; 96 | string public constant symbol = "NCT"; 97 | uint8 public constant decimals = 18; 98 | uint256 public constant INITIAL_SUPPLY = 1000000000 * 10**uint256(decimals); 99 | 100 | constructor() public { 101 | totalSupply = INITIAL_SUPPLY; 102 | balances[msg.sender] = INITIAL_SUPPLY; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /contracts/test/tokens/NormalToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol"; 5 | 6 | contract NormalToken is ERC20, ERC20Detailed { 7 | string private constant _name = "Normal Token"; 8 | string private constant _symbol = "NML"; 9 | uint8 private constant _decimals = 18; 10 | 11 | uint256 public constant INITIAL_SUPPLY = 12 | 1000000000 * 10**uint256(_decimals); 13 | 14 | constructor() public { 15 | ERC20Detailed.initialize(_name, _symbol, _decimals); 16 | _mint(msg.sender, INITIAL_SUPPLY); 17 | } 18 | 19 | function approve(address _spender, uint256 _value) 20 | public 21 | returns (bool success) 22 | { 23 | if ((_value != 0) && (allowance(msg.sender, _spender) != 0)) 24 | revert("approve with previous allowance"); 25 | 26 | return super.approve(_spender, _value); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/test/tokens/PaymentToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Pausable.sol"; 5 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Burnable.sol"; 6 | 7 | contract PaymentToken is ERC20Pausable, ERC20Burnable, ERC20Detailed { 8 | uint8 private constant _decimals = 18; 9 | 10 | uint256 public constant INITIAL_SUPPLY = 11 | 1000000000 * 10**uint256(_decimals); 12 | 13 | constructor(string memory symbol) public { 14 | ERC20Pausable.initialize(msg.sender); 15 | ERC20Detailed.initialize(symbol, symbol, _decimals); 16 | _mint(msg.sender, INITIAL_SUPPLY); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/test/tokens/ReturnsFalseToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | contract RFT_Token { 4 | uint256 public totalSupply; 5 | 6 | function balanceOf(address _owner) public view returns (uint256 balance); 7 | 8 | function transfer(address _to, uint256 _value) 9 | public 10 | returns (bool success); 11 | 12 | function transferFrom( 13 | address _from, 14 | address _to, 15 | uint256 _value 16 | ) public returns (bool success); 17 | 18 | function approve(address _spender, uint256 _value) 19 | public 20 | returns (bool success); 21 | 22 | function allowance(address _owner, address _spender) 23 | public 24 | view 25 | returns (uint256 remaining); 26 | 27 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 28 | event Approval( 29 | address indexed _owner, 30 | address indexed _spender, 31 | uint256 _value 32 | ); 33 | } 34 | 35 | contract RFT_StandardToken is RFT_Token { 36 | function transfer(address _to, uint256 _value) 37 | public 38 | returns (bool success) 39 | { 40 | if (balances[msg.sender] >= _value && _value > 0) { 41 | balances[msg.sender] -= _value; 42 | balances[_to] += _value; 43 | emit Transfer(msg.sender, _to, _value); 44 | return true; 45 | } else { 46 | return false; 47 | } 48 | } 49 | 50 | function transferFrom( 51 | address _from, 52 | address _to, 53 | uint256 _value 54 | ) public returns (bool success) { 55 | if ( 56 | balances[_from] >= _value && 57 | allowed[_from][msg.sender] >= _value && 58 | _value > 0 59 | ) { 60 | balances[_to] += _value; 61 | balances[_from] -= _value; 62 | allowed[_from][msg.sender] -= _value; 63 | emit Transfer(_from, _to, _value); 64 | return true; 65 | } else { 66 | return false; 67 | } 68 | } 69 | 70 | function balanceOf(address _owner) public view returns (uint256 balance) { 71 | return balances[_owner]; 72 | } 73 | 74 | function approve(address _spender, uint256 _value) 75 | public 76 | returns (bool success) 77 | { 78 | allowed[msg.sender][_spender] = _value; 79 | emit Approval(msg.sender, _spender, _value); 80 | return true; 81 | } 82 | 83 | function allowance(address _owner, address _spender) 84 | public 85 | view 86 | returns (uint256 remaining) 87 | { 88 | return allowed[_owner][_spender]; 89 | } 90 | 91 | mapping(address => uint256) balances; 92 | mapping(address => mapping(address => uint256)) allowed; 93 | } 94 | 95 | contract ReturnsFalseToken is RFT_StandardToken { 96 | string public constant name = "Returns False Token"; 97 | string public constant symbol = "RFT"; 98 | uint8 public constant decimals = 18; 99 | uint256 public constant INITIAL_SUPPLY = 1000000000 * 10**uint256(decimals); 100 | 101 | constructor() public { 102 | totalSupply = INITIAL_SUPPLY; 103 | balances[msg.sender] = INITIAL_SUPPLY; 104 | } 105 | 106 | function approve(address _spender, uint256 _value) 107 | public 108 | returns (bool success) 109 | { 110 | if ((_value != 0) && (allowed[msg.sender][_spender] != 0)) return false; 111 | 112 | return super.approve(_spender, _value); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /contracts/test/tokens/SelfDestructingToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol"; 6 | 7 | /// @notice A test ERC20 token that can destroy itself. 8 | contract SelfDestructingToken is ERC20, ERC20Detailed, Ownable { 9 | string private constant _name = "Self Destructing Token"; 10 | string private constant _symbol = "SDT"; 11 | uint8 private constant _decimals = 18; 12 | 13 | uint256 public constant INITIAL_SUPPLY = 14 | 1000000000 * 10**uint256(_decimals); 15 | 16 | /// @notice The SelfDestructingToken Constructor. 17 | constructor() public { 18 | ERC20Detailed.initialize(_name, _symbol, _decimals); 19 | Ownable.initialize(msg.sender); 20 | _mint(msg.sender, INITIAL_SUPPLY); 21 | } 22 | 23 | function destruct() public onlyOwner { 24 | selfdestruct(msg.sender); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/test/tokens/TokenWithFees.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol"; 5 | 6 | contract TokenWithFees is ERC20, ERC20Detailed { 7 | string private constant _name = "Token With Fees"; 8 | string private constant _symbol = "TWF"; 9 | uint8 private constant _decimals = 18; 10 | 11 | uint256 public constant INITIAL_SUPPLY = 12 | 1000000000 * 10**uint256(_decimals); 13 | 14 | constructor() public { 15 | ERC20Detailed.initialize(_name, _symbol, _decimals); 16 | _mint(msg.sender, INITIAL_SUPPLY); 17 | } 18 | 19 | function approve(address _spender, uint256 _value) 20 | public 21 | returns (bool success) 22 | { 23 | if ((_value != 0) && (allowance(msg.sender, _spender) != 0)) 24 | revert("approve with previous allowance"); 25 | 26 | return super.approve(_spender, _value); 27 | } 28 | 29 | function transfer(address to, uint256 value) public returns (bool) { 30 | uint256 fee = (value * 3) / 1000; 31 | _burn(msg.sender, fee); 32 | return super.transfer(to, value - fee); 33 | } 34 | 35 | function transferFrom( 36 | address from, 37 | address to, 38 | uint256 value 39 | ) public returns (bool) { 40 | uint256 fee = (value * 3) / 1000; 41 | bool ret = super.transferFrom(from, to, value); 42 | _burn(to, fee); 43 | return ret; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /migrations/1_darknodes.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | const BN = require("bn.js"); 4 | const { execSync } = require("child_process"); 5 | 6 | const RenToken = artifacts.require("RenToken"); 7 | const DarknodePayment = artifacts.require("DarknodePayment"); 8 | const DarknodePaymentStore = artifacts.require("DarknodePaymentStore"); 9 | const ClaimlessRewards = artifacts.require("ClaimlessRewards"); 10 | const DarknodeRegistryStore = artifacts.require("DarknodeRegistryStore"); 11 | const DarknodeRegistryProxy = artifacts.require("DarknodeRegistryProxy"); 12 | const DarknodeRegistryLogicV1 = artifacts.require("DarknodeRegistryLogicV1"); 13 | const DarknodeSlasher = artifacts.require("DarknodeSlasher"); 14 | const Protocol = artifacts.require("Protocol"); 15 | const ClaimRewards = artifacts.require("ClaimRewards"); 16 | const GetOperatorDarknodes = artifacts.require("GetOperatorDarknodes"); 17 | const RenProxyAdmin = artifacts.require("RenProxyAdmin"); 18 | 19 | const networks = require("./networks.js"); 20 | 21 | const { encodeCallData } = require("./encode"); 22 | 23 | const NULL = "0x0000000000000000000000000000000000000000"; 24 | 25 | const gitCommit = () => 26 | execSync("git describe --always --long") 27 | .toString() 28 | .trim(); 29 | 30 | /** 31 | * @dev In order to specify what contracts to re-deploy, update `networks.js`. 32 | * 33 | * For the network you want to use, set the contracts' addresses to `""` and run: 34 | * `NETWORK=testnet yarn deploy` (replacing network) 35 | * 36 | * Don't forget to verify the contracts on etherscan: 37 | * `NETWORK=testnet yarn verify DarknodePayment DarknodePaymentStore` 38 | * (replacing network and contract names) 39 | * 40 | * @param {any} deployer 41 | * @param {string} network 42 | */ 43 | module.exports = async function(deployer, network) { 44 | const contractOwner = (await web3.eth.getAccounts())[0]; 45 | const Ox = web3.utils.toChecksumAddress; 46 | 47 | deployer.logger.log( 48 | `Deploying to ${network} (${network.replace("-fork", "")})...` 49 | ); 50 | 51 | network = network.replace("-fork", ""); 52 | 53 | const addresses = networks[network] || {}; 54 | const config = networks[network] 55 | ? networks[network].config 56 | : networks.config; 57 | 58 | const VERSION_STRING = `${network}-${gitCommit()}`; 59 | 60 | RenToken.address = addresses.RenToken || ""; 61 | DarknodeSlasher.address = addresses.DarknodeSlasher || ""; 62 | DarknodeRegistryProxy.address = addresses.DarknodeRegistryProxy || ""; 63 | DarknodeRegistryLogicV1.address = addresses.DarknodeRegistryLogicV1 || ""; 64 | DarknodeRegistryStore.address = addresses.DarknodeRegistryStore || ""; 65 | DarknodePaymentStore.address = addresses.DarknodePaymentStore || ""; 66 | DarknodePayment.address = addresses.DarknodePayment || ""; 67 | ClaimlessRewards.address = addresses.ClaimlessRewards || ""; 68 | Protocol.address = addresses.Protocol || ""; 69 | RenProxyAdmin.address = addresses.RenProxyAdmin || ""; 70 | GetOperatorDarknodes.address = addresses.GetOperatorDarknodes || ""; 71 | ClaimRewards.address = addresses.ClaimRewards || ""; 72 | const tokens = addresses.tokens || {}; 73 | 74 | let actionCount = 0; 75 | 76 | /** GetOperatorDarknodes **************************************************************/ 77 | 78 | // !!! 0x4e27a3e21e747cf875ad5829b6d9cb7700b8b5f0 79 | // if (!GetOperatorDarknodes.address) { 80 | // deployer.logger.log("Deploying GetOperatorDarknodes"); 81 | // await deployer.deploy( 82 | // GetOperatorDarknodes, 83 | // DarknodeRegistryProxy.address 84 | // ); 85 | // actionCount++; 86 | // } 87 | // const getOperatorDarknodes = await GetOperatorDarknodes.at( 88 | // GetOperatorDarknodes.address 89 | // ); 90 | 91 | /** PROXY ADMIN ***********************************************************/ 92 | if (!RenProxyAdmin.address) { 93 | deployer.logger.log("Deploying Proxy "); 94 | await deployer.deploy(RenProxyAdmin); 95 | actionCount++; 96 | } 97 | let renProxyAdmin = await RenProxyAdmin.at(RenProxyAdmin.address); 98 | 99 | // /** GetOperatorDarknodes **************************************************************/ 100 | // if (!GetOperatorDarknodes.address) { 101 | // deployer.logger.log("Deploying GetOperatorDarknodes"); 102 | // await deployer.deploy(GetOperatorDarknodes); 103 | // actionCount++; 104 | // } 105 | // const getOperatorDarknodes = await GetOperatorDarknodes.at( 106 | // GetOperatorDarknodes.address 107 | // ); 108 | 109 | /** PROTOCOL **************************************************************/ 110 | if (!Protocol.address) { 111 | deployer.logger.log("Deploying Protocol"); 112 | await deployer.deploy(Protocol); 113 | actionCount++; 114 | } 115 | const protocol = await Protocol.at(Protocol.address); 116 | await protocol.__Protocol_init(contractOwner); 117 | 118 | /** Ren TOKEN *************************************************************/ 119 | if (!RenToken.address) { 120 | deployer.logger.log("Deploying RenToken"); 121 | await deployer.deploy(RenToken); 122 | actionCount++; 123 | } 124 | 125 | /** ClaimRewards **************************************************************/ 126 | if (!ClaimRewards.address) { 127 | deployer.logger.log("Deploying ClaimRewards"); 128 | await deployer.deploy(ClaimRewards); 129 | actionCount++; 130 | } 131 | // const claimRewards = await ClaimRewards.at(ClaimRewards.address); 132 | 133 | /** DARKNODE REGISTRY *****************************************************/ 134 | if (!DarknodeRegistryStore.address) { 135 | deployer.logger.log("Deploying DarknodeRegistryStore"); 136 | await deployer.deploy( 137 | DarknodeRegistryStore, 138 | VERSION_STRING, 139 | RenToken.address 140 | ); 141 | actionCount++; 142 | } 143 | const darknodeRegistryStore = await DarknodeRegistryStore.at( 144 | DarknodeRegistryStore.address 145 | ); 146 | 147 | if (!DarknodeRegistryLogicV1.address) { 148 | deployer.logger.log("Deploying DarknodeRegistryLogicV1"); 149 | await deployer.deploy(DarknodeRegistryLogicV1); 150 | } 151 | const darknodeRegistryLogic = await DarknodeRegistryLogicV1.at( 152 | DarknodeRegistryLogicV1.address 153 | ); 154 | const darknodeRegistryParameters = { 155 | types: [ 156 | "string", 157 | "address", 158 | "address", 159 | "uint256", 160 | "uint256", 161 | "uint256", 162 | "uint256" 163 | ], 164 | values: [ 165 | VERSION_STRING, 166 | RenToken.address, 167 | DarknodeRegistryStore.address, 168 | config.MINIMUM_BOND.toString(), 169 | config.MINIMUM_POD_SIZE, 170 | config.MINIMUM_EPOCH_INTERVAL_SECONDS, 171 | 0 172 | ] 173 | }; 174 | 175 | // Initialize darknodeRegistryLogic so others can't initialize it. 176 | const darknodeRegistryLogicOwner = await darknodeRegistryLogic.owner(); 177 | if (Ox(darknodeRegistryLogicOwner) === Ox(NULL)) { 178 | deployer.logger.log("Ensuring DarknodeRegistryLogic is initialized"); 179 | await darknodeRegistryLogic.initialize( 180 | "", 181 | NULL, 182 | NULL, 183 | "0", 184 | "0", 185 | "0", 186 | 0 187 | ); 188 | actionCount++; 189 | } 190 | 191 | let darknodeRegistryProxy; 192 | if (!DarknodeRegistryProxy.address) { 193 | deployer.logger.log("Deploying DarknodeRegistry"); 194 | await deployer.deploy(DarknodeRegistryProxy); 195 | darknodeRegistryProxy = await DarknodeRegistryProxy.at( 196 | DarknodeRegistryProxy.address 197 | ); 198 | await darknodeRegistryProxy.initialize( 199 | darknodeRegistryLogic.address, 200 | renProxyAdmin.address, 201 | encodeCallData( 202 | web3, 203 | "initialize", 204 | darknodeRegistryParameters.types, 205 | darknodeRegistryParameters.values 206 | ) 207 | ); 208 | actionCount++; 209 | } else { 210 | darknodeRegistryProxy = await DarknodeRegistryProxy.at( 211 | DarknodeRegistryProxy.address 212 | ); 213 | } 214 | const darknodeRegistry = await DarknodeRegistryLogicV1.at( 215 | DarknodeRegistryProxy.address 216 | ); 217 | 218 | const darknodeRegistryProxyLogic = await renProxyAdmin.getProxyImplementation( 219 | darknodeRegistryProxy.address 220 | ); 221 | if (Ox(darknodeRegistryProxyLogic) !== Ox(darknodeRegistryLogic.address)) { 222 | deployer.logger.log( 223 | `DarknodeRegistryProxy is pointing to out-dated ProtocolLogic. Was ${Ox( 224 | darknodeRegistryProxyLogic 225 | )}, now is ${Ox(darknodeRegistryLogic.address)}` 226 | ); 227 | await renProxyAdmin.upgrade( 228 | darknodeRegistryProxy.address, 229 | darknodeRegistryLogic.address 230 | ); 231 | actionCount++; 232 | } 233 | 234 | const storeOwner = await darknodeRegistryStore.owner(); 235 | if (Ox(storeOwner) !== Ox(darknodeRegistry.address)) { 236 | deployer.logger.log( 237 | "Linking DarknodeRegistryStore and DarknodeRegistry" 238 | ); 239 | if (Ox(storeOwner) === Ox(contractOwner)) { 240 | // Initiate ownership transfer of DNR store 241 | const pendingOwner = await darknodeRegistryStore.pendingOwner(); 242 | if (Ox(pendingOwner) !== Ox(darknodeRegistry.address)) { 243 | deployer.logger.log( 244 | "Transferring DarknodeRegistryStore ownership" 245 | ); 246 | await darknodeRegistryStore.transferOwnership( 247 | darknodeRegistry.address 248 | ); 249 | } 250 | 251 | // Claim ownership 252 | deployer.logger.log(`Claiming DNRS ownership in DNR`); 253 | await darknodeRegistry.claimStoreOwnership(); 254 | } else { 255 | deployer.logger.log( 256 | `Transferring DNRS ownership from ${storeOwner} to new DNR` 257 | ); 258 | const oldDNR = await DarknodeRegistryLogicV1.at(storeOwner); 259 | oldDNR.transferStoreOwnership(darknodeRegistry.address); 260 | // This will also call claim, but we try anyway because older 261 | // contracts didn't: 262 | try { 263 | // Claim ownership 264 | await darknodeRegistry.claimStoreOwnership(); 265 | } catch (error) { 266 | // Ignore 267 | } 268 | } 269 | actionCount++; 270 | } 271 | 272 | const protocolDarknodeRegistry = await protocol.getContract( 273 | "DarknodeRegistry" 274 | ); 275 | if (Ox(protocolDarknodeRegistry) !== Ox(darknodeRegistry.address)) { 276 | deployer.logger.log( 277 | `Updating DarknodeRegistry in Protocol contract. Was ${protocolDarknodeRegistry}, now is ${darknodeRegistry.address}` 278 | ); 279 | await protocol.updateContract( 280 | "DarknodeRegistry", 281 | darknodeRegistry.address 282 | ); 283 | actionCount++; 284 | } 285 | 286 | const renInDNR = await darknodeRegistry.ren(); 287 | if (Ox(renInDNR) !== Ox(RenToken.address)) { 288 | console.error( 289 | `ERROR! DNR is pointing to wrong REN token - ${Ox( 290 | renInDNR 291 | )} instead of ${Ox( 292 | RenToken.address 293 | )} - DNR should be updated or redeployed.` 294 | ); 295 | } 296 | 297 | const renInDNRS = await darknodeRegistryStore.ren(); 298 | if (Ox(renInDNRS) !== Ox(RenToken.address)) { 299 | console.error( 300 | `ERROR! DNRS is pointing to wrong REN token - ${Ox( 301 | renInDNRS 302 | )} instead of ${Ox( 303 | RenToken.address 304 | )} - DNRS should be updated or redeployed.` 305 | ); 306 | } 307 | 308 | /*************************************************************************** 309 | ** SLASHER **************************************************************** 310 | **************************************************************************/ 311 | if (!DarknodeSlasher.address) { 312 | deployer.logger.log("Deploying DarknodeSlasher"); 313 | await deployer.deploy(DarknodeSlasher, darknodeRegistry.address); 314 | actionCount++; 315 | } 316 | const slasher = await DarknodeSlasher.at(DarknodeSlasher.address); 317 | 318 | const dnrInSlasher = await slasher.darknodeRegistry(); 319 | if (Ox(dnrInSlasher) !== Ox(darknodeRegistry.address)) { 320 | deployer.logger.log("Updating DNR in Slasher"); 321 | await slasher.updateDarknodeRegistry(darknodeRegistry.address); 322 | actionCount++; 323 | } 324 | 325 | // Set the slash percentages 326 | const blacklistSlashPercent = new BN( 327 | await slasher.blacklistSlashPercent() 328 | ).toNumber(); 329 | if (blacklistSlashPercent !== config.BLACKLIST_SLASH_PERCENT) { 330 | deployer.logger.log("Setting blacklist slash percent"); 331 | await slasher.setBlacklistSlashPercent( 332 | new BN(config.BLACKLIST_SLASH_PERCENT) 333 | ); 334 | actionCount++; 335 | } 336 | const maliciousSlashPercent = new BN( 337 | await slasher.maliciousSlashPercent() 338 | ).toNumber(); 339 | if (maliciousSlashPercent !== config.MALICIOUS_SLASH_PERCENT) { 340 | deployer.logger.log("Setting malicious slash percent"); 341 | await slasher.setMaliciousSlashPercent( 342 | new BN(config.MALICIOUS_SLASH_PERCENT) 343 | ); 344 | actionCount++; 345 | } 346 | const secretRevealSlashPercent = new BN( 347 | await slasher.secretRevealSlashPercent() 348 | ).toNumber(); 349 | if (secretRevealSlashPercent !== config.SECRET_REVEAL_SLASH_PERCENT) { 350 | deployer.logger.log("Setting secret reveal slash percent"); 351 | await slasher.setSecretRevealSlashPercent( 352 | new BN(config.SECRET_REVEAL_SLASH_PERCENT) 353 | ); 354 | actionCount++; 355 | } 356 | 357 | const currentSlasher = await darknodeRegistry.slasher(); 358 | const nextSlasher = await darknodeRegistry.nextSlasher(); 359 | if ( 360 | Ox(currentSlasher) != Ox(DarknodeSlasher.address) && 361 | Ox(nextSlasher) != Ox(DarknodeSlasher.address) 362 | ) { 363 | deployer.logger.log("Linking DarknodeSlasher and DarknodeRegistry"); 364 | // Update slasher address 365 | await darknodeRegistry.updateSlasher(DarknodeSlasher.address); 366 | actionCount++; 367 | } 368 | 369 | /*************************************************************************** 370 | ** DARKNODE PAYMENT ******************************************************* 371 | **************************************************************************/ 372 | if (!DarknodePaymentStore.address) { 373 | deployer.logger.log("Deploying DarknodePaymentStore"); 374 | await deployer.deploy(DarknodePaymentStore, VERSION_STRING); 375 | actionCount++; 376 | } 377 | 378 | if (!DarknodePayment.address) { 379 | // Deploy Darknode Payment 380 | deployer.logger.log("Deploying DarknodePayment"); 381 | await deployer.deploy( 382 | DarknodePayment, 383 | VERSION_STRING, 384 | darknodeRegistry.address, 385 | DarknodePaymentStore.address, 386 | config.DARKNODE_PAYOUT_PERCENT // Reward payout percentage (50% is paid out at any given cycle) 387 | ); 388 | actionCount++; 389 | } 390 | 391 | if (!ClaimlessRewards.address) { 392 | // Deploy Darknode Payment 393 | deployer.logger.log("Deploying ClaimlessRewards"); 394 | await deployer.deploy( 395 | ClaimlessRewards, 396 | darknodeRegistry.address, 397 | DarknodePaymentStore.address, 398 | config.communityFund || contractOwner, 399 | config.communityFundNumerator || 50000 400 | ); 401 | actionCount++; 402 | } 403 | 404 | // Update darknode payment address 405 | if ( 406 | Ox(await darknodeRegistry.darknodePayment()) !== 407 | Ox(DarknodePayment.address) 408 | ) { 409 | deployer.logger.log("Updating DarknodeRegistry's darknode payment"); 410 | await darknodeRegistry.updateDarknodePayment(DarknodePayment.address); 411 | actionCount++; 412 | } 413 | 414 | const darknodePayment = await DarknodePayment.at(DarknodePayment.address); 415 | for (const tokenName of Object.keys(tokens)) { 416 | const tokenAddress = tokens[tokenName]; 417 | const registered = 418 | ( 419 | await darknodePayment.registeredTokenIndex(tokenAddress) 420 | ).toString() !== "0"; 421 | const pendingRegistration = await darknodePayment.tokenPendingRegistration( 422 | tokenAddress 423 | ); 424 | if (!registered && !pendingRegistration) { 425 | deployer.logger.log( 426 | `Registering token ${tokenName} in DarknodePayment` 427 | ); 428 | await darknodePayment.registerToken(tokenAddress); 429 | actionCount++; 430 | } 431 | } 432 | 433 | const dnrInDarknodePayment = await darknodePayment.darknodeRegistry(); 434 | if (Ox(dnrInDarknodePayment) !== Ox(darknodeRegistry.address)) { 435 | deployer.logger.log("DNP is still pointing to Forwarder."); 436 | 437 | // deployer.logger.log("Updating DNR in DNP"); 438 | // await darknodePayment.updateDarknodeRegistry(darknodeRegistry.address); 439 | // actionCount++; 440 | } 441 | 442 | const darknodePaymentStore = await DarknodePaymentStore.at( 443 | DarknodePaymentStore.address 444 | ); 445 | const currentOwner = await darknodePaymentStore.owner(); 446 | if (Ox(currentOwner) !== Ox(DarknodePayment.address)) { 447 | deployer.logger.log("Linking DarknodePaymentStore and DarknodePayment"); 448 | 449 | if (currentOwner === contractOwner) { 450 | await darknodePaymentStore.transferOwnership( 451 | DarknodePayment.address 452 | ); 453 | 454 | // Update DarknodePaymentStore address 455 | deployer.logger.log(`Claiming DNPS ownership in DNP`); 456 | await darknodePayment.claimStoreOwnership(); 457 | } else { 458 | deployer.logger.log( 459 | `Transferring DNPS ownership from ${currentOwner} to new DNP` 460 | ); 461 | const oldDarknodePayment = await DarknodePayment.at(currentOwner); 462 | await oldDarknodePayment.transferStoreOwnership( 463 | DarknodePayment.address 464 | ); 465 | // This will also call claim, but we try anyway because older 466 | // contracts didn't: 467 | try { 468 | // Claim ownership 469 | await darknodePayment.claimStoreOwnership(); 470 | } catch (error) { 471 | // Ignore 472 | } 473 | } 474 | actionCount++; 475 | } 476 | 477 | // if (changeCycle) { 478 | // try { 479 | // deployer.logger.log("Attempting to change cycle"); 480 | // await darknodePayment.changeCycle(); 481 | // } catch (error) { 482 | // deployer.logger.log("Unable to call darknodePayment.changeCycle()"); 483 | // } 484 | // } 485 | 486 | // Set the darknode payment cycle changer to the darknode registry 487 | if ( 488 | Ox(await darknodePayment.cycleChanger()) !== 489 | Ox(darknodeRegistry.address) 490 | ) { 491 | deployer.logger.log("Setting the DarknodePayment's cycle changer"); 492 | await darknodePayment.updateCycleChanger(darknodeRegistry.address); 493 | actionCount++; 494 | } 495 | 496 | deployer.logger.log(`Performed ${actionCount} updates.`); 497 | 498 | deployer.logger.log(` 499 | 500 | /* 1_darknodes.js */ 501 | 502 | RenProxyAdmin: "${RenProxyAdmin.address}", 503 | RenToken: "${RenToken.address}", 504 | 505 | // Protocol 506 | Protocol: "${Protocol.address}", 507 | 508 | // DNR 509 | DarknodeRegistryStore: "${DarknodeRegistryStore.address}", 510 | DarknodeRegistryLogicV1: "${DarknodeRegistryLogicV1.address}", 511 | DarknodeRegistryProxy: "${DarknodeRegistryProxy.address}", 512 | 513 | // DNP 514 | DarknodePaymentStore: "${DarknodePaymentStore.address}", 515 | DarknodePayment: "${DarknodePayment.address}", 516 | 517 | // Slasher 518 | DarknodeSlasher: "${DarknodeSlasher.address}", 519 | `); 520 | }; 521 | -------------------------------------------------------------------------------- /migrations/encode.js: -------------------------------------------------------------------------------- 1 | const encodeCallData = (web3, functioName, parameterTypes, parameters) => { 2 | return web3.eth.abi.encodeFunctionSignature(`${functioName}(${parameterTypes.join(",")})`) + 3 | web3.eth.abi.encodeParameters(parameterTypes, parameters).slice(2); 4 | }; 5 | 6 | module.exports = { 7 | encodeCallData, 8 | }; 9 | -------------------------------------------------------------------------------- /migrations/networks.js: -------------------------------------------------------------------------------- 1 | const BN = require("bn.js"); 2 | 3 | const config = { 4 | MINIMUM_BOND: new BN(100000).mul(new BN(10).pow(new BN(18))), 5 | MINIMUM_POD_SIZE: 3, // 24 in production 6 | MINIMUM_EPOCH_INTERVAL_SECONDS: 30, // 216000 in production, 1 month 7 | DARKNODE_PAYOUT_PERCENT: 50, // Only payout 50% of the reward pool 8 | BLACKLIST_SLASH_PERCENT: 0, // Don't slash bond for blacklisting 9 | MALICIOUS_SLASH_PERCENT: 50, // Slash 50% of the bond 10 | SECRET_REVEAL_SLASH_PERCENT: 100 // Slash 100% of the bond 11 | }; 12 | 13 | module.exports = { 14 | mainnet: { 15 | RenProxyAdmin: "0xDf1D8eD27C54bBE5833320cf5a19fd9E73530145", 16 | RenToken: "0x408e41876cCCDC0F92210600ef50372656052a38", 17 | DarknodeSlasher: "0x64512ff05a27756694E306e483cBB725F1754C0e", 18 | 19 | // Protocol 20 | ProtocolLogicV1: "0x8b49f212F2236F4f49bBeff878a73051a8915DE0", 21 | ProtocolProxy: "0xc25167fFa19B4d9d03c7d5aa4682c7063F345b66", 22 | 23 | // DNR 24 | DarknodeRegistryStore: "0x60Ab11FE605D2A2C3cf351824816772a131f8782", 25 | DarknodeRegistryLogicV1: "0x33b53A700de61b6be01d65A758b3635584bCF140", 26 | DarknodeRegistryProxy: "0x2D7b6C95aFeFFa50C068D50f89C5C0014e054f0A", 27 | 28 | // DNP 29 | DarknodePaymentStore: "0xE33417797d6b8Aec9171d0d6516E88002fbe23E7", 30 | DarknodePayment: "0x098e1708b920EFBdD7afe33Adb6a4CBa30c370B9", 31 | 32 | tokens: { 33 | DAI: "0x6b175474e89094c44da98b954eedeac495271d0f", 34 | ETH: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" 35 | }, 36 | 37 | config: { 38 | ...config 39 | } 40 | }, 41 | testnet: { 42 | RenProxyAdmin: "0x4C695C4Aa6238f0A7092733180328c2E64C912C7", 43 | RenToken: "0x2cd647668494c1b15743ab283a0f980d90a87394", 44 | DarknodeSlasher: "0x5B403bdC360A447290758c8BA779c44cdFC3476F", 45 | 46 | // Protocol 47 | ProtocolLogicV1: "0x43d39d7ea61741f26E09D377F4E79B1F847Dc356", 48 | ProtocolProxy: "0x59e23c087cA9bd9ce162875811CD6e99134D6d0F", 49 | 50 | // DNR 51 | DarknodeRegistryStore: "0x9daa16aA19e37f3de06197a8B5E638EC5e487392", 52 | DarknodeRegistryLogicV1: "0x046EDe9916e13De79d5530b67FF5dEbB7B72742C", 53 | DarknodeRegistryProxy: "0x9954C9F839b31E82bc9CA98F234313112D269712", 54 | 55 | // DNP 56 | DarknodePaymentStore: "0x0EC73cCDCd8e643d909D0c4b663Eb1B2Fb0b1e1C", 57 | DarknodePayment: "0x023f2e94C3eb128D3bFa6317a3fF860BF93C1616", 58 | 59 | tokens: { 60 | DAI: "0xc4375b7de8af5a38a93548eb8453a498222c4ff2", 61 | ETH: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" 62 | }, 63 | 64 | config: { 65 | ...config 66 | } 67 | }, 68 | 69 | devnet: { 70 | RenProxyAdmin: "0xA2C9D593bC096FbB3Cf5b869270645C470E5416B", 71 | RenToken: "0x2cd647668494c1b15743ab283a0f980d90a87394", 72 | DarknodeSlasher: "0xf4E4AdbDDfd6EBc9457ad7ab9249f63701942BE3", 73 | 74 | // Protocol 75 | ProtocolLogicV1: "0x4535CB2f0697e797C534cb0853F25470A9f59037", 76 | ProtocolProxy: "0x5045E727D9D9AcDe1F6DCae52B078EC30dC95455", 77 | 78 | // DNR 79 | DarknodeRegistryStore: "0x3ccF0cd02ff15b59Ce2B152CdDE78551eFd34a62", 80 | DarknodeRegistryLogicV1: "0x26D6fEC1C904EB5b86ACed6BB804b4ed35208704", 81 | DarknodeRegistryProxy: "0x7B69e5e15D4c24c353Fea56f72E4C0c5B93dCb71", 82 | 83 | // DNP 84 | DarknodePaymentStore: "0xfb98D6900330844CeAce6Ae4ae966D272bE1aeC3", 85 | DarknodePayment: "0xC7F24fEDfbbAA5248E1F5a160cC30Dcbff9F1176", 86 | 87 | tokens: { 88 | DAI: "0xc4375b7de8af5a38a93548eb8453a498222c4ff2", 89 | ETH: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" 90 | }, 91 | 92 | config: { 93 | ...config 94 | } 95 | }, 96 | 97 | config 98 | }; 99 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@renproject/sol", 3 | "version": "1.1.0", 4 | "repository": "https://github.io/renproject/darknode-sol", 5 | "public": true, 6 | "scripts": { 7 | "generate": "truffle compile && typechain --target truffle-v5 './build/development/*.json'", 8 | "coverage": "yarn run generate && truffle run coverage", 9 | "build": "yarn run generate && truffle build", 10 | "test": "yarn run generate && truffle test", 11 | "bindings:ts": "truffle compile && typechain --target web3-v1 \"./build/chaosnet/*.json\"", 12 | "bindings:go": "solc --optimize darknode-sol=`pwd`/node_modules/darknode-sol @openzeppelin/upgrades=`pwd`/node_modules/@openzeppelin/upgrades @openzeppelin/contracts=`pwd`/node_modules/@openzeppelin/contracts --combined-json bin,abi,userdoc,devdoc,metadata $(find contracts -type f -name '*.sol') > go-abi.json && abigen -pkg bindings --out bindings.go --combined-json go-abi.json; rm go-abi.json", 13 | "coveralls": "cat ./coverage/lcov.info | coveralls", 14 | "prepare": "patch-package", 15 | "deploy": "truffle migrate --network $NETWORK 2>&1 | tee $NETWORK-output.log", 16 | "deploy-skipDryRun": "truffle migrate --network $NETWORK --skipDryRun 2>&1 | tee $NETWORK-output.log", 17 | "clean-build": "echo \"Have all contracts been verified on Etherscan? Continuing in 10 seconds...\" && sleep 10 && grep -R -l 'networks\": {}' build --exclude-dir=development | xargs rm; node build/clean.js", 18 | "verify": "truffle run verify --network $NETWORK", 19 | "docs": "solidity-docgen --exclude contracts/migrations,contracts/test --solc-module solc --templates ./templates --helpers ./templates/helpers.js --extension=mdx", 20 | "prepare-release": "npmignore" 21 | }, 22 | "dependencies": { 23 | "@openzeppelin/contracts": "^2.5.1", 24 | "@openzeppelin/contracts-ethereum-package": "^2.5.0", 25 | "@openzeppelin/upgrades": "^2.8.0" 26 | }, 27 | "devDependencies": { 28 | "@typechain/truffle-v5": "^5.0.0", 29 | "@typechain/web3-v1": "^3.0.0", 30 | "@types/chai": "^4.2.21", 31 | "@types/chai-as-promised": "^7.1.4", 32 | "@types/ethereumjs-abi": "^0.6.3", 33 | "@types/mocha": "^9.0.0", 34 | "@types/node": "^16.7.1", 35 | "@types/seedrandom": "^3.0.1", 36 | "bignumber.js": "^9.0.1", 37 | "bn.js": "^5.2.0", 38 | "chai": "^4.3.4", 39 | "chai-as-promised": "^7.1.1", 40 | "chai-bignumber": "github:ren-forks/chai-bignumber.git#afa6f46dcbef0b7e622dc27b9b3354fc67afafbc", 41 | "coveralls": "^3.1.1", 42 | "dotenv": "^10.0.0", 43 | "eth-gas-reporter": "^0.2.22", 44 | "ethereumjs-abi": "^0.6.8", 45 | "ganache-cli": "^6.12.2", 46 | "hash.js": "^1.1.7", 47 | "immutable": "^4.0.0-rc.14", 48 | "moment": "^2.29.1", 49 | "npmignore": "^0.2.0", 50 | "patch-package": "^6.4.7", 51 | "seedrandom": "^3.0.5", 52 | "solc": "^0.5.17", 53 | "solidity-coverage": "^0.7.16", 54 | "truffle": "^5.4.7", 55 | "truffle-hdwallet-provider": "^1.0.17", 56 | "truffle-plugin-verify": "0.5.11", 57 | "truffle-typings": "^1.0.8", 58 | "ts-node": "^10.2.1", 59 | "tslint": "^6.1.3", 60 | "typechain": "^5.1.2", 61 | "typescript": "^4.3.5", 62 | "web3": "^2.0.0-alpha", 63 | "web3-core": "^1.5.2", 64 | "web3-utils": "^2.0.0-alpha" 65 | }, 66 | "resolutions": { 67 | "solc": "0.5.17", 68 | "sol-merger": "1.1.1" 69 | }, 70 | "prettier": { 71 | "tabWidth": 4 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /patches/@openzeppelin+contracts+2.5.1.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@openzeppelin/contracts/cryptography/ECDSA.sol b/node_modules/@openzeppelin/contracts/cryptography/ECDSA.sol 2 | index d85ce09..1d34ab2 100644 3 | --- a/node_modules/@openzeppelin/contracts/cryptography/ECDSA.sol 4 | +++ b/node_modules/@openzeppelin/contracts/cryptography/ECDSA.sol 5 | @@ -28,7 +28,7 @@ library ECDSA { 6 | function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { 7 | // Check the signature length 8 | if (signature.length != 65) { 9 | - return (address(0)); 10 | + revert("ECDSA: signature length is invalid"); 11 | } 12 | 13 | // Divide the signature in r, s and v variables 14 | @@ -55,11 +55,11 @@ library ECDSA { 15 | // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept 16 | // these malleable signatures as well. 17 | if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { 18 | - return address(0); 19 | + revert("ECDSA: signature.s is in the wrong range"); 20 | } 21 | 22 | if (v != 27 && v != 28) { 23 | - return address(0); 24 | + revert("ECDSA: signature.v is in the wrong range"); 25 | } 26 | 27 | // If the signature is valid (and not malleable), return the signer address 28 | -------------------------------------------------------------------------------- /patches/@openzeppelin+contracts-ethereum-package+2.5.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@openzeppelin/contracts-ethereum-package/contracts/cryptography/ECDSA.sol b/node_modules/@openzeppelin/contracts-ethereum-package/contracts/cryptography/ECDSA.sol 2 | index d85ce09..1d34ab2 100644 3 | --- a/node_modules/@openzeppelin/contracts-ethereum-package/contracts/cryptography/ECDSA.sol 4 | +++ b/node_modules/@openzeppelin/contracts-ethereum-package/contracts/cryptography/ECDSA.sol 5 | @@ -28,7 +28,7 @@ library ECDSA { 6 | function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { 7 | // Check the signature length 8 | if (signature.length != 65) { 9 | - return (address(0)); 10 | + revert("ECDSA: signature length is invalid"); 11 | } 12 | 13 | // Divide the signature in r, s and v variables 14 | @@ -55,11 +55,11 @@ library ECDSA { 15 | // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept 16 | // these malleable signatures as well. 17 | if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { 18 | - return address(0); 19 | + revert("ECDSA: signature.s is in the wrong range"); 20 | } 21 | 22 | if (v != 27 && v != 28) { 23 | - return address(0); 24 | + revert("ECDSA: signature.v is in the wrong range"); 25 | } 26 | 27 | // If the signature is valid (and not malleable), return the signer address 28 | -------------------------------------------------------------------------------- /templates/contract.hbs: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{name}} 3 | --- 4 | 5 | import { DocTag, DocType } from "/components/DocTag"; 6 | 7 | 8 | 9 | ## Contract: `{{name}}` 10 | 11 | {{{natspec.userdoc}}} 12 | 13 | {{{natspec.devdoc}}} 14 | 15 | {{#if (filterContracts inheritance name)}} 16 | Inherits from: 17 | 18 | {{#each (filterContracts inheritance name)}} 19 | - `{{name}}` 20 | {{/each}} 21 | 22 | {{/if}} 23 | 24 |
25 | 26 | {{#if ownVariables}} 27 | 28 |
29 | 30 | ## Variables 31 | 32 | {{#each ownVariables}} 33 | 34 | ### **`{{name}}`**: *`{{type}}`* 35 | 36 | {{visibility}} 37 | 38 | {{{natspec.userdoc}}} 39 | 40 | {{{natspec.devdoc}}} 41 | 42 |
43 | {{/each}} 44 | {{/if}} 45 | 46 | {{#if ownModifiers}} 47 | 48 |
49 | 50 | ## Modifiers 51 | 52 | {{#each ownModifiers}} 53 | ### `{{name}}({{args}})` 54 | 55 | {{{natspec.userdoc}}} 56 | 57 | {{{natspec.devdoc}}} 58 | 59 |
60 | 61 | {{/each}} 62 | {{/if}} 63 | 64 | {{#if ownFunctions}} 65 | 66 |
67 | 68 | ## Functions 69 | 70 | {{#each ownFunctions}} 71 | ### **`{{name}}`** *`({{args}}){{#if outputs}} → {{outputs}}{{/if}}`* ({{visibility}}) 72 | 73 | {{#if natspec.params}} 74 | **Params** 75 | {{#each natspec.params}} 76 | - `{{param}}`: {{description}} 77 | {{/each}} 78 | {{/if}} 79 | 80 | {{#if natspec.returns}} 81 | **Returns** 82 | {{#each natspec.returns}} 83 | - `{{param}}`: {{description}} 84 | {{/each}} 85 | {{/if}} 86 | 87 | {{{natspec.userdoc}}} 88 | 89 | {{{natspec.devdoc}}} 90 | 91 |
92 | 93 | {{/each}} 94 | {{/if}} 95 | 96 | {{#if ownEvents}} 97 | 98 |
99 | 100 | ## Events 101 | 102 | {{#each ownEvents}} 103 | ### **`{{name}}`** *`({{args}})`* 104 | 105 | {{{natspec.userdoc}}} 106 | 107 | {{#if natspec.devdoc}} 108 | *{{natspec.devdoc}}* 109 | {{/if}} 110 | 111 | {{#if natspec.params}} 112 | **Params** 113 | {{#each natspec.params}} 114 | - `{{param}}`: {{description}} 115 | {{/each}} 116 | {{/if}} 117 | 118 |
119 | 120 | {{/each}} 121 | {{/if}} -------------------------------------------------------------------------------- /templates/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | public(visibility) { 3 | return visibility === "public"; 4 | }, 5 | 6 | hasPublic(children) { 7 | return children.visibility === "public"; 8 | }, 9 | 10 | filterContracts(list, item) { 11 | return list.filter(i => i.name !== item); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /templates/prelude.sample.hbs: -------------------------------------------------------------------------------- 1 | {{#links}} 2 | [{{target.anchor}}]: {{relativePath}}#{{target.anchor}} 3 | {{/links}} -------------------------------------------------------------------------------- /test/ClaimlessRewards/steps.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | import { OrderedMap } from "immutable"; 3 | import moment from "moment"; 4 | import { 5 | ClaimlessRewardsInstance, 6 | DarknodePaymentInstance, 7 | DarknodePaymentStoreContract, 8 | DarknodeRegistryLogicV1Contract 9 | } from "../../types/truffle-contracts"; 10 | import { 11 | ETHEREUM, 12 | getBalance, 13 | getDecimals, 14 | getSymbol, 15 | HOURS, 16 | increaseTime, 17 | NULL, 18 | toBN, 19 | transferToken, 20 | waitForEpoch 21 | } from "../helper/testUtils"; 22 | 23 | const DarknodePaymentStore: DarknodePaymentStoreContract = artifacts.require( 24 | "DarknodePaymentStore" 25 | ); 26 | const DarknodeRegistry: DarknodeRegistryLogicV1Contract = artifacts.require( 27 | "DarknodeRegistryLogicV1" 28 | ); 29 | 30 | const registerToken = async ( 31 | rewards: ClaimlessRewardsInstance, 32 | tokens: string | string[] 33 | ) => { 34 | tokens = Array.isArray(tokens) ? tokens : [tokens]; 35 | 36 | for (const token of tokens) { 37 | // Precondition. The token is not registered. 38 | (await rewards.isRegistered(token)).should.equal(false); 39 | const allTokens = await rewards.getRegisteredTokens(); 40 | 41 | // Effect. Register the token. 42 | await rewards.registerToken(token); 43 | 44 | // Postcondition. The token is registered. 45 | (await rewards.isRegistered(token)).should.equal(true); 46 | (await rewards.getRegisteredTokens()).should.deep.equal([ 47 | ...allTokens, 48 | token 49 | ]); 50 | } 51 | }; 52 | 53 | const deregisterToken = async ( 54 | rewards: ClaimlessRewardsInstance, 55 | tokens: string | string[] 56 | ) => { 57 | tokens = Array.isArray(tokens) ? tokens : [tokens]; 58 | 59 | for (const token of tokens) { 60 | // Precondition. The token is registered. 61 | (await rewards.isRegistered(token)).should.equal(true); 62 | const allTokens = await rewards.getRegisteredTokens(); 63 | 64 | // Effect. Deregister the token. 65 | await rewards.deregisterToken(token); 66 | 67 | // Postcondition. The token is not registered. 68 | (await rewards.isRegistered(token)).should.equal(false); 69 | (await rewards.getRegisteredTokens()).should.deep.equal( 70 | allTokens.filter(x => x !== token) 71 | ); 72 | } 73 | }; 74 | 75 | const changeCycle = async ( 76 | rewards: ClaimlessRewardsInstance, 77 | time: number, 78 | epoch?: boolean 79 | ) => { 80 | const latestTimestamp = await toBN(rewards.latestCycleTimestamp()); 81 | const storeAddress = await rewards.store(); 82 | const store = await DarknodePaymentStore.at(storeAddress); 83 | const dnrAddress = await rewards.darknodeRegistry(); 84 | const dnr = await DarknodeRegistry.at(dnrAddress); 85 | const communityFund = await rewards.communityFund(); 86 | 87 | const tokens = await rewards.getRegisteredTokens(); 88 | let freeBeforeMap = OrderedMap(); 89 | let communityFundBalanceBeforeMap = OrderedMap(); 90 | let darknodePoolBeforeMap = OrderedMap(); 91 | let shareBeforeMap = OrderedMap(); 92 | for (const token of tokens) { 93 | const freeBefore = await toBN(store.availableBalance(token)); 94 | freeBeforeMap = freeBeforeMap.set(token, freeBefore); 95 | 96 | const communityFundBalanceBefore = await toBN( 97 | rewards.darknodeBalances(communityFund, token) 98 | ); 99 | communityFundBalanceBeforeMap = communityFundBalanceBeforeMap.set( 100 | token, 101 | communityFundBalanceBefore 102 | ); 103 | const darknodePoolBefore = await toBN( 104 | rewards.darknodeBalances(NULL, token) 105 | ); 106 | darknodePoolBeforeMap = darknodePoolBeforeMap.set( 107 | token, 108 | darknodePoolBefore 109 | ); 110 | const shareBefore = await toBN( 111 | rewards.cycleCumulativeTokenShares(latestTimestamp.toFixed(), token) 112 | ); 113 | shareBeforeMap = shareBeforeMap.set(token, shareBefore); 114 | } 115 | const shares = await toBN(dnr.numDarknodes()); 116 | const epochTimestampCountBefore = await toBN( 117 | rewards.epochTimestampsLength() 118 | ); 119 | 120 | // Effect. Change the cycle. 121 | let tx; 122 | if (epoch) { 123 | tx = await waitForEpoch(dnr); 124 | } else { 125 | await increaseTime(time); 126 | tx = await rewards.changeCycle(); 127 | } 128 | 129 | // Postcondition. Check that the cycle's timestamp is stored correctly. 130 | const block = await web3.eth.getBlock(tx.receipt.blockNumber); 131 | const timestamp = new BigNumber(block.timestamp); 132 | const newLatestTimestamp = await toBN(rewards.latestCycleTimestamp()); 133 | // Check if the epoch happened too recently to a cycle, so no cycle was 134 | // called. 135 | const expectedTimestamp = epoch 136 | ? timestamp 137 | : latestTimestamp.plus( 138 | timestamp 139 | .minus(latestTimestamp) 140 | .minus(timestamp.minus(latestTimestamp).mod(1 * HOURS)) 141 | ); 142 | newLatestTimestamp.should.not.bignumber.equal(latestTimestamp); 143 | newLatestTimestamp.should.bignumber.equal(expectedTimestamp); 144 | const epochTimestampCountAfter = await toBN( 145 | rewards.epochTimestampsLength() 146 | ); 147 | if (epoch) { 148 | epochTimestampCountAfter.should.bignumber.equal( 149 | epochTimestampCountBefore.plus(1) 150 | ); 151 | } 152 | 153 | // Postcondition. Check conditions for each token. 154 | const hours = timestamp 155 | .minus(latestTimestamp) 156 | .dividedToIntegerBy(1 * HOURS) 157 | .toNumber(); 158 | const numerator = await toBN(rewards.hourlyPayoutWithheldNumerator()); 159 | const denominator = await toBN( 160 | rewards.HOURLY_PAYOUT_WITHHELD_DENOMINATOR() 161 | ); 162 | let numeratorSeries = numerator; 163 | for (let i = 0; i < hours; i++) { 164 | numeratorSeries = numeratorSeries 165 | .times(numerator) 166 | .div(denominator) 167 | .integerValue(BigNumber.ROUND_DOWN); 168 | } 169 | const communityFundNumerator = await toBN(rewards.communityFundNumerator()); 170 | 171 | for (const token of tokens) { 172 | const freeBefore = freeBeforeMap.get(token); 173 | const communityFundBalanceBefore = communityFundBalanceBeforeMap.get( 174 | token 175 | ); 176 | const darknodePoolBefore = darknodePoolBeforeMap.get(token); 177 | const shareBefore = shareBeforeMap.get(token); 178 | 179 | const totalWithheld = freeBefore 180 | .times(numeratorSeries) 181 | .div(denominator) 182 | .integerValue(BigNumber.ROUND_DOWN); 183 | 184 | const totalPaidout = freeBefore.minus(totalWithheld); 185 | const communityFundPaidout = totalPaidout 186 | .times(communityFundNumerator) 187 | .div(denominator) 188 | .integerValue(BigNumber.ROUND_DOWN); 189 | 190 | const darknodePaidout = totalPaidout.minus(communityFundPaidout); 191 | const share = shares.isZero() 192 | ? new BigNumber(0) 193 | : darknodePaidout.div(shares).integerValue(BigNumber.ROUND_DOWN); 194 | 195 | const darknodePaidoutAdjusted = share.times(shares); 196 | 197 | // Postcondition. The stored share is the correct amount. 198 | const shareAfter = await toBN( 199 | rewards.cycleCumulativeTokenShares( 200 | newLatestTimestamp.toFixed(), 201 | token 202 | ) 203 | ); 204 | 205 | shareAfter.minus(shareBefore).should.bignumber.equal(share); 206 | 207 | // Postcondition. The darknode pool increased by the correct amount. 208 | const darknodePoolAfter = await toBN( 209 | rewards.darknodeBalances(NULL, token) 210 | ); 211 | darknodePoolAfter 212 | .minus(darknodePoolBefore) 213 | .should.bignumber.equal(darknodePaidoutAdjusted); 214 | 215 | // Postcondition. The community fund increased by the correct amount. 216 | const communityFundBalanceAfter = await toBN( 217 | rewards.darknodeBalances(communityFund, token) 218 | ); 219 | communityFundBalanceAfter 220 | .minus(communityFundBalanceBefore) 221 | .should.bignumber.equal(communityFundPaidout); 222 | 223 | // Postcondition. The free amount decreased by the correct amount. 224 | const freeAfter = await toBN(store.availableBalance(token)); 225 | freeBefore 226 | .minus(freeAfter) 227 | .should.bignumber.equal( 228 | communityFundPaidout.plus(darknodePaidoutAdjusted) 229 | ); 230 | } 231 | 232 | console.log( 233 | `New cycle after ${moment 234 | .duration(newLatestTimestamp.minus(latestTimestamp).times(1000)) 235 | .humanize()}.` 236 | ); 237 | 238 | return hours; 239 | }; 240 | 241 | const _waitForEpoch = async (rewards: ClaimlessRewardsInstance) => { 242 | await changeCycle(rewards, 0, true); 243 | }; 244 | 245 | const addRewards = async ( 246 | rewards: ClaimlessRewardsInstance | DarknodePaymentInstance, 247 | token: string, 248 | amount: BigNumber | number | string | BN 249 | ) => { 250 | const storeAddress = await rewards.store(); 251 | const balanceBefore = await getBalance(token, storeAddress); 252 | const store = await DarknodePaymentStore.at(storeAddress); 253 | const freeBefore = await toBN(store.availableBalance(token)); 254 | 255 | // Effect. Transfer token to the store contract. 256 | await transferToken(token, storeAddress, amount); 257 | 258 | // Postcondition. The balance after has increased by the amount added. 259 | const balanceAfter = await getBalance(token, storeAddress); 260 | balanceAfter.minus(balanceBefore).should.bignumber.equal(amount); 261 | const freeAfter = await toBN(store.availableBalance(token)); 262 | freeAfter.minus(freeBefore).should.bignumber.equal(amount); 263 | 264 | console.log( 265 | `There are now ${new BigNumber(freeAfter.toString()) 266 | .div(new BigNumber(10).exponentiatedBy(await getDecimals(token))) 267 | .toFixed()} ${await getSymbol(token)} in rewards` 268 | ); 269 | }; 270 | 271 | const withdraw = async ( 272 | rewards: ClaimlessRewardsInstance, 273 | darknodes: string | string[], 274 | tokens: string | string[], 275 | from?: string 276 | ) => { 277 | tokens = Array.isArray(tokens) ? tokens : [tokens]; 278 | darknodes = Array.isArray(darknodes) ? darknodes : [darknodes]; 279 | from = from || darknodes[0]; 280 | 281 | // Store the balance for each token, and the withdrawable amount for each 282 | // darknode and token. 283 | let withdrawableMap = OrderedMap>(); 284 | let balanceBeforeMap = OrderedMap(); 285 | // let legacyBalanceMap = OrderedMap>(); 286 | // let shareBeforeMap = OrderedMap>(); 287 | const storeAddress = await rewards.store(); 288 | const store = await DarknodePaymentStore.at(storeAddress); 289 | const currentCycle = await toBN(rewards.latestCycleTimestamp()); 290 | const dnrAddress = await rewards.darknodeRegistry(); 291 | const dnr = await DarknodeRegistry.at(dnrAddress); 292 | for (const token of tokens) { 293 | const balanceBefore = await getBalance(token, from); 294 | balanceBeforeMap = balanceBeforeMap.set(token, balanceBefore); 295 | 296 | for (const darknode of darknodes) { 297 | const withdrawable = await toBN( 298 | rewards.darknodeBalances(darknode, token) 299 | ); 300 | withdrawableMap = withdrawableMap.set( 301 | darknode, 302 | ( 303 | withdrawableMap.get(darknode) || 304 | OrderedMap() 305 | ).set(token, withdrawable) 306 | ); 307 | 308 | // Precondition. The withdrawable amount should be the correct 309 | // amount, including any legacy balance left-over. 310 | const nodeRegistered = await toBN( 311 | dnr.darknodeRegisteredAt(darknode) 312 | ); 313 | const nodeDeregistered = await toBN( 314 | dnr.darknodeDeregisteredAt(darknode) 315 | ); 316 | // Node not registered. 317 | if (nodeRegistered.isZero()) { 318 | continue; 319 | } 320 | const legacyBalance = await toBN( 321 | store.darknodeBalances(darknode, token) 322 | ); 323 | let lastWithdrawn = await toBN( 324 | rewards.rewardsLastClaimed(darknode, token) 325 | ); 326 | if (lastWithdrawn.lt(nodeRegistered)) { 327 | lastWithdrawn = await toBN( 328 | rewards.getNextEpochFromTimestamp(nodeRegistered.toFixed()) 329 | ); 330 | } 331 | let claimableUntil = currentCycle; 332 | if (nodeDeregistered.isGreaterThan(0)) { 333 | const deregisteredCycle = await toBN( 334 | rewards.getNextEpochFromTimestamp( 335 | nodeDeregistered.toFixed() 336 | ) 337 | ); 338 | if (deregisteredCycle.isGreaterThan(0)) { 339 | claimableUntil = deregisteredCycle; 340 | } 341 | } 342 | const shareBefore = await toBN( 343 | rewards.cycleCumulativeTokenShares( 344 | lastWithdrawn.toFixed(), 345 | token 346 | ) 347 | ); 348 | const shareAfter = await toBN( 349 | rewards.cycleCumulativeTokenShares( 350 | claimableUntil.toFixed(), 351 | token 352 | ) 353 | ); 354 | withdrawable 355 | .minus(legacyBalance) 356 | .should.bignumber.equal(shareAfter.minus(shareBefore)); 357 | } 358 | } 359 | 360 | // Effect. 361 | let tx; 362 | if (tokens.length !== 1) { 363 | tx = await rewards.withdrawMultiple(darknodes, tokens, { from }); 364 | } else if (darknodes.length !== 1) { 365 | tx = await rewards.withdrawToken(darknodes, tokens[0], { from }); 366 | } else { 367 | tx = await rewards.withdraw(darknodes[0], tokens[0], { from }); 368 | } 369 | 370 | // Postcondition. Check conditions for each token and darknode. 371 | for (const token of tokens) { 372 | const balanceBefore = balanceBeforeMap.get(token); 373 | 374 | let withdrawableSum = new BigNumber(0); 375 | for (const darknode of darknodes) { 376 | const withdrawable = withdrawableMap.get(darknode).get(token); 377 | withdrawableSum = withdrawableSum.plus(withdrawable); 378 | 379 | const postWithdrawable = await toBN( 380 | rewards.darknodeBalances(darknode, token) 381 | ); 382 | postWithdrawable.should.bignumber.equal(0); 383 | 384 | console.log( 385 | `${darknode.slice(0, 8)}... withdrew ${withdrawable 386 | .div( 387 | new BigNumber(10).exponentiatedBy( 388 | await getDecimals(token) 389 | ) 390 | ) 391 | .toFixed()} ${await getSymbol(token)}` 392 | ); 393 | } 394 | 395 | // Postcondition. The token balance of the user withdrawing increased 396 | // by the expected amount. 397 | const transactionDetails = await web3.eth.getTransaction(tx.tx); 398 | let gasFee = new BigNumber(0); 399 | if (token === ETHEREUM) { 400 | const { gasPrice } = transactionDetails; 401 | const { gasUsed } = tx.receipt; 402 | gasFee = new BigNumber(gasUsed).times(gasPrice); 403 | } 404 | (await getBalance(token, from)).should.bignumber.equal( 405 | balanceBefore.plus(withdrawableSum).minus(gasFee) 406 | ); 407 | } 408 | 409 | if (darknodes.length && tokens.length) { 410 | return withdrawableMap.get(darknodes[0]).get(tokens[0]); 411 | } else { 412 | return new BigNumber(0); 413 | } 414 | }; 415 | 416 | const withdrawToCommunityFund = async ( 417 | rewards: ClaimlessRewardsInstance, 418 | tokens: string | string[], 419 | from?: string 420 | ) => { 421 | from = from || (await web3.eth.getAccounts())[0]; 422 | tokens = Array.isArray(tokens) ? tokens : [tokens]; 423 | 424 | // Store the balance for each token, and the withdrawable amount for each 425 | // darknode and token. 426 | const communityFund = await rewards.communityFund(); 427 | let withdrawableMap = OrderedMap(); 428 | let balanceBeforeMap = OrderedMap(); 429 | for (const token of tokens) { 430 | const balanceBefore = await getBalance(token, communityFund); 431 | balanceBeforeMap = balanceBeforeMap.set(token, balanceBefore); 432 | 433 | const withdrawable = await toBN( 434 | rewards.darknodeBalances(communityFund, token) 435 | ); 436 | withdrawableMap = withdrawableMap.set(token, withdrawable); 437 | } 438 | 439 | // Effect. 440 | const tx = await rewards.withdrawToCommunityFund(tokens); 441 | 442 | // Postcondition. Check conditions for each token and darknode. 443 | for (const token of tokens) { 444 | const balanceBefore = balanceBeforeMap.get(token); 445 | const withdrawableBefore = withdrawableMap.get(token); 446 | 447 | console.log( 448 | `Withdrew ${withdrawableBefore 449 | .div( 450 | new BigNumber(10).exponentiatedBy(await getDecimals(token)) 451 | ) 452 | .toFixed()} ${await getSymbol(token)} to the community fund.` 453 | ); 454 | 455 | // Postcondition. The token balance of the user withdrawing increased 456 | // by the expected amount. 457 | const transactionDetails = await web3.eth.getTransaction(tx.tx); 458 | let gasFee = new BigNumber(0); 459 | if (token === ETHEREUM && from === communityFund) { 460 | const { gasPrice } = transactionDetails; 461 | const { gasUsed } = tx.receipt; 462 | gasFee = new BigNumber(gasUsed).times(gasPrice); 463 | } 464 | (await getBalance(token, communityFund)).should.bignumber.equal( 465 | balanceBefore.plus(withdrawableBefore).minus(gasFee) 466 | ); 467 | ( 468 | await toBN(rewards.darknodeBalances(communityFund, token)) 469 | ).should.bignumber.equal(0); 470 | } 471 | 472 | if (tokens.length) { 473 | return withdrawableMap.get(tokens[0]); 474 | } else { 475 | return new BigNumber(0); 476 | } 477 | }; 478 | 479 | export const STEPS = { 480 | registerToken, 481 | deregisterToken, 482 | changeCycle, 483 | addRewards, 484 | withdraw, 485 | withdrawToCommunityFund, 486 | waitForEpoch: _waitForEpoch 487 | }; 488 | -------------------------------------------------------------------------------- /test/Compare.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | 3 | import { CompareTestInstance } from "../types/truffle-contracts"; 4 | 5 | const CompareTest = artifacts.require("CompareTest"); 6 | 7 | contract("Compare", accounts => { 8 | let CompareInstance: CompareTestInstance; 9 | 10 | before(async () => { 11 | CompareInstance = await CompareTest.new(); 12 | }); 13 | 14 | describe("when bytes are the same length", async () => { 15 | it("should return false when content is different", async () => { 16 | const blockhash1 = "XTsJ2rO2yD47tg3JfmakVRXLzeou4SMtZvsMc6lkr6o"; 17 | const blockhash2 = "sdkjfhaefhefjhskjefjhefhjksefkehjfsjc6lkr6o"; 18 | const hexBlockhash1 = web3.utils.asciiToHex(blockhash1); 19 | const hexBlockhash2 = web3.utils.asciiToHex(blockhash2); 20 | expect( 21 | await CompareInstance.bytesEqual(hexBlockhash1, hexBlockhash2) 22 | ).to.be.false; 23 | }); 24 | 25 | it("should return true when content is the same", async () => { 26 | const blockhash1 = "XTsJ2rO2yD47tg3JfmakVRXLzeou4SMtZvsMc6lkr6o"; 27 | const hexBlockhash1 = web3.utils.asciiToHex(blockhash1); 28 | expect( 29 | await CompareInstance.bytesEqual(hexBlockhash1, hexBlockhash1) 30 | ).to.be.true; 31 | const hexBlockhash2 = web3.utils.asciiToHex("abcdefghijk"); 32 | expect( 33 | await CompareInstance.bytesEqual(hexBlockhash2, hexBlockhash2) 34 | ).to.be.true; 35 | const hexBlockhash3 = web3.utils.asciiToHex( 36 | "hukrasefaakuflehlafsefhuha2h293f8" 37 | ); 38 | expect( 39 | await CompareInstance.bytesEqual(hexBlockhash3, hexBlockhash3) 40 | ).to.be.true; 41 | }); 42 | }); 43 | 44 | describe("when bytes are of different length", async () => { 45 | it("should return false", async () => { 46 | const blockhash1 = "XTsJ2rO2yD47tg3J"; 47 | const blockhash2 = "sdkjfhaefhefjhskjefjhefhjksefkehjfsjc6lkr6o"; 48 | const hexBlockhash1 = web3.utils.asciiToHex(blockhash1); 49 | const hexBlockhash2 = web3.utils.asciiToHex(blockhash2); 50 | expect( 51 | await CompareInstance.bytesEqual(hexBlockhash1, hexBlockhash2) 52 | ).to.be.false; 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/ERC20WithFees.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | 3 | import { 4 | ERC20WithFeesTestInstance, 5 | ReturnsFalseTokenInstance 6 | } from "../types/truffle-contracts"; 7 | import "./helper/testUtils"; 8 | 9 | const ERC20WithFeesTest = artifacts.require("ERC20WithFeesTest"); 10 | const NormalToken = artifacts.require("NormalToken"); 11 | const ReturnsFalseToken = artifacts.require("ReturnsFalseToken"); 12 | const NonCompliantToken = artifacts.require("NonCompliantToken"); 13 | const TokenWithFees = artifacts.require("TokenWithFees"); 14 | 15 | contract("ERC20WithFees", accounts => { 16 | let mock: ERC20WithFeesTestInstance; 17 | 18 | before(async () => { 19 | mock = await ERC20WithFeesTest.new(); 20 | }); 21 | 22 | const testCases = [ 23 | { 24 | contract: NormalToken, 25 | fees: 0, 26 | desc: "standard token [true for success, throws for failure]" 27 | }, 28 | { 29 | contract: ReturnsFalseToken, 30 | fees: 0, 31 | desc: "alternate token [true for success, false for failure]" 32 | }, 33 | { 34 | contract: NonCompliantToken, 35 | fees: 0, 36 | desc: "non compliant token [nil for success, throws for failure]" 37 | }, 38 | { 39 | contract: TokenWithFees, 40 | fees: 3, 41 | desc: "token with fees [true for success, throws for failure]" 42 | } 43 | ]; 44 | 45 | const VALUE = new BN(100000000000000); 46 | 47 | for (const testCase of testCases) { 48 | context(testCase.desc, async () => { 49 | let token: ReturnsFalseTokenInstance; 50 | const FEE = VALUE.mul(new BN(testCase.fees)).div(new BN(1000)); 51 | 52 | before(async () => { 53 | token = (await testCase.contract.new()) as ReturnsFalseTokenInstance; 54 | }); 55 | 56 | it("approve and transferFrom", async () => { 57 | // Get balances before depositing 58 | const before = new BN(await token.balanceOf(accounts[0])); 59 | const after = new BN(await token.balanceOf(mock.address)); 60 | 61 | // Approve and deposit 62 | await token.approve(mock.address, VALUE); 63 | await mock.deposit(token.address, VALUE); 64 | 65 | // Compare balances after depositing 66 | (await token.balanceOf(accounts[0])).should.bignumber.equal( 67 | before.sub(new BN(VALUE)) 68 | ); 69 | (await token.balanceOf(mock.address)).should.bignumber.equal( 70 | after.add(new BN(VALUE.sub(FEE))) 71 | ); 72 | }); 73 | 74 | it("transfer", async () => { 75 | // Get balances before depositing 76 | const before = new BN(await token.balanceOf(accounts[0])); 77 | const after = new BN(await token.balanceOf(mock.address)); 78 | 79 | const NEW_VALUE = VALUE.sub(FEE); 80 | const NEW_FEE = NEW_VALUE.mul(new BN(testCase.fees)).div( 81 | new BN(1000) 82 | ); 83 | 84 | // Withdraw 85 | await mock.withdraw(token.address, NEW_VALUE); 86 | 87 | // Compare balances after depositing 88 | (await token.balanceOf(accounts[0])).should.bignumber.equal( 89 | before.add(new BN(NEW_VALUE.sub(NEW_FEE))) 90 | ); 91 | (await token.balanceOf(mock.address)).should.bignumber.equal( 92 | after.sub(new BN(NEW_VALUE)) 93 | ); 94 | }); 95 | 96 | it("throws for invalid transferFrom", async () => { 97 | // Get balances before depositing 98 | const before = new BN(await token.balanceOf(accounts[0])); 99 | const after = new BN(await token.balanceOf(mock.address)); 100 | 101 | // Approve and deposit 102 | await token.approve(mock.address, 0); 103 | await mock 104 | .naiveDeposit(token.address, VALUE) 105 | .should.be.rejectedWith( 106 | /SafeERC20: (ERC20 operation did not succeed)|(low-level call failed)/ 107 | ); 108 | 109 | // Compare balances after depositing 110 | (await token.balanceOf(accounts[0])).should.bignumber.equal( 111 | before 112 | ); 113 | (await token.balanceOf(mock.address)).should.bignumber.equal( 114 | after 115 | ); 116 | }); 117 | 118 | it("throws for invalid transferFrom (with fee)", async () => { 119 | // Get balances before depositing 120 | const before = new BN(await token.balanceOf(accounts[0])); 121 | const after = new BN(await token.balanceOf(mock.address)); 122 | 123 | // Approve and deposit 124 | await token.approve(mock.address, 0); 125 | await mock 126 | .deposit(token.address, VALUE) 127 | .should.be.rejectedWith( 128 | /SafeERC20: (ERC20 operation did not succeed)|(low-level call failed)/ 129 | ); 130 | 131 | // Compare balances after depositing 132 | (await token.balanceOf(accounts[0])).should.bignumber.equal( 133 | before 134 | ); 135 | (await token.balanceOf(mock.address)).should.bignumber.equal( 136 | after 137 | ); 138 | }); 139 | 140 | it("throws for invalid transfer", async () => { 141 | // Get balances before depositing 142 | const before = new BN(await token.balanceOf(accounts[0])); 143 | const after = new BN(await token.balanceOf(mock.address)); 144 | 145 | // Withdraw 146 | await mock 147 | .withdraw(token.address, VALUE.mul(new BN(2))) 148 | .should.be.rejectedWith( 149 | /SafeERC20: (ERC20 operation did not succeed)|(low-level call failed)/ 150 | ); 151 | 152 | // Compare balances after depositing 153 | (await token.balanceOf(accounts[0])).should.bignumber.equal( 154 | before 155 | ); 156 | (await token.balanceOf(mock.address)).should.bignumber.equal( 157 | after 158 | ); 159 | }); 160 | 161 | it("throws for invalid approve", async () => { 162 | // Transfer to the contract 163 | await token.transfer(mock.address, VALUE); 164 | 165 | // Subtract fees 166 | const NEW_VALUE = VALUE.sub(FEE); 167 | const NEW_FEE = NEW_VALUE.mul(new BN(testCase.fees)).div( 168 | new BN(1000) 169 | ); 170 | 171 | // Get balances before transferring back 172 | const before = new BN(await token.balanceOf(accounts[0])); 173 | const after = new BN(await token.balanceOf(mock.address)); 174 | 175 | // Approve twice without resetting allowance 176 | await mock.approve(token.address, NEW_VALUE); 177 | await mock 178 | .approve(token.address, NEW_VALUE) 179 | .should.be.rejectedWith( 180 | /SafeERC20: approve from non-zero to non-zero allowance/ 181 | ); 182 | 183 | // Can transfer from the contract 184 | await token.transferFrom( 185 | mock.address, 186 | accounts[0], 187 | NEW_VALUE.sub(NEW_FEE) 188 | ); 189 | 190 | // Subtract fees second time 191 | const NEW_NEW_VALUE = NEW_VALUE.sub(NEW_FEE); 192 | const NEW_NEW_FEE = NEW_NEW_VALUE.mul( 193 | new BN(testCase.fees) 194 | ).div(new BN(1000)); 195 | 196 | // Compare balances after depositing 197 | (await token.balanceOf(accounts[0])).should.bignumber.equal( 198 | before.add(new BN(NEW_NEW_VALUE.sub(NEW_NEW_FEE))) 199 | ); 200 | (await token.balanceOf(mock.address)).should.bignumber.equal( 201 | after.sub(new BN(NEW_NEW_VALUE)) 202 | ); 203 | }); 204 | 205 | it("throws for naive deposit if it has fees", async () => { 206 | // Get balances before depositing 207 | const before = new BN(await token.balanceOf(accounts[0])); 208 | const after = new BN(await token.balanceOf(mock.address)); 209 | 210 | // Approve and deposit 211 | await token.approve(mock.address, VALUE); 212 | if (testCase.fees) { 213 | await mock 214 | .naiveDeposit(token.address, VALUE) 215 | .should.be.rejectedWith( 216 | /ERC20WithFeesTest: incorrect balance in deposit/ 217 | ); 218 | await token.approve(mock.address, 0); 219 | } else { 220 | await mock.naiveDeposit(token.address, VALUE); 221 | 222 | // Compare balances after depositing 223 | (await token.balanceOf(accounts[0])).should.bignumber.equal( 224 | before.sub(new BN(VALUE.sub(FEE))) 225 | ); 226 | ( 227 | await token.balanceOf(mock.address) 228 | ).should.bignumber.equal(after.add(new BN(VALUE))); 229 | } 230 | }); 231 | }); 232 | } 233 | }); 234 | -------------------------------------------------------------------------------- /test/LinkedList.ts: -------------------------------------------------------------------------------- 1 | import { LinkedListTestInstance } from "../types/truffle-contracts"; 2 | import { ID, NULL } from "./helper/testUtils"; 3 | 4 | const LinkedListTest = artifacts.require("LinkedListTest"); 5 | 6 | contract("LinkedList", () => { 7 | let linkedList: LinkedListTestInstance; 8 | 9 | const [NODE1, NODE2, NODE3, NODE4, NOT_NODE1, NOT_NODE2] = [ 10 | ID("1"), 11 | ID("2"), 12 | ID("3"), 13 | ID("4"), 14 | ID("NOT1"), 15 | ID("NOT2") 16 | ]; 17 | 18 | before(async () => { 19 | linkedList = await LinkedListTest.new(); 20 | }); 21 | 22 | it("can append", async () => { 23 | await linkedList.append(NODE1); 24 | (await linkedList.isInList(NODE1)).should.equal(true); 25 | }); 26 | 27 | it("can prepend", async () => { 28 | await linkedList.prepend(NODE2); 29 | (await linkedList.previous(NODE1)).should.equal(NODE2); 30 | }); 31 | 32 | it("can swap", async () => { 33 | await linkedList.swap(NODE1, NODE2); 34 | (await linkedList.previous(NODE2)).should.equal(NODE1); 35 | }); 36 | 37 | it("can insertAfter", async () => { 38 | await linkedList.insertAfter(NODE2, NODE4); 39 | (await linkedList.next(NODE2)).should.equal(NODE4); 40 | }); 41 | 42 | it("can insertBefore", async () => { 43 | await linkedList.insertBefore(NODE4, NODE3); 44 | (await linkedList.previous(NODE4)).should.equal(NODE3); 45 | }); 46 | 47 | it("can remove", async () => { 48 | await linkedList.remove(NODE4); 49 | (await linkedList.isInList(NODE4)).should.equal(false); 50 | }); 51 | 52 | it("can get previous node of the given node", async () => { 53 | (await linkedList.previous(NODE2)).should.equal(NODE1); 54 | }); 55 | 56 | it("can get following node of the given node", async () => { 57 | (await linkedList.next(NODE1)).should.equal(NODE2); 58 | }); 59 | 60 | it("can get the last node of the given list", async () => { 61 | (await linkedList.end()).should.equal(NODE3); 62 | }); 63 | 64 | it("can get the first node of the given list", async () => { 65 | (await linkedList.begin()).should.equal(NODE1); 66 | }); 67 | 68 | it("should not add NULL", async () => { 69 | await linkedList 70 | .insertBefore(NODE1, NULL) 71 | .should.be.rejectedWith(/LinkedList: invalid address/); 72 | await linkedList 73 | .insertAfter(NODE1, NULL) 74 | .should.be.rejectedWith(/LinkedList: invalid address/); 75 | await linkedList 76 | .append(NULL) 77 | .should.be.rejectedWith(/LinkedList: invalid address/); 78 | await linkedList 79 | .remove(NULL) 80 | .should.be.rejectedWith(/LinkedList: not in list/); 81 | }); 82 | 83 | it("should not add the same value more than once", async () => { 84 | await linkedList 85 | .append(NODE1) 86 | .should.be.rejectedWith(/LinkedList: already in list/); 87 | }); 88 | 89 | it("should not remove a node not in the list", async () => { 90 | await linkedList 91 | .remove(NOT_NODE1) 92 | .should.be.rejectedWith(/LinkedList: not in list/); 93 | }); 94 | 95 | it("should not insert after a node not in the list", async () => { 96 | await linkedList 97 | .insertAfter(NOT_NODE1, NOT_NODE2) 98 | .should.be.rejectedWith(/LinkedList: not in list/); 99 | }); 100 | 101 | it("should not insert before a node not in the list", async () => { 102 | await linkedList 103 | .insertBefore(NOT_NODE1, NOT_NODE2) 104 | .should.be.rejectedWith(/LinkedList: not in list/); 105 | }); 106 | 107 | it("should not insert a node already in the list", async () => { 108 | await linkedList 109 | .insertAfter(NODE2, NODE3) 110 | .should.be.rejectedWith(/LinkedList: already in list/); 111 | }); 112 | 113 | it("should not insert a node already in the list", async () => { 114 | await linkedList 115 | .insertBefore(NODE3, NODE2) 116 | .should.be.rejectedWith(/LinkedList: already in list/); 117 | }); 118 | 119 | it("should not prepend a value that already exists", async () => { 120 | await linkedList 121 | .prepend(NODE2) 122 | .should.be.rejectedWith(/LinkedList: already in list/); 123 | }); 124 | 125 | it("should not swap a node not in the list, and a node in the list", async () => { 126 | await linkedList 127 | .swap(NOT_NODE1, NODE2) 128 | .should.be.rejectedWith(/LinkedList: not in list/); 129 | }); 130 | 131 | it("should not swap a node in the list, and a node not in the list", async () => { 132 | await linkedList 133 | .swap(NODE2, NOT_NODE1) 134 | .should.be.rejectedWith(/LinkedList: not in list/); 135 | }); 136 | 137 | it("should not swap two nodes that are not in the list", async () => { 138 | await linkedList 139 | .swap(NOT_NODE1, NOT_NODE2) 140 | .should.be.rejectedWith(/LinkedList: not in list/); 141 | }); 142 | 143 | it("should not get previous node of the node if it is not in the list", async () => { 144 | // NOTE: The revert reason isn't available for .call 145 | await linkedList 146 | .previous(NOT_NODE1) 147 | .should.be.rejectedWith(/LinkedList: not in list/); // not in list 148 | }); 149 | 150 | it("should not get following node of the given node if it is not in the list", async () => { 151 | // NOTE: The revert reason isn't available for .call 152 | await linkedList 153 | .next(NOT_NODE1) 154 | .should.be.rejectedWith(/LinkedList: not in list/); // not in list 155 | }); 156 | 157 | it("should revert when given incorrect count while retrieving elements in the list", async () => { 158 | await linkedList 159 | .elements(NODE1, 0) 160 | .should.be.rejectedWith(/LinkedList: invalid count/); // invalid count 161 | }); 162 | 163 | it("should revert when given incorrect start address while retrieving elements in the list", async () => { 164 | await linkedList 165 | .elements(NODE4, 1) 166 | .should.be.rejectedWith(/LinkedList: not in list/); // invalid count 167 | }); 168 | 169 | it("should return elements in the list", async () => { 170 | let gateways = await linkedList.elements(NODE1, 1); 171 | gateways[0].should.equal(NODE1); 172 | gateways.length.should.equal(1); 173 | 174 | gateways = await linkedList.elements(NODE2, 2); 175 | gateways[0].should.equal(NODE2); 176 | gateways[1].should.equal(NODE3); 177 | gateways.length.should.equal(2); 178 | 179 | await linkedList.append(NODE4); 180 | 181 | gateways = await linkedList.elements(NODE1, 10); 182 | gateways[0].should.equal(NODE1); 183 | gateways[3].should.equal(NODE4); 184 | gateways.length.should.equal(10); 185 | }); 186 | }); 187 | -------------------------------------------------------------------------------- /test/Protocol.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DarknodeRegistryLogicV1Instance, 3 | ProtocolInstance 4 | } from "../types/truffle-contracts"; 5 | import { NULL, waitForEpoch } from "./helper/testUtils"; 6 | 7 | const DarknodeRegistryLogicV1 = artifacts.require("DarknodeRegistryLogicV1"); 8 | const DarknodeRegistryProxy = artifacts.require("DarknodeRegistryProxy"); 9 | const Protocol = artifacts.require("Protocol"); 10 | 11 | contract("Protocol", ([owner, otherAccount]: string[]) => { 12 | let dnr: DarknodeRegistryLogicV1Instance; 13 | let protocol: ProtocolInstance; 14 | 15 | before(async () => { 16 | const dnrProxy = await DarknodeRegistryProxy.deployed(); 17 | dnr = await DarknodeRegistryLogicV1.at(dnrProxy.address); 18 | protocol = await Protocol.at(Protocol.address); 19 | await waitForEpoch(dnr); 20 | }); 21 | 22 | it("Address getters", async () => { 23 | (await protocol.getContract("DarknodeRegistry")).should.equal( 24 | dnr.address 25 | ); 26 | }); 27 | 28 | it("Protocol owner", async () => { 29 | (await protocol.owner()).should.equal(owner); 30 | 31 | await protocol 32 | .transferOwnership(otherAccount, { from: otherAccount }) 33 | .should.be.rejectedWith(/Ownable: caller is not the owner/); 34 | 35 | await protocol.transferOwnership(otherAccount); 36 | 37 | (await protocol.owner()).should.equal(owner); 38 | 39 | await protocol.claimOwnership({ from: otherAccount }); 40 | 41 | (await protocol.owner()).should.equal(otherAccount); 42 | 43 | await protocol.transferOwnership(owner, { from: otherAccount }); 44 | await protocol.claimOwnership({ from: owner }); 45 | 46 | (await protocol.owner()).should.equal(owner); 47 | }); 48 | 49 | it("Update DarknodeRegistry address", async () => { 50 | await protocol 51 | .updateContract("DarknodeRegistry", NULL, { from: otherAccount }) 52 | .should.be.rejectedWith(/Ownable: caller is not the owner/); 53 | 54 | await protocol.updateContract("DarknodeRegistry", NULL); 55 | 56 | (await protocol.getContract("DarknodeRegistry")).should.equal(NULL); 57 | 58 | await protocol.updateContract("DarknodeRegistry", dnr.address); 59 | 60 | (await protocol.getContract("DarknodeRegistry")).should.equal( 61 | dnr.address 62 | ); 63 | }); 64 | 65 | it("Proxy functions", async () => { 66 | // Try to initialize again 67 | await protocol 68 | .__Protocol_init(owner, { from: owner }) 69 | .should.be.rejectedWith( 70 | /Contract instance has already been initialized/ 71 | ); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/String.ts: -------------------------------------------------------------------------------- 1 | import BN = require("bn.js"); 2 | 3 | import { StringTestInstance } from "../types/truffle-contracts"; 4 | import { randomBytes } from "./helper/testUtils"; 5 | 6 | const StringTest = artifacts.require("StringTest"); 7 | 8 | contract("String", accounts => { 9 | let StringInstance: StringTestInstance; 10 | 11 | before(async () => { 12 | StringInstance = await StringTest.new(); 13 | }); 14 | 15 | it("can add strings", async () => { 16 | (await StringInstance.add4("1", "2", "3", "4")).should.equal("1234"); 17 | }); 18 | 19 | it("can convert addresses to hex strings", async () => { 20 | (await StringInstance.fromAddress(accounts[0])).should.equal( 21 | accounts[0].toLowerCase() 22 | ); 23 | }); 24 | 25 | it("can convert bytes32 to hex strings", async () => { 26 | const bytes32 = randomBytes(32); 27 | 28 | (await StringInstance.fromBytes32(bytes32)).should.equal( 29 | bytes32.toLowerCase() 30 | ); 31 | }); 32 | 33 | it("can convert uint to strings", async () => { 34 | await testNumString("0"); 35 | await testNumString("1"); 36 | await testNumString("12345"); 37 | await testNumString( 38 | "81804755166950992694975918889421430561708705428859269028015361660142001064486" 39 | ); 40 | await testNumString( 41 | "90693014804679621771165998959262552553277008236216558633727798007697162314221" 42 | ); 43 | await testNumString( 44 | "65631258835468800295340604864107498262349560547191423452833833494209803247319" 45 | ); 46 | }); 47 | 48 | const testNumString = async (numString: string) => { 49 | (await StringInstance.fromUint(new BN(numString))).should.equal( 50 | numString 51 | ); 52 | }; 53 | }); 54 | -------------------------------------------------------------------------------- /test/Validate.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | import { ecsign } from "ethereumjs-util"; 3 | import hashjs from "hash.js"; 4 | import { Account } from "web3-eth-accounts"; 5 | 6 | import { ValidateTestInstance } from "../types/truffle-contracts"; 7 | import { Ox } from "./helper/testUtils"; 8 | 9 | export interface Darknode { 10 | account: Account; 11 | privateKey: Buffer; 12 | } 13 | 14 | const ValidateTest = artifacts.require("ValidateTest"); 15 | 16 | const numDarknodes = 2; 17 | 18 | contract("Validate", (accounts: string[]) => { 19 | let validateTest: ValidateTestInstance; 20 | const darknodes = new Array(); 21 | 22 | before(async () => { 23 | validateTest = await ValidateTest.new(); 24 | 25 | for (let i = 0; i < numDarknodes; i++) { 26 | const darknode = web3.eth.accounts.create(); 27 | const privKey = Buffer.from(darknode.privateKey.slice(2), "hex"); 28 | darknodes.push({ 29 | account: darknode, 30 | privateKey: privKey 31 | }); 32 | } 33 | }); 34 | 35 | describe("when generating messages", async () => { 36 | it("should correctly generate secret messages", async () => { 37 | const a = new BN("3"); 38 | const b = new BN("7"); 39 | const c = new BN("10"); 40 | const d = new BN( 41 | "81804755166950992694975918889421430561708705428859269028015361660142001064486" 42 | ); 43 | const e = new BN( 44 | "90693014804679621771165998959262552553277008236216558633727798007697162314221" 45 | ); 46 | const f = new BN( 47 | "65631258835468800295340604864107498262349560547191423452833833494209803247319" 48 | ); 49 | const msg = generateSecretMessage(a, b, c, d, e, f); 50 | // tslint:disable-next-line:max-line-length 51 | msg.should.be.equal( 52 | "Secret(ShamirShare(3,7,S256N(10),S256PrivKey(S256N(81804755166950992694975918889421430561708705428859269028015361660142001064486),S256P(90693014804679621771165998959262552553277008236216558633727798007697162314221),S256P(65631258835468800295340604864107498262349560547191423452833833494209803247319))))" 53 | ); 54 | const rawMsg = await validateTest.secretMessage(a, b, c, d, e, f); 55 | msg.should.be.equal(web3.utils.hexToAscii(rawMsg)); 56 | }); 57 | 58 | it("should correctly generate the propose message", async () => { 59 | const height = new BN("6349374925919561232"); 60 | const round = new BN("3652381888914236532"); 61 | const blockhash = "XTsJ2rO2yD47tg3JfmakVRXLzeou4SMtZvsMc6lkr6o"; 62 | const hexBlockhash = web3.utils.asciiToHex(blockhash); 63 | const validRound = new BN("6345888412984379713"); 64 | const proposeMsg = generateProposeMessage( 65 | height, 66 | round, 67 | blockhash, 68 | validRound 69 | ); 70 | const rawMsg = await validateTest.proposeMessage( 71 | height, 72 | round, 73 | hexBlockhash, 74 | validRound 75 | ); 76 | proposeMsg.should.be.equal(web3.utils.hexToAscii(rawMsg)); 77 | }); 78 | 79 | it("should correctly generate the prevote message", async () => { 80 | const height = new BN("6349374925919561232"); 81 | const round = new BN("3652381888914236532"); 82 | const blockhash = "XTsJ2rO2yD47tg3JfmakVRXLzeou4SMtZvsMc6lkr6o"; 83 | const hexBlockhash = web3.utils.asciiToHex(blockhash); 84 | const prevoteMsg = generatePrevoteMessage(height, round, blockhash); 85 | const rawMsg = await validateTest.prevoteMessage( 86 | height, 87 | round, 88 | hexBlockhash 89 | ); 90 | prevoteMsg.should.be.equal(web3.utils.hexToAscii(rawMsg)); 91 | }); 92 | 93 | it("should correctly generate the precommit message", async () => { 94 | const height = new BN("6349374925919561232"); 95 | const round = new BN("3652381888914236532"); 96 | const blockhash = "XTsJ2rO2yD47tg3JfmakVRXLzeou4SMtZvsMc6lkr6o"; 97 | const hexBlockhash = web3.utils.asciiToHex(blockhash); 98 | const precommitMsg = generatePrecommitMessage( 99 | height, 100 | round, 101 | blockhash 102 | ); 103 | const rawMsg = await validateTest.precommitMessage( 104 | height, 105 | round, 106 | hexBlockhash 107 | ); 108 | precommitMsg.should.be.equal(web3.utils.hexToAscii(rawMsg)); 109 | }); 110 | }); 111 | 112 | describe("when recovering signatures", async () => { 113 | it("can recover the signer of a propose message", async () => { 114 | const darknode = darknodes[0]; 115 | const height = new BN("6349374925919561232"); 116 | const round = new BN("3652381888914236532"); 117 | const blockhash = "XTsJ2rO2yD47tg3JfmakVRXLzeou4SMtZvsMc6lkr6o"; 118 | const hexBlockhash = web3.utils.asciiToHex(blockhash); 119 | const validRound = new BN("6345888412984379713"); 120 | const proposeMsg = generateProposeMessage( 121 | height, 122 | round, 123 | blockhash, 124 | validRound 125 | ); 126 | const hash = hashjs 127 | .sha256() 128 | .update(proposeMsg) 129 | .digest("hex"); 130 | const sig = ecsign(Buffer.from(hash, "hex"), darknode.privateKey); 131 | const sigString = Ox( 132 | `${sig.r.toString("hex")}${sig.s.toString( 133 | "hex" 134 | )}${sig.v.toString(16)}` 135 | ); 136 | const signer = await validateTest.recoverPropose( 137 | height, 138 | round, 139 | hexBlockhash, 140 | validRound, 141 | sigString 142 | ); 143 | signer.should.equal(darknode.account.address); 144 | }); 145 | 146 | it("can recover the signer of a prevote message", async () => { 147 | const darknode = darknodes[0]; 148 | const height = new BN("6349374925919561232"); 149 | const round = new BN("3652381888914236532"); 150 | const blockhash = "XTsJ2rO2yD47tg3JfmakVRXLzeou4SMtZvsMc6lkr6o"; 151 | const hexBlockhash = web3.utils.asciiToHex(blockhash); 152 | const proposeMsg = generatePrevoteMessage(height, round, blockhash); 153 | const hash = hashjs 154 | .sha256() 155 | .update(proposeMsg) 156 | .digest("hex"); 157 | const sig = ecsign(Buffer.from(hash, "hex"), darknode.privateKey); 158 | const sigString = Ox( 159 | `${sig.r.toString("hex")}${sig.s.toString( 160 | "hex" 161 | )}${sig.v.toString(16)}` 162 | ); 163 | const signer = await validateTest.recoverPrevote( 164 | height, 165 | round, 166 | hexBlockhash, 167 | sigString 168 | ); 169 | signer.should.equal(darknode.account.address); 170 | }); 171 | 172 | it("can recover the signer of a precommit message", async () => { 173 | const darknode = darknodes[0]; 174 | const height = new BN("6349374925919561232"); 175 | const round = new BN("3652381888914236532"); 176 | const blockhash = "XTsJ2rO2yD47tg3JfmakVRXLzeou4SMtZvsMc6lkr6o"; 177 | const hexBlockhash = web3.utils.asciiToHex(blockhash); 178 | const proposeMsg = generatePrecommitMessage( 179 | height, 180 | round, 181 | blockhash 182 | ); 183 | const hash = hashjs 184 | .sha256() 185 | .update(proposeMsg) 186 | .digest("hex"); 187 | const sig = ecsign(Buffer.from(hash, "hex"), darknode.privateKey); 188 | const sigString = Ox( 189 | `${sig.r.toString("hex")}${sig.s.toString( 190 | "hex" 191 | )}${sig.v.toString(16)}` 192 | ); 193 | const signer = await validateTest.recoverPrecommit( 194 | height, 195 | round, 196 | hexBlockhash, 197 | sigString 198 | ); 199 | signer.should.equal(darknode.account.address); 200 | }); 201 | 202 | it("can recover the signer of a secret message", async () => { 203 | const darknode = darknodes[0]; 204 | const a = new BN("3"); 205 | const b = new BN("7"); 206 | const c = new BN("10"); 207 | const d = new BN( 208 | "81804755166950992694975918889421430561708705428859269028015361660142001064486" 209 | ); 210 | const e = new BN( 211 | "90693014804679621771165998959262552553277008236216558633727798007697162314221" 212 | ); 213 | const f = new BN( 214 | "65631258835468800295340604864107498262349560547191423452833833494209803247319" 215 | ); 216 | const msg = generateSecretMessage(a, b, c, d, e, f); 217 | const hash = hashjs 218 | .sha256() 219 | .update(msg) 220 | .digest("hex"); 221 | const sig = ecsign(Buffer.from(hash, "hex"), darknode.privateKey); 222 | const sigString = Ox( 223 | `${sig.r.toString("hex")}${sig.s.toString( 224 | "hex" 225 | )}${sig.v.toString(16)}` 226 | ); 227 | const signer = await validateTest.recoverSecret( 228 | a, 229 | b, 230 | c, 231 | d, 232 | e, 233 | f, 234 | sigString 235 | ); 236 | signer.should.equal(darknode.account.address); 237 | }); 238 | }); 239 | }); 240 | 241 | export const generateProposeMessage = ( 242 | height: BN, 243 | round: BN, 244 | blockHash: string, 245 | validRound: BN 246 | ): string => { 247 | // tslint:disable-next-line:max-line-length 248 | return `Propose(Height=${height.toString()},Round=${round.toString()},BlockHash=${blockHash},ValidRound=${validRound.toString()})`; 249 | }; 250 | 251 | export const generatePrevoteMessage = ( 252 | height: BN, 253 | round: BN, 254 | blockHash: string 255 | ): string => { 256 | return `Prevote(Height=${height.toString()},Round=${round.toString()},BlockHash=${blockHash})`; 257 | }; 258 | 259 | export const generatePrecommitMessage = ( 260 | height: BN, 261 | round: BN, 262 | blockHash: string 263 | ): string => { 264 | return `Precommit(Height=${height.toString()},Round=${round.toString()},BlockHash=${blockHash})`; 265 | }; 266 | 267 | export const generateSecretMessage = ( 268 | a: BN, 269 | b: BN, 270 | c: BN, 271 | d: BN, 272 | e: BN, 273 | f: BN 274 | ): string => { 275 | // tslint:disable-next-line:max-line-length 276 | return `Secret(ShamirShare(${a.toString()},${b.toString()},S256N(${c.toString()}),S256PrivKey(S256N(${d.toString()}),S256P(${e.toString()}),S256P(${f.toString()}))))`; 277 | }; 278 | -------------------------------------------------------------------------------- /test/helper/logs.ts: -------------------------------------------------------------------------------- 1 | import * as chai from "chai"; 2 | 3 | import BigNumber from "bignumber.js"; 4 | import BN from "bn.js"; 5 | 6 | interface Log { 7 | event: string; 8 | args: object; 9 | } 10 | 11 | interface TransactionReceipt { 12 | tx: string; 13 | receipt: { 14 | transactionHash: string; 15 | transactionIndex: number; 16 | blockHash: string; 17 | blockNumber: number; 18 | gasUsed: number; 19 | cumulativeGasUsed: number; 20 | contractAddress: null; 21 | logs: any[]; 22 | status: boolean; 23 | logsBloom: string; 24 | }; 25 | logs: { 26 | logIndex: number; 27 | transactionIndex: number; 28 | transactionHash: string; 29 | blockHash: string; 30 | blockNumber: number; 31 | address: string; 32 | type: string; 33 | id: string; 34 | event: string; 35 | args: object; 36 | }[]; 37 | } 38 | 39 | export const log = (event: string, args: object) => ({ 40 | event, 41 | args 42 | }); 43 | 44 | // Chai helper for comparing logs 45 | // tslint:disable:only-arrow-functions 46 | chai.use(function(newChai: any, utils: any): void { 47 | const property = "emit"; 48 | newChai.Assertion.addProperty(property, function() { 49 | utils.flag(this, property, true); 50 | }); 51 | 52 | const override = function(fn: any) { 53 | // tslint:disable-next-line:variable-name 54 | return function(_super: any) { 55 | return function(value: any, ...args: any[]) { 56 | if (utils.flag(this, property)) { 57 | const expected = value; 58 | const actual = getLogsFromTx(this._obj); 59 | fn.apply(this, [expected, actual]); 60 | } else { 61 | _super.apply(this, [value, ...args]); 62 | } 63 | }; 64 | }; 65 | }; 66 | 67 | const events = override(function(expected: Log[], actual: Log[]) { 68 | this.assert( 69 | compareArrayOfLogs(expected, actual), 70 | "expected logs #{act} to equal #{exp}", 71 | "expected logs #{act} to be different from #{exp}", 72 | logsToString(expected), 73 | logsToString(actual) 74 | ); 75 | 76 | for (let i = 0; i < expected.length; i++) { 77 | const expectedLog = expected[i]; 78 | const actualLog = actual[i]; 79 | for (const arg in expectedLog.args) { 80 | // skip if the property is from prototype 81 | if (!expectedLog.args.hasOwnProperty(arg)) { 82 | continue; 83 | } 84 | 85 | const expectedArg = (expectedLog.args as any)[arg]; 86 | const actualArg = (actualLog.args as any)[arg]; 87 | 88 | let sameValues: boolean; 89 | if (BN.isBN(expectedArg) || expectedArg.isBigNumber) { 90 | sameValues = new BigNumber(expectedArg).eq( 91 | new BigNumber(actualArg) 92 | ); 93 | } else { 94 | sameValues = expectedArg === (actualLog.args as any)[arg]; 95 | } 96 | 97 | this.assert( 98 | sameValues, 99 | `expected ${arg} to be #{exp} instead of #{act} in log ${expectedLog.event}`, 100 | `expected ${arg} to be different from #{exp} in log ${expectedLog.event}`, 101 | (expectedLog.args as any)[arg], 102 | (actualLog.args as any)[arg] 103 | ); 104 | } 105 | } 106 | }); 107 | newChai.Assertion.overwriteMethod("logs", events); 108 | }); 109 | 110 | // Pretty-print logs 111 | const logsToString = (logs: Log[]): string => { 112 | return `[${logs.map((logItem: Log) => logItem.event).join(", ")}]`; 113 | }; 114 | // const logToString = (logItem: Log): string => { 115 | // return `${logItem.event} ${JSON.stringify(logItem.args)}`; 116 | // }; 117 | 118 | // Compare logs 119 | const compareArrayOfLogs = (expected: Log[], actual: Log[]): boolean => { 120 | if (expected.length !== actual.length) { 121 | return false; 122 | } 123 | 124 | for (let i = 0; i < expected.length; i++) { 125 | const expectedLog = expected[i]; 126 | const actualLog = actual[i]; 127 | 128 | if (expectedLog.event !== actualLog.event) { 129 | return false; 130 | } 131 | } 132 | 133 | return true; 134 | }; 135 | 136 | // Extract logs from transaction receipt in correct format.s 137 | export const getLogsFromTx = (tx: TransactionReceipt): Log[] => { 138 | return tx.logs.map(logItem => { 139 | const args = {}; 140 | for (const arg in logItem.args) { 141 | // skip if the property is from prototype 142 | if (!logItem.args.hasOwnProperty(arg)) { 143 | continue; 144 | } 145 | 146 | if (isNaN(parseInt(arg, 10)) && arg !== "__length__") { 147 | (args as any)[arg] = (logItem.args as any)[arg]; 148 | } 149 | } 150 | return { 151 | event: logItem.event, 152 | args 153 | }; 154 | }); 155 | }; 156 | -------------------------------------------------------------------------------- /test/helper/testUtils.ts: -------------------------------------------------------------------------------- 1 | import * as chai from "chai"; 2 | import * as crypto from "crypto"; 3 | 4 | import BigNumber from "bignumber.js"; 5 | import BN from "bn.js"; 6 | import chaiAsPromised from "chai-as-promised"; 7 | import chaiBigNumber from "chai-bignumber"; 8 | import { ECDSASignature } from "ethereumjs-util"; 9 | import { TransactionReceipt } from "web3-core"; 10 | import { keccak256, toChecksumAddress } from "web3-utils"; 11 | 12 | import { DarknodeRegistryLogicV1Instance } from "../../types/truffle-contracts"; 13 | // Import chai log helper 14 | import "./logs"; 15 | 16 | const ERC20 = artifacts.require("PaymentToken"); 17 | 18 | chai.use(chaiAsPromised); 19 | chai.use((chaiBigNumber as any)(BigNumber) as any); 20 | chai.should(); 21 | 22 | export const { encodeCallData } = require("../../migrations/encode.js"); 23 | 24 | const networkAddresses = require("../../migrations/networks.js"); 25 | 26 | const config = networkAddresses.config; 27 | export const { MINIMUM_POD_SIZE, MINIMUM_EPOCH_INTERVAL_SECONDS } = config; 28 | 29 | export const MINIMUM_BOND = new BN(config.MINIMUM_BOND); 30 | 31 | export const ETHEREUM = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; 32 | 33 | // Makes an ID for a darknode 34 | export function ID(i: string | number) { 35 | return toChecksumAddress(keccak256(i.toString()).slice(0, 42)); 36 | } 37 | 38 | // Makes a public key for a darknode 39 | export function PUBK(i: string | number) { 40 | return keccak256(i.toString()); 41 | } 42 | 43 | export const NULL = "0x0000000000000000000000000000000000000000"; 44 | export const NULL32 = 45 | "0x0000000000000000000000000000000000000000000000000000000000000000"; 46 | 47 | // Add a 0x prefix to a hex value, converting to a string first 48 | export const Ox = (hex: string | BN | Buffer) => { 49 | const hexString = typeof hex === "string" ? hex : hex.toString("hex"); 50 | return hexString.substring(0, 2) === "0x" ? hexString : `0x${hexString}`; 51 | }; 52 | 53 | export const strip0x = (hex: string) => 54 | hex.substring(0, 2) === "0x" ? hex.slice(2) : hex; 55 | 56 | export const hexToBuffer = (hex: string | BN | Buffer) => 57 | BN.isBN(hex) 58 | ? hex.toBuffer() 59 | : Buffer.isBuffer(hex) 60 | ? hex 61 | : Buffer.from(strip0x(hex), "hex"); 62 | 63 | export const randomBytes = (bytes: number): string => { 64 | return Ox(crypto.randomBytes(bytes)); 65 | }; 66 | 67 | export const randomAddress = (): string => { 68 | return toChecksumAddress(randomBytes(20)); 69 | }; 70 | 71 | const increaseTimeHelper = async (seconds: number) => { 72 | await new Promise((resolve, reject) => { 73 | // tslint:disable-next-line: no-floating-promises 74 | return web3.currentProvider.send( 75 | { 76 | jsonrpc: "2.0", 77 | method: "evm_increaseTime", 78 | params: [seconds], 79 | id: 0, 80 | } as any, 81 | ((err: Error) => { 82 | if (err) { 83 | reject(err); 84 | } 85 | // tslint:disable-next-line: no-floating-promises 86 | return web3.currentProvider.send( 87 | { 88 | jsonrpc: "2.0", 89 | method: "evm_mine", 90 | params: [], 91 | id: new Date().getSeconds(), 92 | } as any, 93 | ((innerErr: Error) => { 94 | if (innerErr) { 95 | reject(); 96 | } 97 | resolve(); 98 | }) as any 99 | ); 100 | }) as any 101 | ); 102 | }); 103 | }; 104 | 105 | const getCurrentTimestamp = async (): Promise => 106 | parseInt( 107 | ( 108 | await web3.eth.getBlock(await web3.eth.getBlockNumber()) 109 | ).timestamp.toString(), 110 | 10 111 | ); 112 | 113 | export const increaseTime = async (seconds: number) => { 114 | let currentTimestamp = await getCurrentTimestamp(); 115 | const target = currentTimestamp + seconds; 116 | do { 117 | const increase = Math.ceil(target - currentTimestamp + 1); 118 | await increaseTimeHelper(increase); 119 | currentTimestamp = await getCurrentTimestamp(); 120 | } while (currentTimestamp < target); 121 | }; 122 | 123 | export async function waitForEpoch(dnr: DarknodeRegistryLogicV1Instance) { 124 | // const timeout = MINIMUM_EPOCH_INTERVAL_SECONDS; 125 | const timeout = new BN( 126 | (await dnr.minimumEpochInterval()).toString() 127 | ).toNumber(); 128 | while (true) { 129 | // Must be an on-chain call, or the time won't be updated 130 | try { 131 | return await dnr.epoch(); 132 | } catch (err) { 133 | // epoch reverted, epoch interval hasn't passed 134 | } 135 | // Sleep for `timeout` seconds 136 | await increaseTime(timeout); 137 | // await new Promise((resolve) => setTimeout(resolve, timeout * 1000)); 138 | } 139 | } 140 | 141 | export const deployProxy = async ( 142 | web3: Web3, 143 | ProxyContract: Truffle.Contract, 144 | LogicContract: Truffle.Contract, 145 | proxyGovernanceAddress: string, 146 | params: { type: string; value: any; name?: string }[], 147 | options?: { from: string } 148 | ): Promise => { 149 | const logicContract = await LogicContract.new(); 150 | const proxy = await ProxyContract.new(); 151 | 152 | await proxy.initialize( 153 | logicContract.address, 154 | proxyGovernanceAddress, 155 | encodeCallData( 156 | web3, 157 | "initialize", 158 | params.map((p) => p.type), 159 | params.map((p) => p.value) 160 | ), 161 | options 162 | ); 163 | return await LogicContract.at(proxy.address); 164 | }; 165 | 166 | export const sigToString = (sig: ECDSASignature) => { 167 | return Ox( 168 | `${sig.r.toString("hex")}${sig.s.toString("hex")}${sig.v.toString(16)}` 169 | ); 170 | }; 171 | 172 | export const sleep = (ms: number) => 173 | new Promise((resolve) => setTimeout(resolve, ms)); 174 | 175 | export const HOURS = 60 * 60; 176 | export const DAYS = 24 * HOURS; 177 | 178 | export const getBalance = async ( 179 | token: string, 180 | address: string 181 | ): Promise => { 182 | if (token === ETHEREUM) { 183 | return new BigNumber((await web3.eth.getBalance(address)).toString()); 184 | } else { 185 | const tokenContract = await ERC20.at(token); 186 | return new BigNumber( 187 | (await tokenContract.balanceOf(address)).toString() 188 | ); 189 | } 190 | }; 191 | 192 | export const getSymbol = async (token: string): Promise => { 193 | if (token === ETHEREUM) { 194 | return "ETH"; 195 | } else { 196 | const tokenContract = await ERC20.at(token); 197 | return await tokenContract.symbol(); 198 | } 199 | }; 200 | 201 | export const getDecimals = async (token: string): Promise => { 202 | if (token === ETHEREUM) { 203 | return 18; 204 | } else { 205 | const tokenContract = await ERC20.at(token); 206 | return parseInt((await tokenContract.decimals()).toString(), 10); 207 | } 208 | }; 209 | 210 | export const transferToken = async ( 211 | token: string, 212 | to: string, 213 | amount: BigNumber | string | number | BN 214 | ): Promise => { 215 | if (token === ETHEREUM) { 216 | const from = (await web3.eth.getAccounts())[0]; 217 | return (await web3.eth.sendTransaction({ 218 | to, 219 | value: amount.toString(), 220 | from, 221 | })) as unknown as TransactionReceipt; 222 | } else { 223 | const tokenContract = await ERC20.at(token); 224 | return (await tokenContract.transfer(to, amount.toString())).receipt; 225 | } 226 | }; 227 | 228 | export const isPromise = (x: any): x is Promise => { 229 | return !!x.then; 230 | }; 231 | 232 | export const toBN = < 233 | X extends (string | number | BN) | Promise 234 | >( 235 | inp: X 236 | ): X extends string | number | BN ? BigNumber : Promise => { 237 | if (isPromise(inp)) { 238 | return inp.then((x) => new BigNumber(x.toString())) as X extends 239 | | string 240 | | number 241 | | BN 242 | ? BigNumber 243 | : Promise; 244 | } else { 245 | return new BigNumber(inp.toString()) as X extends string | number | BN 246 | ? BigNumber 247 | : Promise; 248 | } 249 | }; 250 | 251 | export const range = (n: number) => Array.from(new Array(n)).map((_, i) => i); 252 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | require("ts-node/register"); 2 | require("dotenv").config(); 3 | 4 | const HDWalletProvider = require("truffle-hdwallet-provider"); 5 | const { execSync } = require("child_process"); 6 | 7 | const GWEI = 1000000000; 8 | const commitHash = execSync("git describe --always --long") 9 | .toString() 10 | .trim(); 11 | 12 | if ( 13 | (process.env.NETWORK || "").match(/localnet|devnet|testnet|main/) && 14 | process.env.INFURA_KEY === undefined 15 | ) { 16 | throw new Error("Must set INFURA_KEY"); 17 | } 18 | 19 | const kovanNetwork = { 20 | // @ts-ignore 21 | provider: () => 22 | new HDWalletProvider( 23 | process.env.MNEMONIC_TESTNET, 24 | `https://kovan.infura.io/v3/${process.env.INFURA_KEY}` 25 | ), 26 | network_id: 42, 27 | gas: 6721975, 28 | gasPrice: 6.5 * GWEI, 29 | networkCheckTimeout: 20000 30 | }; 31 | 32 | const mainNetwork = { 33 | // @ts-ignore 34 | provider: () => 35 | new HDWalletProvider( 36 | process.env.MNEMONIC_MAINNET, 37 | `https://mainnet.infura.io/v3/${process.env.INFURA_KEY}` 38 | ), 39 | network_id: 1, 40 | gas: 6721975, 41 | gasPrice: 42 * GWEI, 42 | networkCheckTimeout: 20000 43 | }; 44 | 45 | const ethRinkebyNetwork = { 46 | // @ts-ignore 47 | provider: () => 48 | new HDWalletProvider( 49 | process.env.MNEMONIC_TESTNET || process.env.MNEMONIC_TESTNET, 50 | `https://rinkeby.infura.io/v3/${process.env.INFURA_KEY}` 51 | ), 52 | network_id: 4, 53 | // gas: 6721975, 54 | // gasPrice: 6.5 * GWEI, 55 | networkCheckTimeout: 10000 56 | }; 57 | 58 | module.exports = { 59 | networks: { 60 | localnet: kovanNetwork, 61 | devnet: kovanNetwork, 62 | rinkebyDevnet: ethRinkebyNetwork, 63 | testnet: kovanNetwork, 64 | mainnet: mainNetwork, 65 | chaosnet: mainNetwork, 66 | development: { 67 | host: "localhost", 68 | port: 8545, 69 | network_id: "*" 70 | } 71 | }, 72 | mocha: { 73 | // // Use with `npm run test`, not with `npm run coverage` 74 | // reporter: "eth-gas-reporter", 75 | // reporterOptions: { 76 | // currency: "USD", 77 | // gasPrice: 21 78 | // }, 79 | enableTimeouts: false, 80 | useColors: true, 81 | bail: false 82 | }, 83 | compilers: { 84 | solc: { 85 | version: "0.5.17", 86 | settings: { 87 | // evmVersion: "petersburg", // "istanbul", 88 | optimizer: { 89 | enabled: true, 90 | runs: 200 91 | } 92 | } 93 | } 94 | }, 95 | plugins: ["truffle-plugin-verify", "solidity-coverage"], 96 | api_keys: { 97 | etherscan: process.env.ETHERSCAN_KEY 98 | }, 99 | verify: { 100 | preamble: ` 101 | Deployed by Ren Project, https://renproject.io 102 | 103 | Commit hash: ${commitHash} 104 | Repository: https://github.com/renproject/darknode-sol 105 | Issues: https://github.com/renproject/darknode-sol/issues 106 | 107 | Licenses 108 | @openzeppelin/contracts: (MIT) https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/LICENSE 109 | darknode-sol: (GNU GPL V3) https://github.com/renproject/darknode-sol/blob/master/LICENSE 110 | ` 111 | }, 112 | contracts_build_directory: `./build/${process.env.NETWORK || 113 | "development"}`, 114 | // This is required by truffle to find any ts test files 115 | test_file_extension_regexp: /.*\.ts$/ 116 | }; 117 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "typeRoots": ["types"], 4 | "lib": ["es2015"], 5 | "types": ["node", "mocha", "truffle-typings", "chai"], 6 | "module": "commonjs", 7 | "noImplicitAny": true, 8 | "noImplicitReturns": true, 9 | "removeComments": true, 10 | "preserveConstEnums": true, 11 | "sourceMap": true, 12 | "esModuleInterop": true 13 | }, 14 | "include": [ 15 | "test", 16 | "types" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended"], 3 | "linterOptions": { 4 | "exclude": ["node_modules"] 5 | }, 6 | "rules": { 7 | "quotemark": [true, "double"], 8 | "variable-name": [ 9 | true, 10 | "ban-keywords", 11 | "check-format", 12 | "allow-leading-underscore", 13 | "allow-trailing-underscore", 14 | "allow-pascal-case" 15 | ], 16 | "no-shadowed-variable": [ 17 | true, 18 | { 19 | "underscore": false 20 | } 21 | ], 22 | "interface-name": false, 23 | "max-classes-per-file": false, 24 | "no-console": false, 25 | "no-empty-interface": false, 26 | "no-var-requires": false, 27 | "object-literal-sort-keys": false, 28 | "no-implicit-dependencies": false, 29 | "ordered-imports": true, 30 | "no-object-literal-type-assertion": true, 31 | "semicolon": true, 32 | "eofline": true, 33 | "no-non-null-assertion": true, 34 | "no-unused-expression": false, 35 | "one-variable-per-declaration": false, 36 | "space-before-function-paren": false, 37 | "no-floating-promises": true 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /types/chai/index.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | 3 | /// 4 | 5 | declare module "chai-bignumber" { 6 | function chaiBigNumber(bignumber: any, BN?: any): (chai: any, utils: any) => void; 7 | 8 | namespace chaiBigNumber { 9 | } 10 | 11 | export = chaiBigNumber; 12 | } 13 | 14 | declare namespace Chai { 15 | type BigNumber = number | string | { toNumber: () => number }; 16 | 17 | // For BDD API 18 | interface Assertion extends LanguageChains, NumericComparison, TypeComparison { 19 | bignumber: BigNumberAssert; 20 | } 21 | 22 | // For Assert API 23 | interface Assert { 24 | bignumber: BigNumberAssert; 25 | } 26 | 27 | export interface BigNumberAssert { 28 | finite(actual?: BN, msg?: string): void; 29 | integer(actual?: BN, msg?: string): void; 30 | negative(actual?: BN, msg?: string): void; 31 | zero(actual?: BN, msg?: string): void; 32 | 33 | equal(actual?: BN, expected?: BN, msg?: string): void; 34 | equals(actual?: BN, expected?: BN, msg?: string): void; 35 | eq(actual?: BN, expected?: BN, msg?: string): void; 36 | 37 | greaterThan(actual?: BN, expected?: BN, msg?: string): void; 38 | above(actual?: BN, expected?: BN, msg?: string): void; 39 | gt(actual?: BN, expected?: BN, msg?: string): void; 40 | 41 | greaterThanOrEqualTo(actual?: BN, expected?: BN, msg?: string): void; 42 | least(actual?: BN, expected?: BN, msg?: string): void; 43 | gte(actual?: BN, expected?: BN, msg?: string): void; 44 | 45 | lessThan(actual?: BN, expected?: BN, msg?: string): void; 46 | below(actual?: BN, expected?: BN, msg?: string): void; 47 | lt(actual?: BN, expected?: BN, msg?: string): void; 48 | 49 | lessThanOrEqualTo(actual?: BN, expected?: BN, msg?: string): void; 50 | most(actual?: BN, expected?: BN, msg?: string): void; 51 | lte(actual?: BN, expected?: BN, msg?: string): void; 52 | } 53 | } 54 | --------------------------------------------------------------------------------