├── .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 | [](https://circleci.com/gh/renproject/darknode-sol)
6 | [](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 |
--------------------------------------------------------------------------------