diff --git a/.circleci/config.yml b/.circleci/config.yml index ddd1691e87..29038e13d4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -374,6 +374,41 @@ jobs: paths: - coverage-cross-chain-foreign - coverage-cross-chain-home + reputation-test-non-gnosis: + <<: *job_common + steps: + - checkout + - <<: *step_restore_cache + - setup_remote_docker: + version: docker23 + - <<: *step_pull_solc_docker + - <<: *step_setup_global_packages + - run: + name: "Install lsof" + command: | + sudo apt-get update + sudo apt-get install lsof + - run: + name: "Running reputation system unit tests" + command: npm run test:reputation:1:anotherChain + environment: + NODE_OPTIONS: --max-old-space-size=6144 + - run: + name: "Reset chains" + command: | + sudo apt-get update + sudo apt-get install lsof + npm run stop:blockchain:client && rm -rf ganache-chain-db* + - run: + name: "Running reputation system unit tests" + command: npm run test:reputation:2:anotherChain + environment: + NODE_OPTIONS: --max-old-space-size=6144 + # - run: + # name: "Running storage consistency smoke tests" + # command: npm run test:contracts:smoke + + check-coverage: <<: *job_common steps: @@ -448,6 +483,8 @@ workflows: context: dockerhub-credentials - coverage-test-bridging: context: dockerhub-credentials + - reputation-test-non-gnosis: + context: dockerhub-credentials - check-coverage: context: dockerhub-credentials requires: diff --git a/.gitmodules b/.gitmodules index 6534415819..7493bb497d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ path = lib/safe-contracts url = https://github.com/safe-global/safe-contracts ignore = dirty +[submodule "lib/wormhole"] + path = lib/wormhole + url = https://github.com/wormhole-foundation/wormhole.git diff --git a/.solcover.chainid.js b/.solcover.chainid.js index e6ef768f73..9faaa4e163 100644 --- a/.solcover.chainid.js +++ b/.solcover.chainid.js @@ -4,5 +4,4 @@ config.providerOptions.network_id = parseInt(process.env.CHAIN_ID, 10); config.providerOptions._chainId = parseInt(process.env.CHAIN_ID, 10); config.providerOptions._chainIdRpc = parseInt(process.env.CHAIN_ID, 10); config.istanbulFolder = `./coverage-chainid-${process.env.CHAIN_ID}` - module.exports = config \ No newline at end of file diff --git a/.solcover.crosschain.js b/.solcover.crosschain.js index 53b4dad792..0fbeca72ee 100644 --- a/.solcover.crosschain.js +++ b/.solcover.crosschain.js @@ -1,5 +1,24 @@ const config = require("./.solcover.js") +const ethers = require("ethers"); -config.istanbulFolder = `./coverage-cross-chain-${process.env.HARDHAT_FOREIGN ? "foreign" : "home"}` +const { FORKED_XDAI_CHAINID } = require("./helpers/constants"); + +config.istanbulFolder = `./coverage-cross-chain-${process.env.HARDHAT_FOREIGN === "true" ? "foreign" : "home"}` +console.log(`Coverage folder: ${config.istanbulFolder}`) + +let chainId; +// We configure the truffle coverage chain to have the same chainid as one of the +// nodes we've started up, but on a different port +// TODO: Actually query nodes, don't hard-code here, or work out how to get environment +// variables in package.json to work here as I want. +if (JSON.parse(process.env.HARDHAT_FOREIGN)){ + chainId = FORKED_XDAI_CHAINID + 1; +} else { + chainId = FORKED_XDAI_CHAINID; +} + +config.providerOptions.network_id = chainId; +config.providerOptions._chainId = chainId; +config.providerOptions._chainIdRpc = chainId; module.exports = config diff --git a/.solcover.js b/.solcover.js index 3b7e599d4a..43a4ccde8c 100644 --- a/.solcover.js +++ b/.solcover.js @@ -27,6 +27,9 @@ module.exports = { account_keys_path: "./ganache-accounts.json", vmErrorsOnRPCResponse: false, total_accounts: 18, + _chainId: 265669100, + _chainIdRpc: 265669100, + network_id: 265669100, accounts: [ {secretKey:"0x0355596cdb5e5242ad082c4fe3f8bbe48c9dba843fe1f99dd8272f487e70efae","balance":"100000000000000000000"}, {secretKey:"0xe9aebe8791ad1ebd33211687e9c53f13fe8cca53b271a6529c7d7ba05eda5ce2","balance":"100000000000000000000"}, @@ -50,5 +53,5 @@ module.exports = { }, onCompileComplete: provisionTokenContracts, istanbulFolder: "./coverage-contracts", - modifierWhitelist: ["always"], + modifierWhitelist: ["always", "onlyMiningChain", "onlyNotMiningChain"], } diff --git a/contracts/bridging/IColonyBridge.sol b/contracts/bridging/IColonyBridge.sol new file mode 100644 index 0000000000..45997b679d --- /dev/null +++ b/contracts/bridging/IColonyBridge.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + This file is part of The Colony Network. + + The Colony Network is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The Colony Network is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with The Colony Network. If not, see . +*/ + +pragma solidity 0.8.25; +pragma experimental "ABIEncoderV2"; + +interface IColonyBridge { + /// @notice Function that checks whether a chain with the supplied evmChainId is supported + /// @param _evmChainId The chain id to check + /// @return bool Whether the chain is supported + function supportedEvmChainId(uint256 _evmChainId) external view returns (bool); + + /// @notice Function to set the colony network address that the bridge will interact with + /// @param _colonyNetwork The address of the colony network + function setColonyNetworkAddress(address _colonyNetwork) external; + + /// @notice Function to get the colony network address that the bridge is interacting with + /// @return address The address of the colony network + function colonyNetwork() external view returns (address); + + /// @notice Function to set the address of the instance of this contract on other chains, that + /// this contract will expect to receive messages from + /// @param _evmChainId The chain id to set the address for + /// @param _colonyBridge The address of the colony bridge contract on the other chain + function setColonyBridgeAddress(uint256 _evmChainId, address _colonyBridge) external; + + /// @notice Function to get the address of the instance of this contract on other chains + /// @param evmChainId The chain id to get the address for + function getColonyBridgeAddress(uint256 evmChainId) external view returns (address); + + /// @notice Function to send a message to the colony bridge on another chain + /// @param evmChainId The chain id to send the message to + /// @param payload The message payload + /// @return bool Whether the message was sent successfully (to the best of the contract's knowledge, + /// in terms of the underlying bridge implementation) + function sendMessage(uint256 evmChainId, bytes memory payload) external returns (bool); +} diff --git a/contracts/bridging/WormholeBridgeForColony.sol b/contracts/bridging/WormholeBridgeForColony.sol new file mode 100644 index 0000000000..c123ff9efd --- /dev/null +++ b/contracts/bridging/WormholeBridgeForColony.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + This file is part of The Colony Network. + + The Colony Network is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The Colony Network is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with The Colony Network. If not, see . +*/ + +pragma solidity 0.8.25; + +import { IWormhole } from "../../lib/wormhole/ethereum/contracts/interfaces/IWormhole.sol"; +import { IColonyNetwork } from "../colonyNetwork/IColonyNetwork.sol"; +import { IColonyBridge } from "./IColonyBridge.sol"; +import { CallWithGuards } from "../common/CallWithGuards.sol"; +import { DSAuth } from "../../lib/dappsys/auth.sol"; + +contract WormholeBridgeForColony is DSAuth, IColonyBridge, CallWithGuards { + address public colonyNetwork; + IWormhole public wormhole; + + // ChainId => colonyBridge + mapping(uint256 => address) colonyBridges; + + // Maps evm chain id to wormhole chain id + mapping(uint256 => uint16) public evmChainIdToWormholeChainId; + + modifier onlyColonyNetwork() { + require(msg.sender == colonyNetwork, "wormhole-bridge-only-colony-network"); + _; + } + + function setChainIdMapping( + uint256[] calldata evmChainIds, + uint16[] calldata wormholeChainIds + ) public auth { + require( + evmChainIds.length == wormholeChainIds.length, + "colony-bridge-chainid-mapping-length-mismatch" + ); + for (uint256 i = 0; i < evmChainIds.length; i++) { + evmChainIdToWormholeChainId[evmChainIds[i]] = wormholeChainIds[i]; + } + } + + function supportedEvmChainId(uint256 _evmChainId) public view returns (bool) { + return evmChainIdToWormholeChainId[_evmChainId] != 0; + } + + function setWormholeAddress(address _wormhole) public auth { + wormhole = IWormhole(_wormhole); + } + + function setColonyNetworkAddress(address _colonyNetwork) public auth { + colonyNetwork = _colonyNetwork; + } + + function setColonyBridgeAddress(uint256 _evmChainId, address _bridgeAddress) public auth { + require(_evmChainId <= type(uint128).max, "colony-bridge-chainid-too-large"); + uint16 requestedWormholeChainId = evmChainIdToWormholeChainId[_evmChainId]; + colonyBridges[requestedWormholeChainId] = _bridgeAddress; + } + + function getColonyBridgeAddress(uint256 evmChainId) public view returns (address) { + uint16 requestedWormholeChainId = evmChainIdToWormholeChainId[evmChainId]; + return colonyBridges[requestedWormholeChainId]; + } + + function wormholeAddressToEVMAddress( + bytes32 _wormholeFormatAddress + ) public pure returns (address) { + return address(uint160(uint256(_wormholeFormatAddress))); + } + + function receiveMessage(bytes memory _vaa) public { + // VAAs are the primitives used on wormhole (Verified Action Approvals) + // See https://docs.wormhole.com/wormhole/explore-wormhole/vaa for more details + // Note that the documentation sometimes also calls them VMs (as does IWormhole) + // I believe VM stands for 'Verified Message' + (IWormhole.VM memory wormholeMessage, bool valid, string memory reason) = wormhole + .parseAndVerifyVM(_vaa); + + // Check the vaa was valid + require(valid, reason); + + // Check came from a known colony bridge + require( + wormholeAddressToEVMAddress(wormholeMessage.emitterAddress) == + colonyBridges[wormholeMessage.emitterChainId], + "colony-bridge-bridged-tx-only-from-colony-bridge" + ); + + // We ignore sequence numbers - bridging out of order is okay, because we have our own way of handling that + + // Make the call requested to the colony network + (bool success, bytes memory returndata) = callWithGuards( + colonyNetwork, + wormholeMessage.payload + ); + + // Note that this is not a require because returndata might not be a string, and if we try + // to decode it we'll get a revert. + if (!success) { + revert(abi.decode(returndata, (string))); + } + } + + function sendMessage( + uint256 _evmChainId, + bytes memory _payload + ) public onlyColonyNetwork returns (bool) { + require(supportedEvmChainId(_evmChainId), "colony-bridge-not-known-chain"); + // This returns a sequence, but we don't care about it + // The first sequence ID is, I believe 0, so all return values are potentially valid + // slither-disable-next-line unused-return + try wormhole.publishMessage(0, _payload, 0) { + return true; + } catch { + return false; + } + } +} diff --git a/contracts/colony/Colony.sol b/contracts/colony/Colony.sol index d7d122bfe2..7996f35bab 100755 --- a/contracts/colony/Colony.sol +++ b/contracts/colony/Colony.sol @@ -166,23 +166,6 @@ contract Colony is BasicMetaTransaction, Multicall, ColonyStorage, PatriciaTreeP emit TokensMinted(msgSender(), _guy, _wad); } - function mintTokensForColonyNetwork(uint _wad) public stoppable { - // Only the colony Network can call this function - require(msgSender() == colonyNetworkAddress, "colony-access-denied-only-network-allowed"); - // Function only valid on the Meta Colony - require( - address(this) == IColonyNetwork(colonyNetworkAddress).getMetaColony(), - "colony-access-denied-only-meta-colony-allowed" - ); - // Not callable on Xdai - require(!isXdai(), "colony-network-forbidden-on-xdai"); - - ERC20Extended(token).mint(_wad); - assert(ERC20Extended(token).transfer(colonyNetworkAddress, _wad)); - - emit TokensMinted(msgSender(), colonyNetworkAddress, _wad); - } - function registerColonyLabel( string memory colonyName, string memory orbitdb @@ -210,6 +193,22 @@ contract Colony is BasicMetaTransaction, Multicall, ColonyStorage, PatriciaTreeP IColonyNetwork(colonyNetworkAddress).addColonyVersion(_version, _resolver); } + function setColonyBridgeAddress(address _bridgeAddress) public stoppable auth { + IColonyNetwork(colonyNetworkAddress).setColonyBridgeAddress(_bridgeAddress); + } + + function initialiseReputationMining( + uint256 miningChainId, + bytes32 newHash, + uint256 newNLeaves + ) public stoppable auth { + IColonyNetwork(colonyNetworkAddress).initialiseReputationMining( + miningChainId, + newHash, + newNLeaves + ); + } + function addExtensionToNetwork(bytes32 _extensionId, address _resolver) public stoppable auth { IColonyNetwork(colonyNetworkAddress).addExtensionToNetwork(_extensionId, _resolver); } @@ -312,6 +311,12 @@ contract Colony is BasicMetaTransaction, Multicall, ColonyStorage, PatriciaTreeP sig = bytes4(keccak256("finalizeExpenditureViaArbitration(uint256,uint256,uint256)")); colonyAuthority.setRoleCapability(uint8(ColonyRole.Arbitration), address(this), sig, true); + + sig = bytes4(keccak256("setColonyBridgeAddress(address)")); + colonyAuthority.setRoleCapability(uint8(ColonyRole.Root), address(this), sig, true); + + sig = bytes4(keccak256("initialiseReputationMining(uint256,bytes32,uint256)")); + colonyAuthority.setRoleCapability(uint8(ColonyRole.Root), address(this), sig, true); } function getMetatransactionNonce(address _user) public view override returns (uint256 nonce) { diff --git a/contracts/colony/ColonyAuthority.sol b/contracts/colony/ColonyAuthority.sol index ab7a9970b6..092eeca836 100644 --- a/contracts/colony/ColonyAuthority.sol +++ b/contracts/colony/ColonyAuthority.sol @@ -132,6 +132,8 @@ contract ColonyAuthority is CommonAuthority { // Added in colony v15 (hazel-lwss-2) addRoleCapability(ARBITRATION_ROLE, "cancelExpenditureViaArbitration(uint256,uint256,uint256)"); addRoleCapability(ARBITRATION_ROLE, "finalizeExpenditureViaArbitration(uint256,uint256,uint256)"); + addRoleCapability(ROOT_ROLE, "setColonyBridgeAddress(address)"); + addRoleCapability(ROOT_ROLE, "initialiseReputationMining(uint256,bytes32,uint256)"); } function addRoleCapability(uint8 role, bytes memory sig) private { diff --git a/contracts/colony/IMetaColony.sol b/contracts/colony/IMetaColony.sol index 4c4d3a70e1..2bdc38f97c 100644 --- a/contracts/colony/IMetaColony.sol +++ b/contracts/colony/IMetaColony.sol @@ -22,11 +22,6 @@ pragma experimental "ABIEncoderV2"; import { IColony } from "./IColony.sol"; interface IMetaColony is IColony { - /// @notice Mints CLNY in the Meta Colony and transfers them to the colony network. - /// Only allowed to be called on the Meta Colony by the colony network. - /// @param _wad Amount to mint and transfer to the colony network - function mintTokensForColonyNetwork(uint256 _wad) external; - /// @notice Set the Colony Network fee inverse amount. /// @dev Calls `IColonyNetwork.setFeeInverse`. /// @param _feeInverse Nonzero amount for the fee inverse @@ -54,4 +49,21 @@ interface IMetaColony is IColony { /// @param _extensionId keccak256 hash of the extension name, used as an indentifier /// @param _resolver The deployed resolver containing the extension contract logic function addExtensionToNetwork(bytes32 _extensionId, address _resolver) external; + + // @notice Called to set the address of the colony bridge contract + /// @param _bridgeAddress The address of the bridge + function setColonyBridgeAddress(address _bridgeAddress) external; + + /// @notice Creates initial inactive reputation mining cycle. + /// @dev Only callable from metacolony + /// @param miningChainId The chainId of the chain the mining cycle is being created on + /// Can either be this chain or another chain, and the function will behave differently depending + /// on which is the case. + /// @param newHash The root hash of the reputation state tree + /// @param newNLeaves The number of leaves in the state tree + function initialiseReputationMining( + uint256 miningChainId, + bytes32 newHash, + uint256 newNLeaves + ) external; } diff --git a/contracts/colonyNetwork/ColonyNetwork.sol b/contracts/colonyNetwork/ColonyNetwork.sol index 8de117c4d5..137b05a1ae 100644 --- a/contracts/colonyNetwork/ColonyNetwork.sol +++ b/contracts/colonyNetwork/ColonyNetwork.sol @@ -101,6 +101,13 @@ contract ColonyNetwork is BasicMetaTransaction, ColonyNetworkStorage, Multicall colonyVersionResolver[_version] = _resolver; currentColonyVersion = _version; + // Skills on not-xdai chains are prefixed by the chain id + // Xdai was grandfathered in thinking it was the 'only' deployment and has no prefix, predating + // when this was a consideration + if (!isXdai()) { + skillCount = toRootSkillId(block.chainid); + } + emit ColonyNetworkInitialised(_resolver); } diff --git a/contracts/colonyNetwork/ColonyNetworkAuction.sol b/contracts/colonyNetwork/ColonyNetworkAuction.sol index 300190a15f..9be4137de5 100644 --- a/contracts/colonyNetwork/ColonyNetworkAuction.sol +++ b/contracts/colonyNetwork/ColonyNetworkAuction.sol @@ -25,7 +25,7 @@ import { ERC20Extended } from "./../common/ERC20Extended.sol"; import { IMetaColony } from "./../colony/IMetaColony.sol"; import { DSMath } from "./../../lib/dappsys/math.sol"; -contract ColonyNetworkAuction is ColonyNetworkStorage, MultiChain { +contract ColonyNetworkAuction is ColonyNetworkStorage { function startTokenAuction(address _token) public stoppable auth { require(_token != address(0x0), "colony-auction-invalid-token"); @@ -43,15 +43,15 @@ contract ColonyNetworkAuction is ColonyNetworkStorage, MultiChain { if (_token == clny) { // We don't auction CLNY. We just burn it instead. // Note we can do this more often than every 30 days. - if (isXdai()) { - // On Xdai, we can't burn bridged tokens + if (isMainnet()) { + ERC20Extended(clny).burn(availableTokens); + } else { + // Elsewhere, we can't burn bridged tokens // so let's send them to the metacolony for now. require( ERC20Extended(clny).transfer(metaColony, availableTokens), "colony-network-transfer-failed" ); - } else { - ERC20Extended(clny).burn(availableTokens); } return; } @@ -243,15 +243,15 @@ contract DutchAuction is DSMath, MultiChain, BasicMetaTransaction { finalized = true; // Burn all CLNY received - if (isXdai()) { + if (isMainnet()) { + clnyToken.burn(receivedTotal); + } else { // On Xdai, we can't burn bridged tokens // so let's send them to the metacolony for now. require( clnyToken.transfer(metaColonyAddress, receivedTotal), "colony-network-transfer-failed" ); - } else { - clnyToken.burn(receivedTotal); } emit AuctionFinalized(finalPrice); } diff --git a/contracts/colonyNetwork/ColonyNetworkDataTypes.sol b/contracts/colonyNetwork/ColonyNetworkDataTypes.sol index 1c3419af30..30e1d90225 100755 --- a/contracts/colonyNetwork/ColonyNetworkDataTypes.sol +++ b/contracts/colonyNetwork/ColonyNetworkDataTypes.sol @@ -50,7 +50,7 @@ interface ColonyNetworkDataTypes { /// @param metaColony Address of the MetaColony instance (i.e. EtherRouter) /// @param token Address of the associated CLNY token /// @param rootSkillId Id of the root skill of the global skills tree, normally this is 2 - /// Note that the speciat mining skill is created at rootSkillId + 1, so normally this is 3 + /// Note that the special mining skill is created at rootSkillId + 1, so normally this is 3 /// Skill id 1 is normally the local skill associate with meta colony domain 1 event MetaColonyCreated(address metaColony, address token, uint256 rootSkillId); @@ -67,6 +67,19 @@ interface ColonyNetworkDataTypes { /// @param parentSkillId The id of the parent skill under which this new skill is added event SkillAdded(uint256 skillId, uint256 parentSkillId); + /// @notice Event logged when bridging of a skill creation did not succeed. + /// @param skillId The skillId that failed to bridge + event SkillCreationStored(uint256 skillId); + + /// @notice Event logged when a skill is received from a bridge, but can't yet be + /// added to the skill tree. + /// @param skillId The skillId of the skill that was bridged + event SkillStoredFromBridge(uint256 skillId); + + /// @notice Event logged when a skill is successfully added from a bridge. + /// @param skillId The skillId of the skill that was bridged + event SkillAddedFromBridge(uint256 skillId); + /// @notice Event logged when a new auction is created and started /// @dev Emitted from `IColonyNetwork.startTokenAuction` function /// @param auction Address of the created auction contract @@ -147,6 +160,33 @@ interface ColonyNetworkDataTypes { /// @param tokenAuthorityAddress The address of the token authority deployed event TokenAuthorityDeployed(address tokenAuthorityAddress); + /// @notice Event logged when the colony network has data about a bridge contract set. + /// @param bridgeAddress The address of the bridge contract that will be interacted with + event BridgeSet(address bridgeAddress); + + /// @notice Event logged when bridging of a reputation update did not succeed. + /// @param colony The address of the colony where reputation is being emitted + /// @param count The number of the reputation update trying to be bridged in that colony + event ReputationUpdateStored(address colony, uint256 count); + + /// @notice Event logged when a reputation update makes it to the bridge. + /// @param colony The address of the colony where reputation is being emitted + /// @param count The number of the reputation update trying to be bridged in that colony + event ReputationUpdateSentToBridge(address colony, uint256 count); + + /// @notice Event logged when a reputation update is received from a bridge, but can't be + /// added to the reputation update log due to being bridged out of order or the skill not existing. + /// @param chainId The chainId of the chain the bridge is associated with + /// @param colony The address of the colony where reputation is being emitted + /// @param updateNumber The number of the reputation update bridged in that colony + event ReputationUpdateStoredFromBridge(uint256 chainId, address colony, uint256 updateNumber); + + /// @notice Event logged when a reputation update is successfully bridged. + /// @param chainId The chainId of the chain the bridge is associated with + /// @param colony The address of the colony where reputation is being emitted + /// @param updateNumber The number of the reputation update bridged in that colony + event ReputationUpdateAddedFromBridge(uint256 chainId, address colony, uint256 updateNumber); + struct Skill { // total number of parent skills uint128 nParents; @@ -169,7 +209,7 @@ interface ColonyNetworkDataTypes { struct ReputationLogEntry { address user; - int amount; + int256 amount; uint256 skillId; address colony; uint128 nUpdates; @@ -180,4 +220,12 @@ interface ColonyNetworkDataTypes { uint256 amount; uint256 timestamp; } + + struct PendingReputationUpdate { + address user; + int256 amount; + uint256 skillId; + address colony; + uint256 timestamp; + } } diff --git a/contracts/colonyNetwork/ColonyNetworkDeployer.sol b/contracts/colonyNetwork/ColonyNetworkDeployer.sol index fe63e48c62..032f4dc80e 100644 --- a/contracts/colonyNetwork/ColonyNetworkDeployer.sol +++ b/contracts/colonyNetwork/ColonyNetworkDeployer.sol @@ -33,8 +33,8 @@ contract ColonyNetworkDeployer is ColonyNetworkStorage { metaColony = createColony(_tokenAddress, currentColonyVersion, "", ""); - // Add the special mining skill, parent is the root domain - reputationMiningSkillId = IColonyNetwork(address(this)).addSkill(skillCount - 1); + // The mining skill used to be created here, but with the move to + // multi-chain, it now happens in initialiseReputationMining emit MetaColonyCreated(metaColony, _tokenAddress, skillCount); } @@ -141,15 +141,18 @@ contract ColonyNetworkDeployer is ColonyNetworkStorage { DSAuth dsauth = DSAuth(etherRouter); dsauth.setAuthority(colonyAuthority); - colonyAuthority.setOwner(address(etherRouter)); - // Initialise the domain tree with defaults by just incrementing the skillCount - skillCount += 1; colonyCount += 1; colonies[colonyCount] = address(colony); _isColony[address(colony)] = true; + // Initialise the domain tree with defaults by just incrementing the skillCount + skillCount += 1; + + // If we're not mining chain, then bridge the skill + IColonyNetwork(address(this)).bridgeSkillIfNotMiningChain(skillCount); + colony.initialiseColony(address(this), _tokenAddress); emit ColonyAdded(colonyCount, address(etherRouter), _tokenAddress); diff --git a/contracts/colonyNetwork/ColonyNetworkENS.sol b/contracts/colonyNetwork/ColonyNetworkENS.sol index 46fb02b63c..311f7767c1 100644 --- a/contracts/colonyNetwork/ColonyNetworkENS.sol +++ b/contracts/colonyNetwork/ColonyNetworkENS.sol @@ -22,7 +22,7 @@ import { ENS } from "./../ens/ENS.sol"; import { ColonyNetworkStorage } from "./ColonyNetworkStorage.sol"; import { MultiChain } from "./../common/MultiChain.sol"; -contract ColonyNetworkENS is ColonyNetworkStorage, MultiChain { +contract ColonyNetworkENS is ColonyNetworkStorage { bytes32 constant USER_HASH = keccak256("user"); bytes32 constant COLONY_HASH = keccak256("colony"); diff --git a/contracts/colonyNetwork/ColonyNetworkMining.sol b/contracts/colonyNetwork/ColonyNetworkMining.sol index 079a8bf86d..a6d1821268 100644 --- a/contracts/colonyNetwork/ColonyNetworkMining.sol +++ b/contracts/colonyNetwork/ColonyNetworkMining.sol @@ -26,8 +26,11 @@ import { IReputationMiningCycle } from "./../reputationMiningCycle/IReputationMi import { ITokenLocking } from "./../tokenLocking/ITokenLocking.sol"; import { ColonyNetworkStorage } from "./ColonyNetworkStorage.sol"; import { IMetaColony } from "./../colony/IMetaColony.sol"; +import { IColonyBridge } from "./../bridging/IColonyBridge.sol"; +import { IColonyNetwork } from "./IColonyNetwork.sol"; +import { ColonyDataTypes } from "./../colony/ColonyDataTypes.sol"; -contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain { +contract ColonyNetworkMining is ColonyNetworkStorage { // TODO: Can we handle a dispute regarding the very first hash that should be set? modifier onlyReputationMiningCycle() { @@ -38,7 +41,7 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain { _; } - function setMiningDelegate(address _delegate, bool _allowed) public stoppable { + function setMiningDelegate(address _delegate, bool _allowed) public onlyMiningChain stoppable { if (miningDelegators[_delegate] != address(0x00)) { require( miningDelegators[_delegate] == msgSender(), @@ -66,7 +69,7 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain { address _colony, uint128 _nUpdates, uint128 _nPreviousUpdates - ) public recovery auth { + ) public onlyMiningChain recovery auth { replacementReputationUpdateLogsExist[_reputationMiningCycle] = true; replacementReputationUpdateLog[_reputationMiningCycle][_id] = ReputationLogEntry( @@ -92,11 +95,48 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain { return replacementReputationUpdateLogsExist[_reputationMiningCycle]; } + // Well this is a weird hack to need + function newAddressArray() internal pure returns (address[] memory) {} + + function setReputationRootHashFromBridge( + bytes32 _newHash, + uint256 _newNLeaves, + uint256 _nonce + ) public stoppable onlyNotMiningChain onlyColonyBridge { + require( + _nonce >= bridgeCurrentRootHashNonces[block.chainid], + "colony-mining-bridge-invalid-nonce" + ); + bridgeCurrentRootHashNonces[block.chainid] = _nonce; + reputationRootHash = _newHash; + reputationRootHashNLeaves = _newNLeaves; + + emit ReputationRootHashSet(_newHash, _newNLeaves, newAddressArray(), 0); + } + + function bridgeCurrentRootHash(uint256 _chainId) public onlyMiningChain stoppable { + require(colonyBridgeAddress != address(0x0), "colony-network-bridge-not-set"); + + bridgeCurrentRootHashNonces[_chainId] += 1; + + bytes memory payload = abi.encodeWithSignature( + "setReputationRootHashFromBridge(bytes32,uint256,uint256)", + reputationRootHash, + reputationRootHashNLeaves, + bridgeCurrentRootHashNonces[_chainId] + ); + + // slither-disable-next-line unchecked-lowlevel + bool success = IColonyBridge(colonyBridgeAddress).sendMessage(_chainId, payload); + // We require success so estimation calls can tell us if bridging is going to work + require(success, "colony-mining-bridge-call-failed"); + } + function setReputationRootHash( bytes32 newHash, uint256 newNLeaves, address[] memory stakers - ) public stoppable onlyReputationMiningCycle { + ) public onlyMiningChain stoppable onlyReputationMiningCycle { reputationRootHash = newHash; reputationRootHashNLeaves = newNLeaves; // Reward stakers @@ -108,31 +148,59 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain { } // slither-disable-next-line reentrancy-no-eth - function initialiseReputationMining() public stoppable { + function initialiseReputationMining( + uint256 _reputationMiningChainId, + bytes32 _newHash, + uint256 _newNLeaves + ) public stoppable calledByMetaColony { + // NOTE: this function deliberately does not support moving away from the current chain. require( - inactiveReputationMiningCycle == address(0x0), + (reputationMiningChainId == 0) || // Either it's the first time setting it + // Or we're moving from a chain that's not this chain to another chain that's not this one + (reputationMiningChainId != block.chainid && _reputationMiningChainId != block.chainid), "colony-reputation-mining-already-initialised" ); + reputationMiningChainId = _reputationMiningChainId; + + if (reputationMiningChainId != block.chainid) { + // Reputation mining is on another chain, so we will need to set up the bridge + // But we're done here for now other than making sure everything is unset + reputationMiningSkillId = 0; + inactiveReputationMiningCycle = address(0x0); + activeReputationMiningCycle = address(0x0); + return; + } + address clnyToken = IMetaColony(metaColony).getToken(); require(clnyToken != address(0x0), "colony-reputation-mining-clny-token-invalid-address"); + // Add the special mining skill. Note that if moving away from this chain, + // and then back, a new mining skill will be created. It is an open question + // whether this is desireable behaviour. + ColonyDataTypes.Domain memory d = IMetaColony(metaColony).getDomain(1); + + reputationMiningSkillId = IColonyNetwork(address(this)).addSkill(d.skillId); + EtherRouter e = new EtherRouter(); e.setResolver(miningCycleResolver); inactiveReputationMiningCycle = address(e); IReputationMiningCycle(inactiveReputationMiningCycle).initialise(tokenLocking, clnyToken); emit ReputationMiningInitialised(inactiveReputationMiningCycle); + + reputationRootHash = _newHash; + reputationRootHashNLeaves = _newNLeaves; + + emit ReputationRootHashSet(_newHash, _newNLeaves, newAddressArray(), 0); + + startNextCycle(); } // slither-disable-next-line reentrancy-no-eth - function startNextCycle() public stoppable { + function startNextCycle() public onlyMiningChain stoppable { address clnyToken = IMetaColony(metaColony).getToken(); require(clnyToken != address(0x0), "colony-reputation-mining-clny-token-invalid-address"); require(activeReputationMiningCycle == address(0x0), "colony-reputation-mining-still-active"); - require( - inactiveReputationMiningCycle != address(0x0), - "colony-reputation-mining-not-initialised" - ); // Inactive now becomes active activeReputationMiningCycle = inactiveReputationMiningCycle; IReputationMiningCycle(activeReputationMiningCycle).resetWindow(); @@ -205,11 +273,6 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain { } // II. Disburse reputation and tokens - // On Xdai, we can only use bridged tokens, so no minting - if (!isXdai()) { - IMetaColony(metaColony).mintTokensForColonyNetwork(realReward); - } - // slither-disable-next-line unused-return ERC20Extended(clnyToken).approve(tokenLocking, realReward); @@ -234,7 +297,7 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain { function punishStakers( address[] memory _stakers, uint256 _amount - ) public stoppable onlyReputationMiningCycle { + ) public onlyMiningChain stoppable onlyReputationMiningCycle { address clnyToken = IMetaColony(metaColony).getToken(); uint256 lostStake; // Passing an array so that we don't incur the EtherRouter overhead for each staker if we looped over @@ -253,19 +316,22 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain { } } - function reward(address _recipient, uint256 _amount) public stoppable onlyReputationMiningCycle { + function reward( + address _recipient, + uint256 _amount + ) public onlyMiningChain stoppable onlyReputationMiningCycle { // TODO: Gain rep? pendingMiningRewards[_recipient] += _amount; } - function claimMiningReward(address _recipient) public stoppable { + function claimMiningReward(address _recipient) public onlyMiningChain stoppable { address clnyToken = IMetaColony(metaColony).getToken(); uint256 amount = pendingMiningRewards[_recipient]; pendingMiningRewards[_recipient] = 0; ITokenLocking(tokenLocking).transfer(clnyToken, amount, _recipient, true); } - function stakeForMining(uint256 _amount) public stoppable { + function stakeForMining(uint256 _amount) public onlyMiningChainOrDuringSetup stoppable { address clnyToken = IMetaColony(metaColony).getToken(); ITokenLocking(tokenLocking).approveStake(msgSender(), _amount, clnyToken); @@ -280,7 +346,7 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain { miningStakes[msgSender()].amount += _amount; } - function unstakeForMining(uint256 _amount) public stoppable { + function unstakeForMining(uint256 _amount) public onlyMiningChain stoppable { address clnyToken = IMetaColony(metaColony).getToken(); // Prevent those involved in a mining cycle withdrawing stake during the mining process. require( @@ -295,7 +361,9 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain { return miningStakes[_user]; } - function burnUnneededRewards(uint256 _amount) public stoppable onlyReputationMiningCycle { + function burnUnneededRewards( + uint256 _amount + ) public onlyMiningChain stoppable onlyReputationMiningCycle { // If there are no rewards to burn, no need to do anything if (_amount == 0) { return; @@ -303,19 +371,16 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain { address clnyToken = IMetaColony(metaColony).getToken(); ITokenLocking(tokenLocking).withdraw(clnyToken, _amount, true); - if (isXdai()) { - // On Xdai, I'm burning bridged tokens is certainly not what we want. - // So let's send them to the metacolony for now. - require( - ERC20Extended(clnyToken).transfer(metaColony, _amount), - "colony-network-transfer-failed" - ); - } else { - ERC20Extended(clnyToken).burn(_amount); - } + // We send tokens to the metacolony + require( + ERC20Extended(clnyToken).transfer(metaColony, _amount), + "colony-network-transfer-failed" + ); } - function setReputationMiningCycleReward(uint256 _amount) public stoppable calledByMetaColony { + function setReputationMiningCycleReward( + uint256 _amount + ) public onlyMiningChain stoppable calledByMetaColony { totalMinerRewardPerCycle = _amount; emit ReputationMiningRewardSet(_amount); @@ -347,7 +412,9 @@ contract ColonyNetworkMining is ColonyNetworkStorage, MultiChain { // slither-disable-end divide-before-multiply } - function setMiningResolver(address _miningResolver) public stoppable auth { + function setMiningResolver( + address _miningResolver + ) public stoppable onlyMiningChainOrDuringSetup auth { require(_miningResolver != address(0x0), "colony-mining-resolver-cannot-be-zero"); miningCycleResolver = _miningResolver; diff --git a/contracts/colonyNetwork/ColonyNetworkSkills.sol b/contracts/colonyNetwork/ColonyNetworkSkills.sol index c32c266572..52473b7990 100644 --- a/contracts/colonyNetwork/ColonyNetworkSkills.sol +++ b/contracts/colonyNetwork/ColonyNetworkSkills.sol @@ -21,19 +21,341 @@ pragma experimental "ABIEncoderV2"; import "./../reputationMiningCycle/IReputationMiningCycle.sol"; import "./../common/Multicall.sol"; import "./ColonyNetworkStorage.sol"; +import { IColonyBridge } from "./../bridging/IColonyBridge.sol"; +import { CallWithGuards } from "../common/CallWithGuards.sol"; -contract ColonyNetworkSkills is ColonyNetworkStorage, Multicall { +contract ColonyNetworkSkills is ColonyNetworkStorage, Multicall, CallWithGuards { // Skills function addSkill( - uint _parentSkillId + uint256 _parentSkillId ) public stoppable skillExists(_parentSkillId) allowedToAddSkill returns (uint256) { + skillCount += 1; + addSkillToChainTree(_parentSkillId, skillCount); + + bridgeSkillIfNotMiningChain(skillCount); + + return skillCount; + } + + function deprecateSkill( + uint256 _skillId, + bool _deprecated + ) public stoppable allowedToAddSkill returns (bool) { + require( + skills[_skillId].nParents == 0, + "colony-network-deprecate-local-skills-temporarily-disabled" + ); + bool changed = skills[_skillId].deprecated != _deprecated; + skills[_skillId].deprecated = _deprecated; + return changed; + } + + /// @notice @deprecated + function deprecateSkill(uint256 _skillId) public stoppable { + deprecateSkill(_skillId, true); + } + + function initialiseRootLocalSkill() public stoppable calledByColony returns (uint256) { + skillCount += 1; + // If we're not mining chain, then bridge the skill + bridgeSkillIfNotMiningChain(skillCount); + return skillCount; + } + + function appendReputationUpdateLog( + address _user, + int256 _amount, + uint256 _skillId + ) public stoppable calledByColony skillExists(_skillId) { + // We short-circut amount == 0 as it has no effect to save gas, and we ignore Address Zero because it will + // mess up the tracking of the total amount of reputation in a colony, as that's the key that it's + // stored under in the patricia/merkle tree. Colonies can still pay tokens out to it if they want, + // it just won't earn reputation. + if (_amount == 0 || _user == address(0x0)) { + return; + } + + if (isMiningChain()) { + appendReputationUpdateLogInternal(_user, _amount, _skillId, msgSender()); + } else { + bridgeReputationUpdateLog(_user, _amount, _skillId); + } + } + + // Bridging (sending) + + function setColonyBridgeAddress(address _bridgeAddress) public always calledByMetaColony { + // TODO: Move this somewhere else to guard against unsupported chainids + // require(_chainId <= type(uint128).max, "colony-network-chainid-too-large"); + + colonyBridgeAddress = _bridgeAddress; + // TODO: Move this to where the first + + emit BridgeSet(_bridgeAddress); + } + + function bridgeSkillIfNotMiningChain(uint256 _skillId) public stoppable skillExists(_skillId) { + if (isMiningChain()) { + return; + } + // Build the transaction we're going to send to the bridge to register the + // creation of this skill on the home chain + uint256 parentSkillId = skills[_skillId].parents.length == 0 + ? (toRootSkillId(block.chainid)) + : skills[_skillId].parents[0]; + + bytes memory payload = abi.encodeWithSignature( + "addSkillFromBridge(uint256,uint256)", + parentSkillId, + _skillId + ); + + // Send bridge transaction + bool success = callThroughBridgeWithGuards(payload); + + if (!success) { + // Skill creation is implicitly stored by the fact that skillCount has been incremented + emit SkillCreationStored(_skillId); + } + } + + function bridgePendingReputationUpdate( + address _colony, + uint256 _updateNumber + ) public stoppable onlyNotMiningChain { + require(colonyBridgeAddress != address(0x0), "colony-network-foreign-bridge-not-set"); + require( + pendingReputationUpdates[block.chainid][_colony][_updateNumber - 1].colony == address(0x00), + "colony-network-not-next-pending-update" + ); + + PendingReputationUpdate storage pendingUpdate = pendingReputationUpdates[block.chainid][ + _colony + ][_updateNumber]; + require(pendingUpdate.colony != address(0x00), "colony-network-update-does-not-exist"); + + int256 updateAmount = decayReputation(pendingUpdate.amount, pendingUpdate.timestamp); + + // Build the transaction we're going to send to the bridge + bytes memory payload = abi.encodeWithSignature( + "addReputationUpdateLogFromBridge(address,address,int256,uint256,uint256)", + pendingUpdate.colony, + pendingUpdate.user, + updateAmount, + pendingUpdate.skillId, + _updateNumber + ); + + delete pendingReputationUpdates[block.chainid][_colony][_updateNumber]; + + bool success = callThroughBridgeWithGuards(payload); + + require(success, "colony-network-bridging-tx-unsuccessful"); + + emit ReputationUpdateSentToBridge(_colony, _updateNumber); + } + + // Bridging (receiving) + + function addSkillFromBridge( + uint256 _parentSkillId, + uint256 _skillId + ) public always onlyMiningChain onlyColonyBridge { + uint256 bridgeChainId = toChainId(_skillId); + if (networkSkillCounts[bridgeChainId] == 0) { + // Initialise the skill count to match the foreign chain + networkSkillCounts[bridgeChainId] = toRootSkillId(bridgeChainId); + } + + require(networkSkillCounts[bridgeChainId] < _skillId, "colony-network-skill-already-added"); + + // Check skill count - if not next, then store for later. + if (networkSkillCounts[bridgeChainId] + 1 == _skillId) { + addSkillToChainTree(_parentSkillId, _skillId); + networkSkillCounts[bridgeChainId] += 1; + + emit SkillAddedFromBridge(_skillId); + } else { + require( + pendingSkillAdditions[bridgeChainId][_skillId] == 0, + "colony-network-skill-already-pending" + ); + + pendingSkillAdditions[bridgeChainId][_skillId] = _parentSkillId; + + emit SkillStoredFromBridge(_skillId); + } + } + + function addReputationUpdateLogFromBridge( + address _colony, + address _user, + int256 _amount, + uint256 _skillId, + uint256 _updateNumber + ) public stoppable onlyMiningChain onlyColonyBridge { + uint256 bridgeChainId = toChainId(_skillId); + + require( + reputationUpdateCount[bridgeChainId][_colony] < _updateNumber, + "colony-network-update-already-added" + ); + + // If next expected update, add to log + if ( + reputationUpdateCount[bridgeChainId][_colony] + 1 == _updateNumber && // It's the next reputation update for this colony + networkSkillCounts[bridgeChainId] >= _skillId // Skill has been bridged + ) { + reputationUpdateCount[bridgeChainId][_colony] += 1; + appendReputationUpdateLogInternal(_user, _amount, _skillId, _colony); + + emit ReputationUpdateAddedFromBridge(bridgeChainId, _colony, _updateNumber); + } else { + // Not next update, store for later + require( + pendingReputationUpdates[bridgeChainId][_colony][_updateNumber].timestamp == 0, + "colony-network-update-already-pending" + ); + pendingReputationUpdates[bridgeChainId][_colony][_updateNumber] = PendingReputationUpdate( + _user, + _amount, + _skillId, + _colony, + block.timestamp + ); + + emit ReputationUpdateStoredFromBridge(bridgeChainId, _colony, _updateNumber); + } + } + + function addPendingSkill(uint256 _skillId) public always onlyMiningChain { + uint256 bridgeChainId = toChainId(_skillId); + + // Require that specified skill is next + // Note this also implicitly checks that the chainId prefix of the skill is correct + require( + networkSkillCounts[bridgeChainId] + 1 == _skillId, + "colony-network-not-next-bridged-skill" + ); + + uint256 parentSkillId = pendingSkillAdditions[bridgeChainId][_skillId]; + require(parentSkillId != 0, "colony-network-no-such-bridged-skill"); + addSkillToChainTree(parentSkillId, _skillId); + networkSkillCounts[bridgeChainId] += 1; + + // Delete the pending addition + delete pendingSkillAdditions[bridgeChainId][_skillId]; + + emit SkillAddedFromBridge(_skillId); + } + + function addPendingReputationUpdate( + uint256 _chainId, + address _colony + ) public stoppable onlyMiningChain { + uint256 mostRecentUpdateNumber = reputationUpdateCount[_chainId][_colony]; + assert( + pendingReputationUpdates[_chainId][_colony][mostRecentUpdateNumber].colony == address(0x00) + ); + + PendingReputationUpdate storage pendingUpdate = pendingReputationUpdates[_chainId][_colony][ + mostRecentUpdateNumber + 1 + ]; + require(pendingUpdate.colony != address(0x00), "colony-network-next-update-does-not-exist"); + + // Skill creation must have been bridged + require( + networkSkillCounts[toChainId(pendingUpdate.skillId)] >= pendingUpdate.skillId, + "colony-network-invalid-skill-id" + ); + + reputationUpdateCount[_chainId][_colony] += 1; + address user = pendingUpdate.user; + uint256 skillId = pendingUpdate.skillId; + int256 updateAmount = decayReputation(pendingUpdate.amount, pendingUpdate.timestamp); + + delete pendingReputationUpdates[_chainId][_colony][mostRecentUpdateNumber + 1]; + + appendReputationUpdateLogInternal(user, updateAmount, skillId, _colony); + + emit ReputationUpdateAddedFromBridge(_chainId, _colony, mostRecentUpdateNumber + 1); + } + + // View + + function getParentSkillId( + uint256 _skillId, + uint256 _parentSkillIndex + ) public view returns (uint256) { + return ascendSkillTree(_skillId, _parentSkillIndex + 1); + } + + function getChildSkillId( + uint256 _skillId, + uint256 _childSkillIndex + ) public view returns (uint256) { + if (_childSkillIndex == UINT256_MAX) { + return _skillId; + } else { + Skill storage skill = skills[_skillId]; + require( + _childSkillIndex < skill.children.length, + "colony-network-out-of-range-child-skill-index" + ); + return skill.children[_childSkillIndex]; + } + } + + function getColonyBridgeAddress() public view returns (address) { + return colonyBridgeAddress; + } + + function getBridgedSkillCounts(uint256 _chainId) public view returns (uint256) { + if (networkSkillCounts[_chainId] == 0) { + return toRootSkillId(_chainId); + } + return networkSkillCounts[_chainId]; + } + + function getBridgedReputationUpdateCount( + uint256 _chainId, + address _colony + ) public view returns (uint256) { + return reputationUpdateCount[_chainId][_colony]; + } + + function getPendingSkillAddition( + uint256 _chainId, + uint256 _skillCount + ) public view returns (uint256) { + return pendingSkillAdditions[_chainId][_skillCount]; + } + + function getPendingReputationUpdate( + uint256 _chainId, + address _colony, + uint256 _updateNumber + ) public view onlyMiningChain returns (PendingReputationUpdate memory) { + return pendingReputationUpdates[_chainId][_colony][_updateNumber]; + } + + // Internal + + function addSkillToChainTree(uint256 _parentSkillId, uint256 _skillId) private { + // This indicates a new root local skill bridged from another chain, i.e. 0x{chainId}{0} + // We don't do anything to the tree in this scenario, other than incrementing the skill count, + // which should be/is done where this function is called. + // (this mirrors the behaviour of not calling addSkill() in initialiseRootLocalSkill) + if (_parentSkillId != 0 && _parentSkillId << 128 == 0) { + return; + } + require(_parentSkillId > 0, "colony-network-invalid-parent-skill"); Skill storage parentSkill = skills[_parentSkillId]; require(!parentSkill.DEPRECATED_globalSkill, "colony-network-no-global-skills"); - skillCount += 1; Skill memory s; s.nParents = parentSkill.nParents + 1; @@ -71,91 +393,130 @@ contract ColonyNetworkSkills is ColonyNetworkStorage, Multicall { } emit SkillAdded(skillCount, _parentSkillId); - return skillCount; } - function deprecateSkill( + function ascendSkillTree( uint256 _skillId, - bool _deprecated - ) public stoppable allowedToAddSkill returns (bool) { - require( - skills[_skillId].nParents == 0, - "colony-network-deprecate-local-skills-temporarily-disabled" - ); - bool changed = skills[_skillId].deprecated != _deprecated; - skills[_skillId].deprecated = _deprecated; - return changed; - } - - /// @notice @deprecated - function deprecateSkill(uint256 _skillId) public stoppable { - deprecateSkill(_skillId, true); - } + uint256 _parentSkillNumber + ) internal view returns (uint256) { + if (_parentSkillNumber == 0) { + return _skillId; + } - function initialiseRootLocalSkill() public stoppable calledByColony returns (uint256) { - skillCount++; - return skillCount; + Skill storage skill = skills[_skillId]; + for (uint256 i; i < skill.parents.length; i++) { + if (2 ** (i + 1) > _parentSkillNumber) { + uint256 _newSkillId = skill.parents[i]; + uint256 _newParentSkillNumber = _parentSkillNumber - 2 ** i; + return ascendSkillTree(_newSkillId, _newParentSkillNumber); + } + } } - function appendReputationUpdateLog( + function appendReputationUpdateLogInternal( address _user, - int _amount, - uint _skillId - ) public stoppable calledByColony skillExists(_skillId) { - if (_amount == 0 || _user == address(0x0)) { - // We short-circut amount=0 as it has no effect to save gas, and we ignore Address Zero because it will - // mess up the tracking of the total amount of reputation in a colony, as that's the key that it's - // stored under in the patricia/merkle tree. Colonies can still pay tokens out to it if they want, - // it just won't earn reputation. - return; - } - + int256 _amount, + uint256 _skillId, + address _colony + ) internal { uint128 nParents = skills[_skillId].nParents; // We only update child skill reputation if the update is negative, otherwise just set nChildren to 0 to save gas - uint128 nChildren = _amount < 0 ? skills[_skillId].nChildren : 0; + uint128 nChildren = (_amount < 0) ? skills[_skillId].nChildren : 0; IReputationMiningCycle(inactiveReputationMiningCycle).appendReputationUpdateLog( _user, _amount, _skillId, - msgSender(), + _colony, nParents, nChildren ); } - // View + function bridgeReputationUpdateLog(address _user, int256 _amount, uint256 _skillId) internal { + // TODO: Maybe force to be set on deployment? + require(colonyBridgeAddress != address(0x0), "colony-network-foreign-bridge-not-set"); + address colonyAddress = msgSender(); + reputationUpdateCount[block.chainid][colonyAddress] += 1; + // Build the transaction we're going to send to the bridge + bytes memory payload = abi.encodeWithSignature( + "addReputationUpdateLogFromBridge(address,address,int256,uint256,uint256)", + colonyAddress, + _user, + _amount, + _skillId, + reputationUpdateCount[block.chainid][colonyAddress] + ); - function getParentSkillId(uint _skillId, uint _parentSkillIndex) public view returns (uint256) { - return ascendSkillTree(_skillId, _parentSkillIndex + 1); - } + bool success = callThroughBridgeWithGuards(payload); - function getChildSkillId(uint _skillId, uint _childSkillIndex) public view returns (uint256) { - if (_childSkillIndex == UINT256_MAX) { - return _skillId; - } else { - Skill storage skill = skills[_skillId]; - require( - _childSkillIndex < skill.children.length, - "colony-network-out-of-range-child-skill-index" + if (success) { + emit ReputationUpdateSentToBridge( + colonyAddress, + reputationUpdateCount[block.chainid][colonyAddress] ); - return skill.children[_childSkillIndex]; + return; } + + // Store to resend later + PendingReputationUpdate memory pendingReputationUpdate = PendingReputationUpdate( + _user, + _amount, + _skillId, + msgSender(), + block.timestamp + ); + pendingReputationUpdates[block.chainid][colonyAddress][ + reputationUpdateCount[block.chainid][colonyAddress] + ] = pendingReputationUpdate; + + emit ReputationUpdateStored(colonyAddress, reputationUpdateCount[block.chainid][colonyAddress]); } - // Internal + // Mining cycle decay constants + // Note that these values and the mining window size (defined in ReputationMiningCycleCommon) + // need to be consistent with each other, but are not checked, in order for the decay + // rate to be as-expected. + int256 constant DECAY_NUMERATOR = 999679150010889; // 1-hr mining cycle + int256 constant DECAY_DENOMINATOR = 1000000000000000; + uint256 constant DECAY_PERIOD = 1 hours; - function ascendSkillTree(uint _skillId, uint _parentSkillNumber) internal view returns (uint256) { - if (_parentSkillNumber == 0) { - return _skillId; - } + function decayReputation( + int256 _reputation, + uint256 _since + ) internal view returns (int256 decayedReputation) { + uint256 decayEpochs = (block.timestamp - _since) / DECAY_PERIOD; + int256 adjustedNumerator = DECAY_NUMERATOR; - Skill storage skill = skills[_skillId]; - for (uint256 i; i < skill.parents.length; i++) { - if (2 ** (i + 1) > _parentSkillNumber) { - uint _newSkillId = skill.parents[i]; - uint _newParentSkillNumber = _parentSkillNumber - 2 ** i; - return ascendSkillTree(_newSkillId, _newParentSkillNumber); + // This algorithm successively doubles the decay factor while halving the number of epochs + // This allows us to perform the decay in O(log(n)) time + // For example, a decay of 50 epochs would be applied as (k**2)(k**16)(k**32) + while (decayEpochs > 0) { + // slither-disable-next-line weak-prng + if (decayEpochs % 2 >= 1) { + // slither-disable-next-line divide-before-multiply + _reputation = (_reputation * adjustedNumerator) / DECAY_DENOMINATOR; } + // slither-disable-next-line divide-before-multiply + adjustedNumerator = (adjustedNumerator * adjustedNumerator) / DECAY_DENOMINATOR; + decayEpochs >>= 1; + } + return _reputation; + } + + function callThroughBridgeWithGuards(bytes memory payload) internal returns (bool) { + bytes memory bridgePayload = abi.encodeWithSignature( + "sendMessage(uint256,bytes)", + getAndCacheReputationMiningChainId(), + payload + ); + + (bool success, bytes memory returnData) = callWithGuards(colonyBridgeAddress, bridgePayload); + + // If the function call was a success, and it returned true + if (success) { + bool res = abi.decode(returnData, (bool)); + return res; } + return false; } } diff --git a/contracts/colonyNetwork/ColonyNetworkStorage.sol b/contracts/colonyNetwork/ColonyNetworkStorage.sol index 01c57f4e30..ac640b70e3 100644 --- a/contracts/colonyNetwork/ColonyNetworkStorage.sol +++ b/contracts/colonyNetwork/ColonyNetworkStorage.sol @@ -21,13 +21,14 @@ pragma solidity 0.8.25; import { DSMath } from "./../../lib/dappsys/math.sol"; import { IMetaColony } from "./../colony/IMetaColony.sol"; import { CommonStorage } from "./../common/CommonStorage.sol"; +import { MultiChain } from "./../common/MultiChain.sol"; import { ERC20Extended } from "./../common/ERC20Extended.sol"; import { ColonyNetworkDataTypes } from "./ColonyNetworkDataTypes.sol"; // ignore-file-swc-131 // ignore-file-swc-108 -contract ColonyNetworkStorage is ColonyNetworkDataTypes, DSMath, CommonStorage { +contract ColonyNetworkStorage is ColonyNetworkDataTypes, DSMath, CommonStorage, MultiChain { // Number of colonies in the network uint256 colonyCount; // Storage slot 6 // uint256 version number of the latest deployed Colony contract, used in creating new colonies @@ -106,6 +107,30 @@ contract ColonyNetworkStorage is ColonyNetworkDataTypes, DSMath, CommonStorage { // Mining delegation mapping mapping(address => address) miningDelegators; // Storage slot 42 + uint256 reputationMiningChainId; // Storage slot 43 + + address colonyBridgeAddress; // Storage slot 44 + + mapping(uint256 => uint256) bridgeCurrentRootHashNonces; // Storage slot 45 + + // A mapping that maps chain id -> skill count + mapping(uint256 => uint256) networkSkillCounts; // Storage slot 46 + + // A mapping that stores pending bridged skill additions that have been bridged out-of-order + // chainId -> skillCount -> parentSkillId + mapping(uint256 => mapping(uint256 => uint256)) pendingSkillAdditions; // Storage slot 47 + + // A mapping that stores the latest reputation update received from a colony on a particular chain + // chainId -> colonyAddress -> updateCount + mapping(uint256 => mapping(address => uint256)) reputationUpdateCount; // Storage slot 48 + + // A mapping that stores reputation updates that haven't been added to the log yet, either because they've been + // received out of order, or because the skill in question hasn't been bridged yet. + // networkId -> colonyAddress -> updateCount -> update + mapping(uint256 => mapping(address => mapping(uint256 => PendingReputationUpdate))) pendingReputationUpdates; // Storage slot 49 + + // Modifiers + modifier calledByColony() { require(_isColony[msgSender()], "colony-caller-must-be-colony"); assert(msgSender() == msg.sender); @@ -128,8 +153,67 @@ contract ColonyNetworkStorage is ColonyNetworkDataTypes, DSMath, CommonStorage { _; } - modifier skillExists(uint skillId) { + modifier skillExists(uint256 skillId) { require(skillCount >= skillId, "colony-invalid-skill-id"); + require(toChainId(skillId) == block.chainid || isXdai(), "colony-invalid-skill-id"); _; } + + modifier onlyColonyBridge() { + require(msgSender() == colonyBridgeAddress, "colony-network-caller-must-be-colony-bridge"); + _; + } + + modifier onlyMiningChain() { + if (getMiningChainId() == block.chainid) { + require( + inactiveReputationMiningCycle != address(0x0), + "colony-reputation-mining-not-initialised" + ); + } + require(isMiningChain(), "colony-only-valid-on-mining-chain"); + _; + } + + modifier onlyMiningChainOrDuringSetup() { + require( + isMiningChain() || getMiningChainId() == 0, + "colony-only-valid-on-mining-chain-or-during-setup" + ); + _; + } + + modifier onlyNotMiningChain() { + require(!isMiningChain(), "colony-only-valid-not-on-mining-chain"); + _; + } + + // Internal functions + + function toRootSkillId(uint256 _chainId) internal pure returns (uint256) { + require(_chainId <= type(uint128).max, "colony-chain-id-too-large"); + return _chainId << 128; + } + + function toChainId(uint256 _skillId) internal pure returns (uint256) { + return _skillId >> 128; + } + + function isMiningChain() internal view returns (bool) { + return block.chainid == getMiningChainId(); + } + + function getMiningChainId() public view returns (uint256) { + if (reputationMiningChainId == 0 && isXdai()) { + return block.chainid; + } + return reputationMiningChainId; + } + + function getAndCacheReputationMiningChainId() internal returns (uint256) { + if (reputationMiningChainId == 0 && isXdai()) { + reputationMiningChainId = block.chainid; + } + return reputationMiningChainId; + } } diff --git a/contracts/colonyNetwork/IColonyNetwork.sol b/contracts/colonyNetwork/IColonyNetwork.sol index 0beef7e8a6..8b1cc46e3c 100644 --- a/contracts/colonyNetwork/IColonyNetwork.sol +++ b/contracts/colonyNetwork/IColonyNetwork.sol @@ -114,7 +114,7 @@ interface IColonyNetwork is ColonyNetworkDataTypes, IRecovery, IBasicMetaTransac /// @return _rootLocalSkillId The root local skill function initialiseRootLocalSkill() external returns (uint256 _rootLocalSkillId); - /// @notice Adds a reputation update entry to log. + /// @notice Adds a reputation update entry to the log. /// @dev Errors if it is called by anyone but a colony or if skill with id `_skillId` does not exist or. /// @param _user The address of the user for the reputation update /// @param _amount The amount of reputation change for the update, this can be a negative as well as a positive value @@ -295,7 +295,21 @@ interface IColonyNetwork is ColonyNetworkDataTypes, IRecovery, IBasicMetaTransac function startNextCycle() external; /// @notice Creates initial inactive reputation mining cycle. - function initialiseReputationMining() external; + /// @dev Only callable from metacolony + /// @param miningChainId The chainId of the chain the mining cycle is being created on + /// Can either be this chain or another chain, and the function will behave differently depending + /// on which is the case. + /// @param newHash The root hash of the reputation state tree + /// @param newNLeaves The number of leaves in the state tree + function initialiseReputationMining( + uint256 miningChainId, + bytes32 newHash, + uint256 newNLeaves + ) external; + + /// @notice Returns the chainId the network is expecting reputation mining to be one + /// @return reputationMiningChainId The chainId + function getMiningChainId() external view returns (uint256 reputationMiningChainId); /// @notice Get the root hash of the current reputation state tree. /// @return rootHash The current Reputation Root Hash @@ -508,4 +522,104 @@ interface IColonyNetwork is ColonyNetworkDataTypes, IRecovery, IBasicMetaTransac /// @param _delegate The address that wants to mine /// @return _delegator The address they are allowed to mine on behalf of function getMiningDelegator(address _delegate) external view returns (address _delegator); + + // @notice Called to set the address of the colony bridge contract + /// @param _bridgeAddress The address of the bridge + function setColonyBridgeAddress(address _bridgeAddress) external; + + /// @notice Called to get the next bridge in the list after bridge _bridgeAddress + /// @return bridge The address of the bridge to the mining chain, if set + function getColonyBridgeAddress() external view returns (address bridge); + + /// @notice Update the reputation on a foreign chain from the mining chain + /// @dev Should error if called by anyone other than the known bridge from the mining chain + /// @param newHash The new root hash + /// @param newNLeaves The new nLeaves in the root hash + /// @param nonce The nonce to ensure these txs can't be replayed + function setReputationRootHashFromBridge( + bytes32 newHash, + uint256 newNLeaves, + uint256 nonce + ) external; + + /// @notice Initiate a cross-chain update of the current reputation state + /// @param chainId The chainid we want to bridge to + function bridgeCurrentRootHash(uint256 chainId) external; + + /// @notice Called to re-send the bridging transaction for a skill to the + /// @param skillId The skillId we're bridging the creation of + function bridgeSkillIfNotMiningChain(uint256 skillId) external; + + /// @notice Function called by bridge transactions to add a new skill + /// @param _parentSkillId The parent id of the new skill + /// @param _skillCount The number of the new skill being created + function addSkillFromBridge(uint256 _parentSkillId, uint256 _skillCount) external; + + /// @notice Called to add a bridged skill that wasn't next when it was bridged, + /// but now is + /// @param _skillId The skillId of the skill being bridged + function addPendingSkill(uint256 _skillId) external; + + /// @notice Called to get the information about a skill that has been bridged out of order + /// @param _chainId The chainId we're bridging from + /// @param _skillCount The skill count + /// @return parentId The parent id of the skill being added + function getPendingSkillAddition( + uint256 _chainId, + uint256 _skillCount + ) external view returns (uint256 parentId); + + /// @notice Get the (currently bridged) skill count of another chain + /// @param _chainId The chainid of foreign chain + /// @return skillCount The skillCount of the corresponding chain + function getBridgedSkillCounts(uint256 _chainId) external view returns (uint256 skillCount); + + /// @notice Adds a reputation update entry to log. + /// @dev Errors if it is called by anyone but a known bridge + /// @param _colony The colony the reputation is being awarded in + /// @param _user The address of the user for the reputation update + /// @param _amount The amount of reputation change for the update, this can be a negative as well as a positive value + /// @param _skillId The skill for the reputation update + /// @param _updateNumber The counter used for ordering bridged updates + function addReputationUpdateLogFromBridge( + address _colony, + address _user, + int _amount, + uint _skillId, + uint256 _updateNumber + ) external; + + /// @notice Get the (currently bridged) reputation update count of a chain + /// @param _chainId The chainid of the chain + /// @param _colony The colony being queried + /// @return bridgedReputationCount The bridge reputation count of the corresponding chain + /// @dev On the non-mining chain, this tracks the number of reputation updates that have either been bridged, or attempted to + /// be bridged (and failed, and are now pending bridging). On the mining chain, it tracks how many have been successfully bridged + /// and added to the log. + function getBridgedReputationUpdateCount( + uint256 _chainId, + address _colony + ) external view returns (uint256 bridgedReputationCount); + + /// @notice Try to bridge a reputation update that (previously) failed + /// @param _colony The colony being queried + /// @param _updateNumber the emission index to bridge + function bridgePendingReputationUpdate(address _colony, uint256 _updateNumber) external; + + /// @notice Get the details of a reputation update that was bridged but was not added to the log because it was + /// bridged out of order + /// @param _chainId The chainId the update was bridged from + /// @param _colony The colony being queried + /// @param _updateNumber the updatenumber being queries + /// @return update The update stored for that chain/colony/updateNumber + function getPendingReputationUpdate( + uint256 _chainId, + address _colony, + uint256 _updateNumber + ) external view returns (PendingReputationUpdate memory update); + + /// @notice Try to emit the next reputation update that was bridged but previously failed, if any + /// @param _chainId The chainId the update was bridged from + /// @param _colony The colony being queried + function addPendingReputationUpdate(uint256 _chainId, address _colony) external; } diff --git a/contracts/common/BasicMetaTransaction.sol b/contracts/common/BasicMetaTransaction.sol index 199239292e..72ef780029 100644 --- a/contracts/common/BasicMetaTransaction.sol +++ b/contracts/common/BasicMetaTransaction.sol @@ -36,7 +36,7 @@ abstract contract BasicMetaTransaction is DSMath, MetaTransactionMsgSender, Mult uint8 _sigV ) public payable returns (bytes memory) { require( - verify(_user, getMetatransactionNonce(_user), getChainId(), _payload, _sigR, _sigS, _sigV), + verify(_user, getMetatransactionNonce(_user), block.chainid, _payload, _sigR, _sigS, _sigV), "metatransaction-signer-signature-mismatch" ); incrementMetatransactionNonce(_user); diff --git a/contracts/common/CallWithGuards.sol b/contracts/common/CallWithGuards.sol new file mode 100644 index 0000000000..4b5e0ff403 --- /dev/null +++ b/contracts/common/CallWithGuards.sol @@ -0,0 +1,35 @@ +pragma solidity 0.8.25; + +contract CallWithGuards { + function isContract(address addr) internal view returns (bool) { + uint256 size; + assembly { + size := extcodesize(addr) + } + return size > 0; + } + + function callWithGuards( + address _target, + bytes memory _payload + ) internal returns (bool, bytes memory) { + if (!isContract(_target)) { + return (false, abi.encode("require-execute-call-target-not-contract")); + } + (bool success, bytes memory returndata) = address(_target).call(_payload); + if (!success) { + // Stolen shamelessly from + // https://ethereum.stackexchange.com/questions/83528/how-can-i-get-the-revert-reason-of-a-call-in-solidity-so-that-i-can-use-it-in-th + // If the _res length is less than 68, then the transaction failed silently (without a revert message) + if (returndata.length >= 68) { + assembly { + // Slice the sighash. + returndata := add(returndata, 0x04) + } + return (false, returndata); // All that remains is the revert string + } + return (false, abi.encode("require-execute-call-reverted-with-no-error")); + } + return (success, returndata); + } +} diff --git a/contracts/common/MultiChain.sol b/contracts/common/MultiChain.sol index c645bd916a..64d44d72f1 100644 --- a/contracts/common/MultiChain.sol +++ b/contracts/common/MultiChain.sol @@ -19,29 +19,21 @@ pragma solidity 0.8.25; contract MultiChain { - function getChainId() public view returns (uint256) { - uint256 id; - assembly { - id := chainid() - } - return id; - } - // Prefixes of 265669 indicate a private forked version of the network // used for testing function isXdai() internal view returns (bool) { - uint256 chainId = getChainId(); + uint256 chainId = block.chainid; return (chainId == 100 || chainId == 265669100); } function isMainnet() internal view returns (bool) { - uint256 chainId = getChainId(); + uint256 chainId = block.chainid; return (chainId == 1 || chainId == 2656691); } function isGoerli() internal view returns (bool) { - uint256 chainId = getChainId(); + uint256 chainId = block.chainid; return (chainId == 5 || chainId == 2656695); } } diff --git a/contracts/extensions/CoinMachine.sol b/contracts/extensions/CoinMachine.sol index 59623f14d9..6f9a1f9cb7 100644 --- a/contracts/extensions/CoinMachine.sol +++ b/contracts/extensions/CoinMachine.sol @@ -102,7 +102,7 @@ contract CoinMachine is ColonyExtension, BasicMetaTransaction { /// @notice Returns the version of the extension /// @return _version The extension's version number function version() public pure override returns (uint256 _version) { - return 9; + return 10; } /// @notice Configures the extension diff --git a/contracts/extensions/ColonyExtension.sol b/contracts/extensions/ColonyExtension.sol index db83912b79..037f5b6f9d 100644 --- a/contracts/extensions/ColonyExtension.sol +++ b/contracts/extensions/ColonyExtension.sol @@ -27,8 +27,9 @@ import { IColony } from "./../colony/IColony.sol"; import { ColonyDataTypes } from "./../colony/ColonyDataTypes.sol"; import { IColonyNetwork } from "./../colonyNetwork/IColonyNetwork.sol"; import { PatriciaTreeProofs } from "./../patriciaTree/PatriciaTreeProofs.sol"; +import { MultiChain } from "./../common/MultiChain.sol"; -abstract contract ColonyExtension is DSAuth, DSMath, PatriciaTreeProofs, Multicall { +abstract contract ColonyExtension is DSAuth, DSMath, PatriciaTreeProofs, Multicall, MultiChain { uint256 constant UINT256_MAX = 2 ** 256 - 1; event ExtensionInitialised(); diff --git a/contracts/extensions/EvaluatedExpenditure.sol b/contracts/extensions/EvaluatedExpenditure.sol index fba50aa813..5af54c0dca 100644 --- a/contracts/extensions/EvaluatedExpenditure.sol +++ b/contracts/extensions/EvaluatedExpenditure.sol @@ -41,7 +41,7 @@ contract EvaluatedExpenditure is ColonyExtension, BasicMetaTransaction { /// @notice Returns the version of the extension /// @return _version The extension's version number function version() public pure override returns (uint256 _version) { - return 5; + return 6; } /// @notice Configures the extension diff --git a/contracts/extensions/FundingQueue.sol b/contracts/extensions/FundingQueue.sol index 129061b470..017cecc443 100644 --- a/contracts/extensions/FundingQueue.sol +++ b/contracts/extensions/FundingQueue.sol @@ -115,7 +115,7 @@ contract FundingQueue is ColonyExtension, BasicMetaTransaction { /// @notice Returns the version of the extension /// @return _version The extension's version number function version() public pure override returns (uint256 _version) { - return 6; + return 7; } /// @notice Configures the extension diff --git a/contracts/extensions/OneTxPayment.sol b/contracts/extensions/OneTxPayment.sol index 11030f3d4f..ac5faaadc8 100644 --- a/contracts/extensions/OneTxPayment.sol +++ b/contracts/extensions/OneTxPayment.sol @@ -56,7 +56,7 @@ contract OneTxPayment is ColonyExtension, BasicMetaTransaction { /// @notice Returns the version of the extension /// @return _version The extension's version number function version() public pure override returns (uint256 _version) { - return 6; + return 7; } /// @notice Configures the extension diff --git a/contracts/extensions/ReputationBootstrapper.sol b/contracts/extensions/ReputationBootstrapper.sol index 93a906a973..43023b2244 100644 --- a/contracts/extensions/ReputationBootstrapper.sol +++ b/contracts/extensions/ReputationBootstrapper.sol @@ -76,7 +76,7 @@ contract ReputationBootstrapper is ColonyExtensionMeta { /// @notice Returns the version of the extension function version() public pure override returns (uint256) { - return 3; + return 4; } /// @notice Configures the extension diff --git a/contracts/extensions/StagedExpenditure.sol b/contracts/extensions/StagedExpenditure.sol index ce870e9a1b..2a3d7ba0ce 100644 --- a/contracts/extensions/StagedExpenditure.sol +++ b/contracts/extensions/StagedExpenditure.sol @@ -45,7 +45,7 @@ contract StagedExpenditure is ColonyExtensionMeta, ColonyDataTypes { /// @notice Returns the version of the extension /// @return _version The extension's version number function version() public pure override returns (uint256 _version) { - return 1; + return 2; } /// @notice Configures the extension diff --git a/contracts/extensions/StakedExpenditure.sol b/contracts/extensions/StakedExpenditure.sol index d044b9bfe6..86ae1f35e1 100644 --- a/contracts/extensions/StakedExpenditure.sol +++ b/contracts/extensions/StakedExpenditure.sol @@ -68,7 +68,7 @@ contract StakedExpenditure is ColonyExtensionMeta { /// @notice Returns the version of the extension /// @return _version The extension's version number function version() public pure override returns (uint256 _version) { - return 4; + return 5; } /// @notice Configures the extension diff --git a/contracts/extensions/StreamingPayments.sol b/contracts/extensions/StreamingPayments.sol index c7e26b59ae..966a007cc5 100644 --- a/contracts/extensions/StreamingPayments.sol +++ b/contracts/extensions/StreamingPayments.sol @@ -117,7 +117,7 @@ contract StreamingPayments is ColonyExtensionMeta { /// @notice Returns the version of the extension /// @return _version The extension's version number function version() public pure override returns (uint256 _version) { - return 3; + return 4; } /// @notice Configures the extension diff --git a/contracts/extensions/TokenSupplier.sol b/contracts/extensions/TokenSupplier.sol index 9b3c7cccef..04ee1bc96e 100644 --- a/contracts/extensions/TokenSupplier.sol +++ b/contracts/extensions/TokenSupplier.sol @@ -74,7 +74,7 @@ contract TokenSupplier is ColonyExtension, BasicMetaTransaction { /// @notice Returns the version of the extension /// @return _version The extension's version number function version() public pure override returns (uint256 _version) { - return 6; + return 7; } /// @notice Configures the extension diff --git a/contracts/extensions/Whitelist.sol b/contracts/extensions/Whitelist.sol index 19af06a98b..f35c0af9b3 100644 --- a/contracts/extensions/Whitelist.sol +++ b/contracts/extensions/Whitelist.sol @@ -72,7 +72,7 @@ contract Whitelist is ColonyExtension, BasicMetaTransaction { /// @notice Returns the version of the extension /// @return _version The extension's version number function version() public pure override returns (uint256 _version) { - return 5; + return 6; } /// @notice Configures the extension diff --git a/contracts/extensions/votingReputation/VotingReputationStorage.sol b/contracts/extensions/votingReputation/VotingReputationStorage.sol index 3fe855aff6..8d74eb45b8 100644 --- a/contracts/extensions/votingReputation/VotingReputationStorage.sol +++ b/contracts/extensions/votingReputation/VotingReputationStorage.sol @@ -135,7 +135,7 @@ contract VotingReputationStorage is } function version() public pure override returns (uint256 _version) { - return 10; + return 11; } function install(address _colony) public override { diff --git a/contracts/reputationMiningCycle/IReputationMiningCycle.sol b/contracts/reputationMiningCycle/IReputationMiningCycle.sol index 1f13b25ed5..78c8b991cd 100644 --- a/contracts/reputationMiningCycle/IReputationMiningCycle.sol +++ b/contracts/reputationMiningCycle/IReputationMiningCycle.sol @@ -144,7 +144,7 @@ interface IReputationMiningCycle is ReputationMiningCycleDataTypes { /// * 24. A dummy variable that should be set to 0. If nonzero, transaction will still work but be slightly more expensive. For an explanation of why this is present, look at the corresponding solidity code. /// * 25. The value of the reputation that would be origin-adjacent that proves that the origin reputation does not exist in the tree /// * 26. The value of the reputation that would be child-adjacent that proves that the child reputation does not exist in the tree - /// @param _b32 A `bytes32[8]` array. The elements of this array, in order are: + /// @param _b32 A `bytes32[7]` array. The elements of this array, in order are: /// * 1. The colony address in the key of the reputation being changed that the disagreement is over. /// * 2. The skillid in the key of the reputation being changed that the disagreement is over. /// * 3. The user address in the key of the reputation being changed that the disagreement is over. diff --git a/contracts/reputationMiningCycle/ReputationMiningCycleRespond.sol b/contracts/reputationMiningCycle/ReputationMiningCycleRespond.sol index 2cdfaf8fc4..5834f2bda4 100644 --- a/contracts/reputationMiningCycle/ReputationMiningCycleRespond.sol +++ b/contracts/reputationMiningCycle/ReputationMiningCycleRespond.sol @@ -372,6 +372,7 @@ contract ReputationMiningCycleRespond is ReputationMiningCycleCommon { logEntry.skillId, relativeUpdateNumber ); + bytes memory childReputationKey = abi.encodePacked( logEntry.colony, expectedSkillId, diff --git a/contracts/testHelpers/BridgeMock.sol b/contracts/testHelpers/BridgeMock.sol index 089b64a665..2efe5d60d5 100644 --- a/contracts/testHelpers/BridgeMock.sol +++ b/contracts/testHelpers/BridgeMock.sol @@ -20,8 +20,11 @@ pragma solidity 0.8.25; contract BridgeMock { event UserRequestForSignature(bytes32 indexed messageId, bytes encodedData); + bool bridgeEnabled = true; + address public messageSender; function requireToPassMessage(address _target, bytes memory _data, uint256 _gasLimit) public { + require(bridgeEnabled, "bridge-not-working"); emit UserRequestForSignature( keccak256(abi.encodePacked(_target, _data, block.timestamp)), abi.encode(_target, _data, _gasLimit, msg.sender) @@ -37,16 +40,25 @@ contract BridgeMock { bytes32 _messageId, address _sender ) public { - bool success; - assembly { - // call contract at address a with input mem[in…(in+insize)) - // providing g gas and v wei and output area mem[out…(out+outsize)) - // returning 0 on error (eg. out of gas) and 1 on success - - // call(g, a, v, in, insize, out, outsize) - success := call(_gasLimit, _target, 0, add(_data, 0x20), mload(_data), 0, 0) + require(messageSender == address(0), "bridge-no-nested-calls"); + messageSender = _sender; + + (bool success, bytes memory returndata) = address(_target).call{ gas: _gasLimit }(_data); + + // call failed + if (!success) { + if (returndata.length == 0) revert(); + assembly { + revert(add(32, returndata), mload(returndata)) + } } + messageSender = address(0); + emit RelayedMessage(_sender, msg.sender, _messageId, success); } + + function setBridgeEnabled(bool val) public { + bridgeEnabled = val; + } } diff --git a/contracts/testHelpers/ChainId.sol b/contracts/testHelpers/ChainId.sol new file mode 100644 index 0000000000..ec165f8438 --- /dev/null +++ b/contracts/testHelpers/ChainId.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + This file is part of The Colony Network. + + The Colony Network is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The Colony Network is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with The Colony Network. If not, see . +*/ + +pragma solidity 0.8.25; + +contract ChainId { + function getChainId() public view returns (uint256) { + return block.chainid; + } +} diff --git a/contracts/testHelpers/RequireExecuteCall.sol b/contracts/testHelpers/RequireExecuteCall.sol index 341ee85940..cb39655387 100644 --- a/contracts/testHelpers/RequireExecuteCall.sol +++ b/contracts/testHelpers/RequireExecuteCall.sol @@ -19,23 +19,11 @@ pragma solidity 0.8.25; pragma experimental ABIEncoderV2; -contract RequireExecuteCall { +import { CallWithGuards } from "./../common/CallWithGuards.sol"; + +contract RequireExecuteCall is CallWithGuards { function executeCall(address target, bytes memory action) public { - bool success; - bytes memory returndata; - (success, returndata) = target.call(action); - if (!success) { - // Stolen shamelessly from - // https://ethereum.stackexchange.com/questions/83528/how-can-i-get-the-revert-reason-of-a-call-in-solidity-so-that-i-can-use-it-in-th - // If the _res length is less than 68, then the transaction failed silently (without a revert message) - if (returndata.length >= 68) { - assembly { - // Slice the sighash. - returndata := add(returndata, 0x04) - } - require(false, abi.decode(returndata, (string))); // All that remains is the revert string - } - require(false, "require-execute-call-reverted-with-no-error"); - } + (bool success, bytes memory returndata) = callWithGuards(target, action); + require(success, abi.decode(returndata, (string))); } } diff --git a/contracts/testHelpers/WormholeMock.sol b/contracts/testHelpers/WormholeMock.sol new file mode 100644 index 0000000000..3c86447f3b --- /dev/null +++ b/contracts/testHelpers/WormholeMock.sol @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +/* + This file is part of The Colony Network. + + The Colony Network is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + The Colony Network is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with The Colony Network. If not, see . +*/ + +pragma solidity 0.8.25; + +import { IWormhole } from "../../lib/wormhole/ethereum/contracts/interfaces/IWormhole.sol"; + +contract WormholeMock is IWormhole { + bool public bridgeEnabled = true; + uint64 cumulativeSequence = 0; + + bool vmResult = true; + string invalidVMReason = ""; + + function setVerifyVMResult(bool _valid, string memory _reason) public { + vmResult = _valid; + invalidVMReason = _reason; + } + + function buildVM( + uint8 version, + uint32 timestamp, + uint32 nonce, + uint16 emitterChainId, + bytes32 emitterAddress, + uint64 sequence, + uint8 consistencyLevel, + bytes memory payload, + uint32 guardianSetIndex, + Signature[] memory signatures, + bytes32 hash + ) external pure returns (bytes memory encodedVm) { + return + abi.encode( + VM( + version, + timestamp, + nonce, + emitterChainId, + emitterAddress, + sequence, + consistencyLevel, + payload, + guardianSetIndex, + signatures, + hash + ) + ); + } + + function parseAndVerifyVM( + bytes calldata encodedVM + ) external view returns (VM memory vm, bool valid, string memory reason) { + // For our mock wormhole contract, the encodedVM isn't a VAA, it's just appropriately packed data + + (vm) = abi.decode(encodedVM, (VM)); + + return (vm, vmResult, invalidVMReason); + } + + function publishMessage( + uint32 nonce, + bytes memory payload, + uint8 consistencyLevel + ) external payable returns (uint64 sequence) { + require(bridgeEnabled, "bridge-disabled"); + cumulativeSequence += 1; + + emit LogMessagePublished(msg.sender, sequence, nonce, payload, consistencyLevel); + return cumulativeSequence; + } + + function initialize() external {} + + function verifyVM(VM memory vm) external view returns (bool valid, string memory reason) {} + + function verifySignatures( + bytes32 hash, + Signature[] memory signatures, + GuardianSet memory guardianSet + ) external pure returns (bool valid, string memory reason) {} + + function parseVM(bytes memory encodedVM) external pure returns (VM memory vm) {} + + function quorum(uint numGuardians) external pure returns (uint numSignaturesRequiredForQuorum) {} + + function getGuardianSet(uint32 index) external view returns (GuardianSet memory) {} + + function getCurrentGuardianSetIndex() external view returns (uint32) {} + + function getGuardianSetExpiry() external view returns (uint32) {} + + function governanceActionIsConsumed(bytes32 hash) external view returns (bool) {} + + function isInitialized(address impl) external view returns (bool) {} + + function chainId() external view returns (uint16) { + return uint16(block.chainid % 265669); + } + + function isFork() external view returns (bool) {} + + function governanceChainId() external view returns (uint16) {} + + function governanceContract() external view returns (bytes32) {} + + function messageFee() external view returns (uint256) {} + + function evmChainId() external view returns (uint256) { + return block.chainid; + } + + function nextSequence(address emitter) external view returns (uint64) {} + + function parseContractUpgrade( + bytes memory encodedUpgrade + ) external pure returns (ContractUpgrade memory cu) {} + + function parseGuardianSetUpgrade( + bytes memory encodedUpgrade + ) external pure returns (GuardianSetUpgrade memory gsu) {} + + function parseSetMessageFee( + bytes memory encodedSetMessageFee + ) external pure returns (SetMessageFee memory smf) {} + + function parseTransferFees( + bytes memory encodedTransferFees + ) external pure returns (TransferFees memory tf) {} + + function parseRecoverChainId( + bytes memory encodedRecoverChainId + ) external pure returns (RecoverChainId memory rci) {} + + function submitContractUpgrade(bytes memory _vm) external {} + + function submitSetMessageFee(bytes memory _vm) external {} + + function submitNewGuardianSet(bytes memory _vm) external {} + + function submitTransferFees(bytes memory _vm) external {} + + function submitRecoverChainId(bytes memory _vm) external {} + + function setBridgeEnabled(bool val) public { + bridgeEnabled = val; + } +} diff --git a/docs/interfaces/icolonynetwork.md b/docs/interfaces/icolonynetwork.md index c0a350c566..06db5eef5b 100644 --- a/docs/interfaces/icolonynetwork.md +++ b/docs/interfaces/icolonynetwork.md @@ -36,6 +36,48 @@ Add a new extension resolver to the Extensions repository. |_resolver|address|The deployed resolver containing the extension contract logic +### ▸ `addPendingReputationUpdate(uint256 _chainId, address _colony)` + +Try to emit the next reputation update that was bridged but previously failed, if any + + +**Parameters** + +|Name|Type|Description| +|---|---|---| +|_chainId|uint256|The chainId the update was bridged from +|_colony|address|The colony being queried + + +### ▸ `addPendingSkill(uint256 _skillId)` + +Called to add a bridged skill that wasn't next when it was bridged, but now is + + +**Parameters** + +|Name|Type|Description| +|---|---|---| +|_skillId|uint256|The skillId of the skill being bridged + + +### ▸ `addReputationUpdateLogFromBridge(address _colony, address _user, int _amount, uint _skillId, uint256 _updateNumber)` + +Adds a reputation update entry to log. + +*Note: Errors if it is called by anyone but a known bridge* + +**Parameters** + +|Name|Type|Description| +|---|---|---| +|_colony|address|The colony the reputation is being awarded in +|_user|address|The address of the user for the reputation update +|_amount|int|The amount of reputation change for the update, this can be a negative as well as a positive value +|_skillId|uint|The skill for the reputation update +|_updateNumber|uint256|The counter used for ordering bridged updates + + ### ▸ `addSkill(uint256 _parentSkillId):uint256 _skillId` Adds a new skill to the domain or local skills tree, under skill `_parentSkillId`. Any colony is allowed to add a local skill and which is associated with a new domain via `IColony.addDomain`. @@ -54,6 +96,19 @@ Adds a new skill to the domain or local skills tree, under skill `_parentSkillId |---|---|---| |_skillId|uint256|Id of the added skill +### ▸ `addSkillFromBridge(uint256 _parentSkillId, uint256 _skillCount)` + +Function called by bridge transactions to add a new skill + + +**Parameters** + +|Name|Type|Description| +|---|---|---| +|_parentSkillId|uint256|The parent id of the new skill +|_skillCount|uint256|The number of the new skill being created + + ### ▸ `addr(bytes32 _node):address _address` Returns the address the supplied node resolves do, if we are the resolver. @@ -73,7 +128,7 @@ Returns the address the supplied node resolves do, if we are the resolver. ### ▸ `appendReputationUpdateLog(address _user, int256 _amount, uint256 _skillId)` -Adds a reputation update entry to log. +Adds a reputation update entry to the log. *Note: Errors if it is called by anyone but a colony or if skill with id `_skillId` does not exist or.* @@ -86,6 +141,43 @@ Adds a reputation update entry to log. |_skillId|uint256|The skill for the reputation update +### ▸ `bridgeCurrentRootHash(uint256 chainId)` + +Initiate a cross-chain update of the current reputation state + + +**Parameters** + +|Name|Type|Description| +|---|---|---| +|chainId|uint256|The chainid we want to bridge to + + +### ▸ `bridgePendingReputationUpdate(address _colony, uint256 _updateNumber)` + +Try to bridge a reputation update that (previously) failed + + +**Parameters** + +|Name|Type|Description| +|---|---|---| +|_colony|address|The colony being queried +|_updateNumber|uint256|the emission index to bridge + + +### ▸ `bridgeSkillIfNotMiningChain(uint256 skillId)` + +Called to re-send the bridging transaction for a skill to the + + +**Parameters** + +|Name|Type|Description| +|---|---|---| +|skillId|uint256|The skillId we're bridging the creation of + + ### ▸ `burnUnneededRewards(uint256 _amount)` Used to burn tokens that are not needed to pay out rewards (because not every possible defence was made for all submissions) @@ -333,6 +425,42 @@ Set deprecation status for a skill |---|---|---| |_changed|bool|Whether the deprecated state was changed +### ▸ `getBridgedReputationUpdateCount(uint256 _chainId, address _colony):uint256 bridgedReputationCount` + +Get the (currently bridged) reputation update count of a chain + +*Note: On the non-mining chain, this tracks the number of reputation updates that have either been bridged, or attempted to be bridged (and failed, and are now pending bridging). On the mining chain, it tracks how many have been successfully bridged and added to the log.* + +**Parameters** + +|Name|Type|Description| +|---|---|---| +|_chainId|uint256|The chainid of the chain +|_colony|address|The colony being queried + +**Return Parameters** + +|Name|Type|Description| +|---|---|---| +|bridgedReputationCount|uint256|The bridge reputation count of the corresponding chain + +### ▸ `getBridgedSkillCounts(uint256 _chainId):uint256 skillCount` + +Get the (currently bridged) skill count of another chain + + +**Parameters** + +|Name|Type|Description| +|---|---|---| +|_chainId|uint256|The chainid of foreign chain + +**Return Parameters** + +|Name|Type|Description| +|---|---|---| +|skillCount|uint256|The skillCount of the corresponding chain + ### ▸ `getChildSkillId(uint256 _skillId, uint256 _childSkillIndex):uint256 _childSkillId` Get the id of the child skill at index `_childSkillIndex` for skill with Id `_skillId`. @@ -368,6 +496,18 @@ Get a colony address by its Id in the network. |---|---|---| |_colonyAddress|address|The colony address, if no colony was found, returns 0x0 +### ▸ `getColonyBridgeAddress():address bridge` + +Called to get the next bridge in the list after bridge _bridgeAddress + + + +**Return Parameters** + +|Name|Type|Description| +|---|---|---| +|bridge|address|The address of the bridge to the mining chain, if set + ### ▸ `getColonyCount():uint256 _count` Get the number of colonies in the network. @@ -481,6 +621,18 @@ Get the Meta Colony address. |---|---|---| |_colonyAddress|address|The Meta colony address, if no colony was found, returns 0x0 +### ▸ `getMiningChainId():uint256 reputationMiningChainId` + +Returns the chainId the network is expecting reputation mining to be one + + + +**Return Parameters** + +|Name|Type|Description| +|---|---|---| +|reputationMiningChainId|uint256|The chainId + ### ▸ `getMiningDelegator(address _delegate):address _delegator` Called to get the address _delegate is allowed to mine for @@ -562,6 +714,43 @@ Get a token's status in the payout whitelist |---|---|---| |_status|bool|Will be `true` if token is whitelisted +### ▸ `getPendingReputationUpdate(uint256 _chainId, address _colony, uint256 _updateNumber):PendingReputationUpdate update` + +Get the details of a reputation update that was bridged but was not added to the log because it was bridged out of order + + +**Parameters** + +|Name|Type|Description| +|---|---|---| +|_chainId|uint256|The chainId the update was bridged from +|_colony|address|The colony being queried +|_updateNumber|uint256|the updatenumber being queries + +**Return Parameters** + +|Name|Type|Description| +|---|---|---| +|update|PendingReputationUpdate|The update stored for that chain/colony/updateNumber + +### ▸ `getPendingSkillAddition(uint256 _chainId, uint256 _skillCount):uint256 parentId` + +Called to get the information about a skill that has been bridged out of order + + +**Parameters** + +|Name|Type|Description| +|---|---|---| +|_chainId|uint256|The chainId we're bridging from +|_skillCount|uint256|The skill count + +**Return Parameters** + +|Name|Type|Description| +|---|---|---| +|parentId|uint256|The parent id of the skill being added + ### ▸ `getProfileDBAddress(bytes32 _node):string _orbitdb` Retrieve the orbitdb address corresponding to a registered account. @@ -748,11 +937,19 @@ Initialises the colony network by setting the first Colony version resolver to ` |_version|uint256|Version of the Colony contract the resolver represents -### ▸ `initialiseReputationMining()` +### ▸ `initialiseReputationMining(uint256 miningChainId, bytes32 newHash, uint256 newNLeaves)` Creates initial inactive reputation mining cycle. +*Note: Only callable from metacolony* +**Parameters** + +|Name|Type|Description| +|---|---|---| +|miningChainId|uint256|The chainId of the chain the mining cycle is being created on Can either be this chain or another chain, and the function will behave differently depending on which is the case. +|newHash|bytes32|The root hash of the reputation state tree +|newNLeaves|uint256|The number of leaves in the state tree ### ▸ `initialiseRootLocalSkill():uint256 _rootLocalSkillId` @@ -868,6 +1065,18 @@ Used to track that a user is eligible to claim a reward |_amount|uint256|The amount of CLNY to be awarded +### ▸ `setColonyBridgeAddress(address _bridgeAddress)` + +Called to set the address of the colony bridge contract + + +**Parameters** + +|Name|Type|Description| +|---|---|---| +|_bridgeAddress|address|The address of the bridge + + ### ▸ `setFeeInverse(uint256 _feeInverse)` Set the colony network fee to pay. e.g. if the fee is 1% (or 0.01), pass 100 as `_feeInverse`. @@ -965,6 +1174,21 @@ Set a new Reputation root hash and starts a new mining cycle. Can only be called |_stakers|address[]|Array of users who submitted or backed the hash, being accepted here as the new reputation root hash +### ▸ `setReputationRootHashFromBridge(bytes32 newHash, uint256 newNLeaves, uint256 nonce)` + +Update the reputation on a foreign chain from the mining chain + +*Note: Should error if called by anyone other than the known bridge from the mining chain* + +**Parameters** + +|Name|Type|Description| +|---|---|---| +|newHash|bytes32|The new root hash +|newNLeaves|uint256|The new nLeaves in the root hash +|nonce|uint256|The nonce to ensure these txs can't be replayed + + ### ▸ `setTokenLocking(address _tokenLockingAddress)` Sets the token locking address. This is only set once, and can't be changed afterwards. diff --git a/docs/interfaces/imetacolony.md b/docs/interfaces/imetacolony.md index 1ffd531ed4..4a1397c03e 100644 --- a/docs/interfaces/imetacolony.md +++ b/docs/interfaces/imetacolony.md @@ -35,16 +35,31 @@ Adds a new Colony contract version and the address of associated `_resolver` con |_resolver|address|Address of the `Resolver` contract which will be used with the underlying `EtherRouter` contract -### ▸ `mintTokensForColonyNetwork(uint256 _wad)` +### ▸ `initialiseReputationMining(uint256 miningChainId, bytes32 newHash, uint256 newNLeaves)` -Mints CLNY in the Meta Colony and transfers them to the colony network. Only allowed to be called on the Meta Colony by the colony network. +Creates initial inactive reputation mining cycle. +*Note: Only callable from metacolony* **Parameters** |Name|Type|Description| |---|---|---| -|_wad|uint256|Amount to mint and transfer to the colony network +|miningChainId|uint256|The chainId of the chain the mining cycle is being created on Can either be this chain or another chain, and the function will behave differently depending on which is the case. +|newHash|bytes32|The root hash of the reputation state tree +|newNLeaves|uint256|The number of leaves in the state tree + + +### ▸ `setColonyBridgeAddress(address _bridgeAddress)` + +Called to set the address of the colony bridge contract + + +**Parameters** + +|Name|Type|Description| +|---|---|---| +|_bridgeAddress|address|The address of the bridge ### ▸ `setNetworkFeeInverse(uint256 _feeInverse)` diff --git a/docs/interfaces/ireputationminingcycle.md b/docs/interfaces/ireputationminingcycle.md index 4133892364..ea6fe8d72e 100644 --- a/docs/interfaces/ireputationminingcycle.md +++ b/docs/interfaces/ireputationminingcycle.md @@ -390,7 +390,7 @@ Respond to challenge, to establish which (if either) of the two submissions faci |Name|Type|Description| |---|---|---| |_u|uint256[26]|A `uint256[27]` array. The elements of this array, in order are: * 1. The current round of the hash being responded on behalf of * 2. The current index in the round of the hash being responded on behalf of * 3. The branchMask of the proof that the reputation is in the reputation state tree for the reputation with the disputed change * 4. The number of leaves in the last reputation state that both submitted hashes agree on * 5. The branchMask of the proof that the last reputation state the submitted hashes agreed on is in this submitted hash's justification tree * 6. The number of leaves this hash considers to be present in the first reputation state the two hashes in this challenge disagree on * 7. The branchMask of the proof that reputation root hash of the first reputation state the two hashes in this challenge disagree on is in this submitted hash's justification tree * 8. The index of the log entry that the update in question was implied by. Each log entry can imply multiple reputation updates, and so we expect the clients to pass the log entry index corresponding to the update to avoid us having to iterate over the log. * 9. A dummy variable that should be set to 0. If nonzero, transaction will still work but be slightly more expensive. For an explanation of why this is present, look at the corresponding solidity code. * 10. Origin skill reputation branch mask. Nonzero for child reputation updates. * 11. The amount of reputation that the entry in the tree under dispute has in the agree state * 12. The UID that the entry in the tree under dispute has in the agree state * 13. The amount of reputation that the entry in the tree under dispute has in the disagree state * 14. The UID that the entry in the tree under dispute has in the disagree state * 15. The amount of reputation that the user's origin reputation entry in the tree has in the state being disputed * 16. The UID that the user's origin reputation entry in the tree has in the state being disputed * 17. The branchMask of the proof that the child reputation for the user being updated is in the agree state * 18. The amount of reputation that the child reputation for the user being updated is in the agree state * 19. The UID of the child reputation for the user being updated in the agree state * 20. A dummy variable that should be set to 0. If nonzero, transaction will still work but be slightly more expensive. For an explanation of why this is present, look at the corresponding solidity code. * 21. The branchMask of the proof that the reputation adjacent to the new reputation being inserted is in the agree state * 22. The amount of reputation that the reputation adjacent to a new reputation being inserted has in the agree state * 23. The UID of the reputation adjacent to the new reputation being inserted * 24. A dummy variable that should be set to 0. If nonzero, transaction will still work but be slightly more expensive. For an explanation of why this is present, look at the corresponding solidity code. * 25. The value of the reputation that would be origin-adjacent that proves that the origin reputation does not exist in the tree * 26. The value of the reputation that would be child-adjacent that proves that the child reputation does not exist in the tree -|_b32|bytes32[7]|A `bytes32[8]` array. The elements of this array, in order are: * 1. The colony address in the key of the reputation being changed that the disagreement is over. * 2. The skillid in the key of the reputation being changed that the disagreement is over. * 3. The user address in the key of the reputation being changed that the disagreement is over. * 4. The keccak256 hash of the key of the reputation being changed that the disagreement is over. * 5. The keccak256 hash of the key for a reputation already in the tree adjacent to the new reputation being inserted, if required. * 6. The keccak256 hash of the key of the reputation that would be origin-adjacent that proves that the origin reputation does not exist in the tree * 7. The keccak256 hash of the key of the reputation that would be child-adjacent that proves that the child reputation does not exist in the tree +|_b32|bytes32[7]|A `bytes32[7]` array. The elements of this array, in order are: * 1. The colony address in the key of the reputation being changed that the disagreement is over. * 2. The skillid in the key of the reputation being changed that the disagreement is over. * 3. The user address in the key of the reputation being changed that the disagreement is over. * 4. The keccak256 hash of the key of the reputation being changed that the disagreement is over. * 5. The keccak256 hash of the key for a reputation already in the tree adjacent to the new reputation being inserted, if required. * 6. The keccak256 hash of the key of the reputation that would be origin-adjacent that proves that the origin reputation does not exist in the tree * 7. The keccak256 hash of the key of the reputation that would be child-adjacent that proves that the child reputation does not exist in the tree |_reputationSiblings|bytes32[]|The siblings of the Merkle proof that the reputation corresponding to `_reputationKey` is in the reputation state before and after the disagreement |_agreeStateSiblings|bytes32[]|The siblings of the Merkle proof that the last reputation state the submitted hashes agreed on is in this submitted hash's justification tree |_disagreeStateSiblings|bytes32[]|The siblings of the Merkle proof that the first reputation state the submitted hashes disagreed on is in this submitted hash's justification tree diff --git a/hardhat.config.js b/hardhat.config.js index 131383e660..b21a3ace53 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -7,6 +7,7 @@ const express = require("express"); const bodyParser = require("body-parser"); const ethers = require("ethers"); +const { FORKED_XDAI_CHAINID } = require("./helpers/constants"); require("@nomiclabs/hardhat-truffle5"); require("hardhat-contract-sizer"); require("solidity-coverage"); @@ -94,7 +95,7 @@ module.exports = { networks: { development: { url: "http://localhost:8545", - chainId: 2656691, + chainId: Number(process.env.CHAIN_ID) || 265669100, throwOnCallFailures: false, throwOnTransactionFailures: false, allowBlocksWithSameTimestamp: true, @@ -103,7 +104,7 @@ module.exports = { }, development2: { url: "http://localhost:8546", - chainId: 2656692, + chainId: Number(process.env.CHAIN_ID) || 265669101, throwOnCallFailures: false, throwOnTransactionFailures: false, allowBlocksWithSameTimestamp: true, @@ -112,7 +113,7 @@ module.exports = { }, integration: { url: "http://localhost:8545", - chainId: 1998, + chainId: 265669100, throwOnCallFailures: false, throwOnTransactionFailures: false, allowBlocksWithSameTimestamp: true, @@ -120,7 +121,7 @@ module.exports = { blockGasLimit: 6721975, }, hardhat: { - chainId: Number(process.env.CHAIN_ID) || 2656691, // Supports chainId tests + chainId: Number(process.env.CHAIN_ID) || FORKED_XDAI_CHAINID, // Supports chainId tests throwOnCallFailures: false, throwOnTransactionFailures: false, allowBlocksWithSameTimestamp: true, diff --git a/helpers/constants.js b/helpers/constants.js index 80b8496db5..f65b05e685 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -75,6 +75,11 @@ const SLOT0 = 0; const SLOT1 = 1; const SLOT2 = 2; +const XDAI_CHAINID = 100; +const FORKED_XDAI_CHAINID = 265669100; +const MAINNET_CHAINID = 1; +const FORKED_MAINNET_CHAINID = 2656691; + module.exports = { UINT256_MAX, UINT128_MAX, @@ -126,4 +131,8 @@ module.exports = { SLOT0, SLOT1, SLOT2, + XDAI_CHAINID, + FORKED_XDAI_CHAINID, + MAINNET_CHAINID, + FORKED_MAINNET_CHAINID, }; diff --git a/helpers/test-data-generator.js b/helpers/test-data-generator.js index 245170f113..1e9238346c 100644 --- a/helpers/test-data-generator.js +++ b/helpers/test-data-generator.js @@ -5,14 +5,13 @@ const { signTypedData_v4: signTypedData } = require("eth-sig-util"); const { UINT256_MAX, MANAGER_PAYOUT, EVALUATOR_PAYOUT, WORKER_PAYOUT, INITIAL_FUNDING, SLOT0, SLOT1, SLOT2, ADDRESS_ZERO } = require("./constants"); -const { getTokenArgs, web3GetAccounts, getChildSkillIndex } = require("./test-helper"); +const { getTokenArgs, web3GetAccounts, getChildSkillIndex, getChainId } = require("./test-helper"); const IColony = artifacts.require("IColony"); const IMetaColony = artifacts.require("IMetaColony"); const ITokenLocking = artifacts.require("ITokenLocking"); const Token = artifacts.require("Token"); const BasicMetaTransaction = artifacts.require("BasicMetaTransaction"); -const MultiChain = artifacts.require("MultiChain"); const EtherRouter = artifacts.require("EtherRouter"); const Resolver = artifacts.require("Resolver"); const MetaTxToken = artifacts.require("MetaTxToken"); @@ -196,7 +195,7 @@ exports.setupMetaColonyWithLockedCLNYToken = async function setupMetaColonyWithL // The following are the needed `transfer` function permissions on the locked CLNY that we setup via the TokenAuthority here // IColonyNetworkMining: rewardStakers - // IColony: bootstrapColony, mintTokensForColonyNetwork, claimPayout and claimRewardPayout + // IColony: bootstrapColony, claimPayout and claimRewardPayout // ITokenLocking: withdraw, deposit const tokenAuthority = await TokenAuthority.new(clnyToken.address, metaColonyAddress, [colonyNetwork.address, tokenLockingAddress]); @@ -245,9 +244,13 @@ exports.setupColonyNetwork = async function setupColonyNetwork() { // Initialise with originally deployed version await colonyNetwork.initialise(colonyVersionResolverAddress, version); - // Jumping through these hoops to avoid the need to rewire ReputationMiningCycleResolver. - const reputationMiningCycleResolverAddress = await deployedColonyNetwork.getMiningResolver(); - await colonyNetwork.setMiningResolver(reputationMiningCycleResolverAddress); + const chainId = await getChainId(); + const miningChainId = parseInt(process.env.MINING_CHAIN_ID, 10) || chainId; + if (chainId === miningChainId) { + // Jumping through these hoops to avoid the need to rewire ReputationMiningCycleResolver. + const reputationMiningCycleResolverAddress = await deployedColonyNetwork.getMiningResolver(); + await colonyNetwork.setMiningResolver(reputationMiningCycleResolverAddress); + } // Get token-locking router from when it was deployed during migrations const deployedTokenLockingAddress = await deployedColonyNetwork.getTokenLocking(); @@ -304,8 +307,7 @@ exports.getMetaTransactionParameters = async function getMetaTransactionParamete const nonce = await contract.getMetatransactionNonce(userAddress); // We should just be able to get the chain id via a web3 call, but until ganache sort their stuff out, // we dance around the houses. - const multichain = await MultiChain.new(); - const chainId = await multichain.getChainId(); + const chainId = await getChainId(); // Sign data const msg = web3.utils.soliditySha3( @@ -326,8 +328,7 @@ exports.getMetaTransactionParameters = async function getMetaTransactionParamete exports.getPermitParameters = async function getPermitParameters(owner, privateKey, spender, amount, deadline, targetAddress) { const contract = await MetaTxToken.at(targetAddress); const nonce = await contract.nonces(owner); - const multichain = await MultiChain.new(); - const chainId = await multichain.getChainId(); + const chainId = await getChainId(); const name = await contract.name(); const sigObject = { @@ -377,7 +378,7 @@ exports.getPermitParameters = async function getPermitParameters(owner, privateK domain: { name, version: "1", - chainId: chainId.toNumber(), + chainId, verifyingContract: contract.address, }, message: { diff --git a/helpers/test-helper.js b/helpers/test-helper.js index 978104cf15..fc942a15f2 100644 --- a/helpers/test-helper.js +++ b/helpers/test-helper.js @@ -1,4 +1,4 @@ -/* globals artifacts */ +/* globals artifacts, hre */ const shortid = require("shortid"); const chai = require("chai"); const { asciiToHex, isBN } = require("web3-utils"); @@ -7,7 +7,17 @@ const { ethers } = require("ethers"); const { BigNumber } = require("bignumber.js"); const helpers = require("@nomicfoundation/hardhat-network-helpers"); -const { UINT256_MAX, MIN_STAKE, MINING_CYCLE_DURATION, DEFAULT_STAKE, CHALLENGE_RESPONSE_WINDOW_DURATION } = require("./constants"); +const { + UINT256_MAX, + MIN_STAKE, + MINING_CYCLE_DURATION, + DEFAULT_STAKE, + CHALLENGE_RESPONSE_WINDOW_DURATION, + FORKED_MAINNET_CHAINID, + MAINNET_CHAINID, + XDAI_CHAINID, + FORKED_XDAI_CHAINID, +} = require("./constants"); const IColony = artifacts.require("IColony"); const IMetaColony = artifacts.require("IMetaColony"); @@ -20,6 +30,7 @@ const Resolver = artifacts.require("Resolver"); const ContractEditing = artifacts.require("ContractEditing"); const ColonyDomains = artifacts.require("ColonyDomains"); const EtherRouter = artifacts.require("EtherRouter"); +const ChainId = artifacts.require("ChainId"); const { expect } = chai; @@ -120,6 +131,45 @@ exports.web3GetChainId = async function web3GetChainId() { }); }; +exports.getChainId = async function getChainId() { + // Why do we do this? Because with the check-if-we-are-on-the-minign chain setup for + // tests, we've introduced a new transaction in the setup process. This causes the + // past-version-caching to fail, because we end up deploying different code to the same + // addresses. This is a workaround for that, but should be considered temporary. + const packet = { + jsonrpc: "2.0", + method: "eth_accounts", + params: [], + id: new Date().getTime(), + }; + + return new Promise((resolve, reject) => { + ChainId.currentProvider.send(packet, async function (err, res) { + if (err !== null) return reject(err); + const accounts = res.result; + const c = await ChainId.new({ from: accounts.slice(-1)[0] }); + const chainId = await c.getChainId(); + return resolve(chainId.toNumber()); + }); + }); +}; + +exports.web3SignTypedData = function web3SignTypedData(address, typedData) { + const packet = { + jsonrpc: "2.0", + method: "eth_signTypedData", + params: [address, typedData], + id: new Date().getTime(), + }; + + return new Promise((resolve, reject) => { + web3.currentProvider.send(packet, (err, res) => { + if (err !== null) return reject(err); + return resolve(res.result); + }); + }); +}; + exports.web3GetRawCall = function web3GetRawCall(params, blockTag) { const packet = { jsonrpc: "2.0", @@ -193,25 +243,37 @@ exports.checkErrorRevertEthers = async function checkErrorRevertEthers(promise, receipt = await promise; } catch (err) { const txid = err.transactionHash; - const tx = await exports.web3GetTransaction(txid); - receipt = await exports.web3GetTransactionReceipt(txid); - const response = await exports.web3GetRawCall( - { - from: tx.from, - to: tx.to, - data: tx.input, - gas: ethers.utils.hexValue(tx.gas), - value: ethers.utils.hexValue(parseInt(tx.value, 10)), - }, - ethers.utils.hexValue(receipt.blockNumber), - ); - const reason = exports.extractReasonString(response); + const TRUFFLE_PORT = hre.__SOLIDITY_COVERAGE_RUNNING ? 8555 : 8545; + const OTHER_RPC_PORT = 8546; + + let provider = new ethers.providers.JsonRpcProvider(`http://127.0.0.1:${TRUFFLE_PORT}`); + receipt = await provider.getTransactionReceipt(txid); + if (!receipt) { + provider = new ethers.providers.JsonRpcProvider(`http://127.0.0.1:${OTHER_RPC_PORT}`); + receipt = await provider.getTransactionReceipt(txid); + } + + const tx = await provider.getTransaction(txid); + let reason; + try { + const callResult = await provider.call( + { + from: tx.from, + to: tx.to, + data: tx.data, + gas: ethers.utils.hexValue(tx.gasLimit), + value: ethers.utils.hexValue(parseInt(tx.value, 10)), + }, + receipt.blockNumber, + ); + reason = web3.eth.abi.decodeParameter("string", callResult.slice(10)); + } catch (err2) { + reason = web3.eth.abi.decodeParameter("string", err2.error.error.data.slice(10)); + } expect(reason).to.equal(errorMessage); - return; } - - expect(receipt.status, `Transaction succeeded, but expected to fail with: ${errorMessage}`).to.be.zero; + expect(receipt.status, `Transaction succeeded, but expected to fail with: ${errorMessage}`).to.equal(0); }; // Sometimes we might have to use this function because of @@ -395,22 +457,30 @@ exports.expectAllEvents = async function expectAllEvents(tx, eventNames) { return expect(events).to.be.true; }; -exports.forwardTime = async function forwardTime(seconds, test) { +exports.forwardTime = async function forwardTime(seconds, test, _web3provider) { if (typeof seconds !== "number") { throw new Error("typeof seconds is not a number"); } - const client = await exports.web3GetClient(); - if (client.indexOf("Hardhat") !== -1) { - return helpers.time.increase(seconds); + if (!_web3provider) { + const client = await exports.web3GetClient(); + if (client.indexOf("Hardhat") !== -1) { + return helpers.time.increase(seconds); + } } + const web3provider = _web3provider || web3.currentProvider; + // eslint-disable-next-line no-warning-comments + // FIXME: not strictly correct, but it's late. + // Should really call the rpc node with web3provider.send + const client = await exports.web3GetClient(); + const p = new Promise((resolve, reject) => { if (client.indexOf("TestRPC") === -1 && client.indexOf("Hardhat") === -1) { resolve(test.skip()); } else { // console.log(`Forwarding time with ${seconds}s ...`); - web3.currentProvider.send( + web3provider.send( { jsonrpc: "2.0", method: "evm_increaseTime", @@ -421,7 +491,7 @@ exports.forwardTime = async function forwardTime(seconds, test) { if (err) { return reject(err); } - return web3.currentProvider.send( + return web3provider.send( { jsonrpc: "2.0", method: "evm_mine", @@ -469,6 +539,44 @@ exports.getHardhatAutomine = async function checkHardhatAutomine() { }); }; +exports.snapshot = async function snapshot(provider) { + return new Promise((resolve, reject) => { + provider.send( + { + jsonrpc: "2.0", + method: "evm_snapshot", + params: [], + id: new Date().getTime(), + }, + (err, res) => { + if (err) { + return reject(err); + } + return resolve(res.result); + }, + ); + }); +}; + +exports.revert = async function revert(provider, snapshotId) { + return new Promise((resolve, reject) => { + provider.send( + { + jsonrpc: "2.0", + method: "evm_revert", + params: [snapshotId], + id: new Date().getTime(), + }, + (err) => { + if (err) { + return reject(err); + } + return resolve(); + }, + ); + }); +}; + exports.stopMining = async function stopMining() { const client = await exports.web3GetClient(); if (client.indexOf("Hardhat") !== -1) { @@ -1118,6 +1226,32 @@ exports.sleep = function sleep(ms) { }); }; +exports.getMultichainSkillId = function getMultichainSkillId(chainId, skillId) { + if (chainId === XDAI_CHAINID || chainId === FORKED_XDAI_CHAINID) { + return skillId; + } + return ethers.BigNumber.from(chainId).mul(ethers.BigNumber.from(2).pow(128)).add(ethers.BigNumber.from(skillId)); +}; + +exports.upgradeColonyTo = async function (colony, _version) { + const version = new BN(_version); + let currentVersion = await colony.version(); + while (currentVersion.ltn(version)) { + await colony.upgrade(currentVersion.addn(1)); + currentVersion = await colony.version(); + } +}; + +exports.isMainnet = async function isMainnet() { + const chainId = await exports.web3GetChainId(); + return chainId === MAINNET_CHAINID || chainId === FORKED_MAINNET_CHAINID; +}; + +exports.isXdai = async function isXdai() { + const chainId = await exports.web3GetChainId(); + return chainId === XDAI_CHAINID || chainId === FORKED_XDAI_CHAINID; +}; + class TestAdapter { constructor() { this.outputs = []; diff --git a/helpers/upgradable-contracts.js b/helpers/upgradable-contracts.js index d6d75e3003..d65c032ddb 100644 --- a/helpers/upgradable-contracts.js +++ b/helpers/upgradable-contracts.js @@ -141,6 +141,7 @@ exports.setupUpgradableColonyNetwork = async function setupUpgradableColonyNetwo deployedImplementations.ColonyNetworkENS = colonyNetworkENS.address; deployedImplementations.ColonyNetworkSkills = colonyNetworkSkills.address; deployedImplementations.ColonyNetworkExtensions = colonyNetworkExtensions.address; + deployedImplementations.ColonyNetworkSkills = colonyNetworkSkills.address; deployedImplementations.ContractRecovery = contractRecovery.address; await exports.setupEtherRouter("colonyNetwork", "IColonyNetwork", deployedImplementations, resolver); @@ -174,8 +175,8 @@ exports.setupReputationMiningCycleResolver = async function setupReputationMinin await colonyNetwork.setMiningResolver(resolver.address); }; -exports.setupENSRegistrar = async function setupENSRegistrar(colonyNetwork, ensRegistry, registrarOwner) { - const rootNode = namehash.hash("joincolony.eth"); +exports.setupENSRegistrar = async function setupENSRegistrar(colonyNetwork, ensRegistry, registrarOwner, suffix) { + const rootNode = namehash.hash(`joincolony.${suffix}`); const USER_HASH = soliditySha3("user"); const COLONY_HASH = soliditySha3("colony"); diff --git a/lib/wormhole b/lib/wormhole new file mode 160000 index 0000000000..eee4641f55 --- /dev/null +++ b/lib/wormhole @@ -0,0 +1 @@ +Subproject commit eee4641f55954d2d0db47831688a2e97eb20f7ee diff --git a/migrations/6_setup_mining_cycle_resolver.js b/migrations/6_setup_mining_cycle_resolver.js index bdd4c943b2..28c60f31d5 100644 --- a/migrations/6_setup_mining_cycle_resolver.js +++ b/migrations/6_setup_mining_cycle_resolver.js @@ -1,6 +1,7 @@ /* globals artifacts */ const { setupReputationMiningCycleResolver } = require("../helpers/upgradable-contracts"); +const { getChainId } = require("../helpers/test-helper"); const IColonyNetwork = artifacts.require("./IColonyNetwork"); const ReputationMiningCycle = artifacts.require("./ReputationMiningCycle"); @@ -11,6 +12,16 @@ const Resolver = artifacts.require("./Resolver"); // eslint-disable-next-line no-unused-vars module.exports = async function (deployer) { + // Check chain id + // If not a mining chain, then skip + const chainId = await getChainId(); + const miningChainId = parseInt(process.env.MINING_CHAIN_ID, 10) || chainId; + + if (chainId !== miningChainId) { + console.log("Not mining chain, skipping setting up mining cycle resolver"); + return; + } + // Create a new Colony (version) and setup a new Resolver for it const reputationMiningCycle = await ReputationMiningCycle.deployed(); const reputationMiningCycleRespond = await ReputationMiningCycleRespond.deployed(); diff --git a/migrations/8_setup_meta_colony.js b/migrations/8_setup_meta_colony.js index 90773349fd..d95611bcb7 100644 --- a/migrations/8_setup_meta_colony.js +++ b/migrations/8_setup_meta_colony.js @@ -1,6 +1,8 @@ /* globals artifacts */ const assert = require("assert"); +const ethers = require("ethers"); +const { UINT256_MAX, XDAI_CHAINID, FORKED_XDAI_CHAINID } = require("../helpers/constants"); const Token = artifacts.require("./Token"); const IColonyNetwork = artifacts.require("./IColonyNetwork"); @@ -14,6 +16,7 @@ const EtherRouter = artifacts.require("./EtherRouter"); const Version3 = artifacts.require("./Version3"); const Version4 = artifacts.require("./Version4"); const { setupColonyVersionResolver } = require("../helpers/upgradable-contracts"); +const { getChainId } = require("../helpers/test-helper"); const DEFAULT_STAKE = "2000000000000000000000000"; // DEFAULT_STAKE @@ -21,10 +24,8 @@ const DEFAULT_STAKE = "2000000000000000000000000"; // DEFAULT_STAKE module.exports = async function (deployer, network, accounts) { const MAIN_ACCOUNT = accounts[5]; const TOKEN_OWNER = accounts[11]; - const etherRouterDeployed = await EtherRouter.deployed(); const colonyNetwork = await IColonyNetwork.at(etherRouterDeployed.address); - const clnyToken = await Token.new("Colony Network Token", "CLNY", 18); await colonyNetwork.createMetaColony(clnyToken.address); const metaColonyAddress = await colonyNetwork.getMetaColony(); @@ -43,15 +44,22 @@ module.exports = async function (deployer, network, accounts) { await clnyToken.setAuthority(tokenAuthority.address); await clnyToken.setOwner(TOKEN_OWNER); - // These commands add MAIN_ACCOUNT as a reputation miner. - // This is necessary because the first miner must have staked before the mining cycle begins. - await clnyToken.mint(MAIN_ACCOUNT, DEFAULT_STAKE, { from: TOKEN_OWNER }); - await clnyToken.approve(tokenLockingAddress, DEFAULT_STAKE, { from: MAIN_ACCOUNT }); - const mainAccountBalance = await clnyToken.balanceOf(MAIN_ACCOUNT); - assert.equal(mainAccountBalance.toString(), DEFAULT_STAKE.toString()); - const tokenLocking = await ITokenLocking.at(tokenLockingAddress); - await tokenLocking.methods["deposit(address,uint256,bool)"](clnyToken.address, DEFAULT_STAKE, true, { from: MAIN_ACCOUNT }); - await colonyNetwork.stakeForMining(DEFAULT_STAKE, { from: MAIN_ACCOUNT }); + // Check chain id + // If not a mining chain, then skip setting up mining + const chainId = await getChainId(); + const miningChainId = parseInt(process.env.MINING_CHAIN_ID, 10) || chainId; + + if (miningChainId === chainId) { + // These commands add MAIN_ACCOUNT as a reputation miner. + // This is necessary because the first miner must have staked before the mining cycle begins. + await clnyToken.mint(MAIN_ACCOUNT, DEFAULT_STAKE, { from: TOKEN_OWNER }); + await clnyToken.approve(tokenLockingAddress, DEFAULT_STAKE, { from: MAIN_ACCOUNT }); + const mainAccountBalance = await clnyToken.balanceOf(MAIN_ACCOUNT); + assert.equal(mainAccountBalance.toString(), DEFAULT_STAKE.toString()); + const tokenLocking = await ITokenLocking.at(tokenLockingAddress); + await tokenLocking.methods["deposit(address,uint256,bool)"](clnyToken.address, DEFAULT_STAKE, true, { from: MAIN_ACCOUNT }); + await colonyNetwork.stakeForMining(DEFAULT_STAKE, { from: MAIN_ACCOUNT }); + } // Set up functional resolvers that identify correctly as previous versions. const Colony = artifacts.require("./Colony"); @@ -104,11 +112,21 @@ module.exports = async function (deployer, network, accounts) { await resolver4.register("version()", v4responder.address); await metaColony.addNetworkColonyVersion(4, resolver4.address); - await colonyNetwork.initialiseReputationMining(); - await colonyNetwork.startNextCycle(); - - const skillCount = await colonyNetwork.getSkillCount(); - assert.equal(skillCount.toNumber(), 3); // Root domain, root local skill, mining skill + if (chainId === miningChainId) { + await metaColony.initialiseReputationMining(miningChainId, ethers.constants.HashZero, 0); + // await colonyNetwork.startNextCycle(); + const skillCount = await colonyNetwork.getSkillCount(); + console.log(skillCount.toString(16)); + if (chainId === XDAI_CHAINID || chainId === FORKED_XDAI_CHAINID) { + assert.equal(skillCount.toNumber(), 3); + } else { + assert.equal(skillCount.shln(128).mod(UINT256_MAX).shrn(128).toNumber(), 3); + } + } else { + await metaColony.initialiseReputationMining(miningChainId, ethers.constants.HashZero, 0); + const skillCount = await colonyNetwork.getSkillCount(); + assert.equal(skillCount.shln(128).mod(UINT256_MAX).shrn(128).toNumber(), 2); + } console.log("### Meta Colony created at", metaColony.address); }; diff --git a/migrations/9_setup_extensions.js b/migrations/9_setup_extensions.js index dd22cb176d..4798fc7d42 100644 --- a/migrations/9_setup_extensions.js +++ b/migrations/9_setup_extensions.js @@ -28,7 +28,13 @@ async function addExtension(colonyNetwork, contractDir, interfaceName, extension const metaColony = await IMetaColony.at(metaColonyAddress); const NAME_HASH = soliditySha3(extensionName); - const deployments = await Promise.all(implementations.map((x) => x.new())); + const deployments = []; + // eslint-disable-next-line no-restricted-syntax + for (const implementation of implementations) { + const deployment = await implementation.new(); + deployments.push(deployment); + } + const resolver = await Resolver.new(); const deployedImplementations = {}; diff --git a/package-lock.json b/package-lock.json index 507c275424..6e6984659e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -87,13 +87,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -109,14 +109,15 @@ } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -148,9 +149,9 @@ "optional": true }, "node_modules/@babel/runtime": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", - "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", + "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -662,6 +663,7 @@ "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", "dev": true, + "hasInstallScript": true, "optional": true, "peer": true, "dependencies": { @@ -1050,6 +1052,7 @@ "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", "dev": true, + "hasInstallScript": true, "optional": true, "peer": true, "dependencies": { @@ -3335,8 +3338,7 @@ "dev": true, "optional": true, "os": [ - "darwin", - "linux" + "darwin" ], "engines": { "node": ">= 10" @@ -3352,8 +3354,7 @@ "dev": true, "optional": true, "os": [ - "darwin", - "linux" + "darwin" ], "engines": { "node": ">= 10" @@ -3772,9 +3773,9 @@ } }, "node_modules/@scure/base": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", - "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", + "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==", "funding": { "url": "https://paulmillr.com/funding/" } @@ -5130,8 +5131,6 @@ "integrity": "sha512-bdM5cEGCOhDSwminryHJbRmXc1x7dPKg6Pqns3qyTwFlxsqUgxE29lsERS3PlIW1HTjoIGMUqsk1zQQwST1Yxw==", "dev": true, "hasInstallScript": true, - "optional": true, - "peer": true, "dependencies": { "node-gyp-build": "4.3.0" }, @@ -5155,6 +5154,64 @@ "strip-indent": "^2.0.0" } }, + "node_modules/@trufflesuite/uws-js-unofficial": { + "version": "20.30.0-unofficial.0", + "resolved": "https://registry.npmjs.org/@trufflesuite/uws-js-unofficial/-/uws-js-unofficial-20.30.0-unofficial.0.tgz", + "integrity": "sha512-r5X0aOQcuT6pLwTRLD+mPnAM/nlKtvIK4Z+My++A8tTOR0qTjNRx8UB8jzRj3D+p9PMAp5LnpCUUGmz7/TppwA==", + "dependencies": { + "ws": "8.13.0" + }, + "optionalDependencies": { + "bufferutil": "4.0.7", + "utf-8-validate": "6.0.3" + } + }, + "node_modules/@trufflesuite/uws-js-unofficial/node_modules/bufferutil": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", + "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/@trufflesuite/uws-js-unofficial/node_modules/utf-8-validate": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.3.tgz", + "integrity": "sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/@trufflesuite/uws-js-unofficial/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@typechain/ethers-v5": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-10.2.1.tgz", @@ -5204,7 +5261,6 @@ "version": "5.1.5", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -5222,9 +5278,9 @@ } }, "node_modules/@types/chai": { - "version": "4.3.12", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.12.tgz", - "integrity": "sha512-zNKDHG/1yxm8Il6uCCVsm+dRdEsJlFoDu73X17y09bId6UwoYww+vFBsAcRzl8knM1sab3Dp1VRikFQwDOtDDw==", + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz", + "integrity": "sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w==", "dev": true }, "node_modules/@types/concat-stream": { @@ -5298,8 +5354,7 @@ "node_modules/@types/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", - "dev": true + "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==" }, "node_modules/@types/minimatch": { "version": "5.1.2", @@ -5318,10 +5373,9 @@ } }, "node_modules/@types/node": { - "version": "20.11.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.25.tgz", - "integrity": "sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw==", - "dev": true, + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", "dependencies": { "undici-types": "~5.26.4" } @@ -5354,9 +5408,9 @@ "peer": true }, "node_modules/@types/qs": { - "version": "6.9.12", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.12.tgz", - "integrity": "sha512-bZcOkJ6uWrL0Qb2NAWKa7TBU+mJHPzhx9jjLL1KHF+XpzEcR7EXHvjbHlGtR/IsP1vyPrehuS6XqkmaePy//mg==", + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", + "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==", "dev": true }, "node_modules/@types/readable-stream": { @@ -5393,6 +5447,11 @@ "@types/node": "*" } }, + "node_modules/@types/seedrandom": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.1.tgz", + "integrity": "sha512-giB9gzDeiCeloIXDgzFBCgjj1k4WxcDrZtGl6h1IqmUPlxF+Nx8Ve+96QCyDZ/HseB/uvDsKbpib9hU5cU53pw==" + }, "node_modules/@types/sinon": { "version": "17.0.3", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", @@ -5870,12 +5929,12 @@ "dev": true }, "node_modules/antlr4": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.1.tgz", - "integrity": "sha512-kiXTspaRYvnIArgE97z5YVVf/cDVQABr3abFRR6mE7yesLMkgu4ujuyV/sgxafQ8wgve0DJQUJ38Z8tkgA2izA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.11.0.tgz", + "integrity": "sha512-GUGlpE2JUjAN+G8G5vY+nOoeyNhHsXoIJwP1XF1oRw89vifA1K46T6SEkwLwr7drihN7I/lf0DIjKc4OZvBX8w==", "dev": true, "engines": { - "node": ">=16" + "node": ">=14" } }, "node_modules/antlr4ts": { @@ -6012,15 +6071,16 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "node_modules/array-includes": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", - "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" }, "engines": { @@ -6048,35 +6108,17 @@ "node": ">=0.10.0" } }, - "node_modules/array.prototype.filter": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", - "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", - "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", + "es-abstract": "^1.23.2", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" }, "engines": { @@ -6205,7 +6247,6 @@ "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, "dependencies": { "lodash": "^4.17.14" } @@ -6226,7 +6267,6 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", "integrity": "sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==", - "dev": true, "dependencies": { "async": "^2.4.0" } @@ -7960,9 +8000,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001598", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001598.tgz", - "integrity": "sha512-j8mQRDziG94uoBfeFuqsJUNECW37DXpnvhcMJMdlH2u3MRkq1sAI0LJcXP1i/Py0KbSIC4UDj8YHPrTn5YsL+Q==", + "version": "1.0.30001600", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz", + "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==", "dev": true, "funding": [ { @@ -8012,7 +8052,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", - "dev": true, "engines": { "node": ">=6" } @@ -8186,8 +8225,7 @@ "hasInstallScript": true, "optional": true, "os": [ - "darwin", - "linux" + "darwin" ], "dependencies": { "bindings": "^1.5.0", @@ -8726,9 +8764,9 @@ "optional": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -8757,9 +8795,9 @@ "optional": true }, "node_modules/core-js-pure": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.0.tgz", - "integrity": "sha512-cN28qmhRNgbMZZMc/RFu5w8pK9VJzpb2rJVR/lHuZJKwmXnoWOpXmMkxqBB514igkp1Hu8WGROsiOAzUcKdHOQ==", + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.1.tgz", + "integrity": "sha512-NXCvHvSVYSrewP0L5OhltzXeWFJLo2AL2TYnj6iLV3Bw8mM62wAQMNgUCRI6EBu6hVVpbCxmOPlxh1Ikw2PfUA==", "dev": true, "hasInstallScript": true, "peer": true, @@ -9051,12 +9089,15 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/decode-uri-component": { @@ -9288,9 +9329,9 @@ } }, "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", "engines": { "node": ">=8" } @@ -9484,9 +9525,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.708", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.708.tgz", - "integrity": "sha512-iWgEEvREL4GTXXHKohhh33+6Y8XkPI5eHihDmm8zUk5Zo7HICEW+wI/j5kJ2tbuNUCXJ/sNXa03ajW635DiJXA==", + "version": "1.4.715", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.715.tgz", + "integrity": "sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==", "dev": true, "optional": true }, @@ -9513,8 +9554,6 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.0.tgz", "integrity": "sha512-AGvFfs+d0JKCJQ4o01ASQLGPmSCxgfU9RFXvzPvZdjKK8oscynksuJhWrSTSw7j7Ep/sZct5b5ZhYCi8S/t0HQ==", - "dev": true, - "peer": true, "engines": { "node": ">=12" }, @@ -9669,9 +9708,9 @@ } }, "node_modules/es-abstract": { - "version": "1.23.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.1.tgz", - "integrity": "sha512-r+YVn6hTqQb+P5kK0u3KeDqrmhHKm+OhU/Mw4jSL4eQtOxXmp75fXIUUb3sUqFZOlb/YtW5JRaIfEC3UyjYUZQ==", + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.2.tgz", + "integrity": "sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.1", @@ -9711,8 +9750,8 @@ "regexp.prototype.flags": "^1.5.2", "safe-array-concat": "^1.1.2", "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.8", - "string.prototype.trimend": "^1.0.7", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", "string.prototype.trimstart": "^1.0.7", "typed-array-buffer": "^1.0.2", "typed-array-byte-length": "^1.0.1", @@ -9728,12 +9767,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -12243,16 +12276,16 @@ "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==" }, "node_modules/express": { - "version": "4.18.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", - "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.1.tgz", + "integrity": "sha512-K4w1/Bp7y8iSiVObmCrtq8Cs79XjJc/RU2YYkZQ7wpUu5ZyZ7MtPHkqoMz4pf+mgXfNvo2qft8D9OnrH2ABk9w==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -12896,8 +12929,7 @@ "hasInstallScript": true, "optional": true, "os": [ - "darwin", - "linux" + "darwin" ], "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" @@ -12958,6 +12990,7 @@ "@types/lru-cache", "@types/seedrandom" ], + "dev": true, "hasShrinkwrap": true, "dependencies": { "@trufflesuite/bigint-buffer": "1.1.10", @@ -12982,6 +13015,7 @@ "version": "1.1.10", "resolved": "https://registry.npmjs.org/@trufflesuite/bigint-buffer/-/bigint-buffer-1.1.10.tgz", "integrity": "sha512-pYIQC5EcMmID74t26GCC67946mgTJFiLXOT/BYozgrd4UEY2JHEGLhWi9cMiQCt5BSqFEvKkCHNnoj82SRjiEw==", + "dev": true, "hasInstallScript": true, "inBundle": true, "license": "Apache-2.0", @@ -12996,6 +13030,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", + "dev": true, "inBundle": true, "license": "MIT", "bin": { @@ -13008,6 +13043,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz", "integrity": "sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -13018,6 +13054,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", + "dev": true, "inBundle": true, "license": "MIT" }, @@ -13025,6 +13062,7 @@ "version": "17.0.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.0.tgz", "integrity": "sha512-eMhwJXc931Ihh4tkU+Y7GiLzT/y/DBNpNtr4yU9O2w3SYBsr9NaOPhQlLKRmoWtI54uNwuo0IOUFQjVOTZYRvw==", + "dev": true, "inBundle": true, "license": "MIT" }, @@ -13032,6 +13070,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.1.tgz", "integrity": "sha512-giB9gzDeiCeloIXDgzFBCgjj1k4WxcDrZtGl6h1IqmUPlxF+Nx8Ve+96QCyDZ/HseB/uvDsKbpib9hU5cU53pw==", + "dev": true, "inBundle": true, "license": "MIT" }, @@ -13039,6 +13078,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, "funding": [ { "type": "github", @@ -13060,6 +13100,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true, "inBundle": true, "license": "MIT" }, @@ -13067,6 +13108,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, "funding": [ { "type": "github", @@ -13092,6 +13134,8 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", + "dev": true, + "hasInstallScript": true, "optional": true, "dependencies": { "node-gyp-build": "^4.3.0" @@ -13101,6 +13145,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.0.tgz", "integrity": "sha512-M5imwzQn6y+ODBfgi+cfgZv2hIUI6oYU/0f35Mdb1ujGeqeoI5tOnl9Q13DTH7LW+7er+NYq8stNOKZD/Z3U/A==", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -13114,6 +13159,7 @@ "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -13130,6 +13176,7 @@ "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true, "inBundle": true, "license": "MIT" }, @@ -13137,6 +13184,7 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.0.tgz", "integrity": "sha512-AGvFfs+d0JKCJQ4o01ASQLGPmSCxgfU9RFXvzPvZdjKK8oscynksuJhWrSTSw7j7Ep/sZct5b5ZhYCi8S/t0HQ==", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -13150,6 +13198,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -13161,6 +13210,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -13173,6 +13223,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, "funding": [ { "type": "github", @@ -13194,6 +13245,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, "inBundle": true, "license": "ISC" }, @@ -13201,6 +13253,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, "funding": [ { "type": "github", @@ -13225,6 +13278,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz", "integrity": "sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==", + "dev": true, "hasInstallScript": true, "inBundle": true, "license": "MIT", @@ -13241,6 +13295,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-6.1.0.tgz", "integrity": "sha512-8C7oJDT44JXxh04aSSsfcMI8YiaGRhOFI9/pMEL7nWJLVsWajDPTRxsSHTM2WcTVY5nXM+SuRHzPPi0GbnDX+w==", + "dev": true, "hasInstallScript": true, "inBundle": true, "license": "MIT", @@ -13257,6 +13312,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz", "integrity": "sha512-DnhQwcFEaYsvYDnACLZhMmCWd3rkOeEvglpa4q5i/5Jlm3UIsWaxVzuXvDLFCSCWRO3yy2/+V/G7FusFgejnfQ==", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -13275,6 +13331,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-3.1.0.tgz", "integrity": "sha512-BWRCMHBxbIqPxJ8vHOvKUsaO0v1sLYZtjN3K2iZJsRBYtp+ONsY6Jfi6hy9K3+zolgQRryhIn2NRZjZnWJ9NmQ==", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -13288,6 +13345,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-2.1.0.tgz", "integrity": "sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA==", + "dev": true, "inBundle": true, "license": "MIT", "engines": { @@ -13298,6 +13356,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, "inBundle": true, "license": "ISC" }, @@ -13305,6 +13364,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true, "inBundle": true, "license": "MIT" }, @@ -13312,6 +13372,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", + "dev": true, "inBundle": true, "license": "MIT" }, @@ -13319,6 +13380,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", + "dev": true, "inBundle": true, "license": "MIT" }, @@ -13326,6 +13388,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", + "dev": true, "inBundle": true, "license": "MIT", "bin": { @@ -13338,6 +13401,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -13359,6 +13423,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.0.tgz", "integrity": "sha512-ULWhjjE8BmiICGn3G8+1L9wFpERNxkf8ysxkAer4+TFdRefDaXOCV5m92aMB9FtBVmn/8sETXLXY6BfW7hyaWQ==", + "dev": true, "inBundle": true, "license": "MIT" }, @@ -13366,6 +13431,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -13381,6 +13447,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, "funding": [ { "type": "github", @@ -13402,6 +13469,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", + "dev": true, "hasInstallScript": true, "inBundle": true, "license": "MIT", @@ -13418,6 +13486,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, "inBundle": true, "license": "MIT", "dependencies": { @@ -13428,6 +13497,8 @@ "version": "5.0.7", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", + "dev": true, + "hasInstallScript": true, "optional": true, "dependencies": { "node-gyp-build": "^4.3.0" @@ -13437,6 +13508,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true, "inBundle": true, "license": "MIT" }, @@ -14346,15 +14418,6 @@ "node": ">=4" } }, - "node_modules/hardhat/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/hardhat/node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -15258,7 +15321,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true, "funding": [ { "type": "github", @@ -16477,7 +16539,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz", "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==", - "dev": true, "engines": { "node": ">=12" } @@ -16486,7 +16547,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", - "dev": true, "dependencies": { "buffer": "^6.0.3", "module-error": "^1.0.1" @@ -16499,7 +16559,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, "funding": [ { "type": "github", @@ -16656,8 +16715,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.assign": { "version": "4.2.0", @@ -17950,7 +18008,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", - "dev": true, "engines": { "node": ">=10" } @@ -18536,28 +18593,29 @@ } }, "node_modules/object.entries": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", - "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/object.fromentries": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", - "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -18567,16 +18625,17 @@ } }, "node_modules/object.groupby": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", - "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, "dependencies": { - "array.prototype.filter": "^1.0.3", - "call-bind": "^1.0.5", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.0.0" + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/object.omit": { @@ -18614,14 +18673,14 @@ } }, "node_modules/object.values": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", - "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -18784,6 +18843,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/package-utils": { "resolved": "packages/package-utils", "link": true @@ -19039,6 +19107,12 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -19486,7 +19560,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -20367,6 +20440,12 @@ "node": ">=0.10.0" } }, + "node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", + "dev": true + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -21475,6 +21554,15 @@ "wrap-ansi": "^2.0.0" } }, + "node_modules/solc/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/solc/node_modules/fs-extra": { "version": "0.30.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", @@ -21515,12 +21603,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/solc/node_modules/require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true - }, "node_modules/solc/node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -21568,12 +21650,6 @@ "node": ">=0.10.0" } }, - "node_modules/solc/node_modules/which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", - "dev": true - }, "node_modules/solc/node_modules/wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", @@ -22046,6 +22122,15 @@ "ms": "2.0.0" } }, + "node_modules/solparse/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/solparse/node_modules/diff": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", @@ -22267,15 +22352,6 @@ "node": ">=4" } }, - "node_modules/solparse/node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/solparse/node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -22285,12 +22361,6 @@ "node": ">=4" } }, - "node_modules/solparse/node_modules/require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true - }, "node_modules/solparse/node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -22336,6 +22406,12 @@ "which": "bin/which" } }, + "node_modules/solparse/node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true + }, "node_modules/solparse/node_modules/wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", @@ -22559,12 +22635,9 @@ } }, "node_modules/sqlite3/node_modules/node-addon-api": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz", - "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==", - "engines": { - "node": "^16 || ^18 || >= 20" - } + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==" }, "node_modules/sshpk": { "version": "1.18.0", @@ -22766,14 +22839,17 @@ } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -22791,6 +22867,15 @@ "node": ">=4" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -23240,9 +23325,9 @@ } }, "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -23681,15 +23766,6 @@ "json5": "lib/cli.js" } }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -23898,9 +23974,9 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", - "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", "dev": true, "dependencies": { "call-bind": "^1.0.7", @@ -23933,9 +24009,9 @@ } }, "node_modules/typescript": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", - "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", "dev": true, "peer": true, "bin": { @@ -24018,8 +24094,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/union-value": { "version": "1.0.1", @@ -24886,9 +24961,9 @@ } }, "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", "dev": true }, "node_modules/which-pm-runs": { @@ -25311,18 +25386,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/yargs/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -25640,6 +25703,22 @@ "reputation-miner": "bin/index" } }, + "packages/reputation-miner/node_modules/abstract-level": { + "version": "1.0.3", + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "catering": "^2.1.0", + "is-buffer": "^2.0.5", + "level-supports": "^4.0.0", + "level-transcoder": "^1.0.1", + "module-error": "^1.0.1", + "queue-microtask": "^1.2.3" + }, + "engines": { + "node": ">=12" + } + }, "packages/reputation-miner/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -25648,6 +25727,41 @@ "node": ">=8" } }, + "packages/reputation-miner/node_modules/buffer": { + "version": "6.0.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "packages/reputation-miner/node_modules/bufferutil": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", + "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "packages/reputation-miner/node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -25661,6 +25775,399 @@ "node": ">=12" } }, + "packages/reputation-miner/node_modules/ganache": { + "version": "7.9.2", + "bundleDependencies": [ + "@trufflesuite/bigint-buffer", + "keccak", + "leveldown", + "secp256k1" + ], + "license": "MIT", + "dependencies": { + "@trufflesuite/bigint-buffer": "1.1.10", + "@trufflesuite/uws-js-unofficial": "20.30.0-unofficial.0", + "@types/bn.js": "^5.1.0", + "@types/lru-cache": "5.1.1", + "@types/seedrandom": "3.0.1", + "abstract-level": "1.0.3", + "abstract-leveldown": "7.2.0", + "async-eventemitter": "0.2.4", + "emittery": "0.10.0", + "keccak": "3.0.2", + "leveldown": "6.1.0", + "secp256k1": "4.0.3" + }, + "bin": { + "ganache": "dist/node/cli.js", + "ganache-cli": "dist/node/cli.js" + }, + "optionalDependencies": { + "bufferutil": "4.0.5", + "utf-8-validate": "5.0.7" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/@trufflesuite/bigint-buffer": { + "version": "1.1.10", + "extraneous": true, + "hasInstallScript": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "node-gyp-build": "4.4.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/@trufflesuite/bigint-buffer/node_modules/node-gyp-build": { + "version": "4.4.0", + "extraneous": true, + "inBundle": true, + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/abstract-leveldown": { + "version": "7.2.0", + "extraneous": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "catering": "^2.0.0", + "is-buffer": "^2.0.5", + "level-concat-iterator": "^3.0.0", + "level-supports": "^2.0.1", + "queue-microtask": "^1.2.3" + }, + "engines": { + "node": ">=10" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/base64-js": { + "version": "1.5.1", + "extraneous": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT" + }, + "packages/reputation-miner/node_modules/ganache/node_modules/brorand": { + "version": "1.1.0", + "extraneous": true, + "inBundle": true, + "license": "MIT" + }, + "packages/reputation-miner/node_modules/ganache/node_modules/buffer": { + "version": "6.0.3", + "extraneous": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/catering": { + "version": "2.1.1", + "extraneous": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/elliptic": { + "version": "6.5.4", + "extraneous": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "extraneous": true, + "inBundle": true, + "license": "MIT" + }, + "packages/reputation-miner/node_modules/ganache/node_modules/hash.js": { + "version": "1.1.7", + "extraneous": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/hmac-drbg": { + "version": "1.0.1", + "extraneous": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/ieee754": { + "version": "1.2.1", + "extraneous": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "BSD-3-Clause" + }, + "packages/reputation-miner/node_modules/ganache/node_modules/inherits": { + "version": "2.0.4", + "extraneous": true, + "inBundle": true, + "license": "ISC" + }, + "packages/reputation-miner/node_modules/ganache/node_modules/is-buffer": { + "version": "2.0.5", + "extraneous": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/keccak": { + "version": "3.0.2", + "extraneous": true, + "hasInstallScript": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/level-concat-iterator": { + "version": "3.1.0", + "extraneous": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "catering": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/level-supports": { + "version": "2.1.0", + "extraneous": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/leveldown": { + "version": "6.1.0", + "extraneous": true, + "hasInstallScript": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "abstract-leveldown": "^7.2.0", + "napi-macros": "~2.0.0", + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/minimalistic-assert": { + "version": "1.0.1", + "extraneous": true, + "inBundle": true, + "license": "ISC" + }, + "packages/reputation-miner/node_modules/ganache/node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "extraneous": true, + "inBundle": true, + "license": "MIT" + }, + "packages/reputation-miner/node_modules/ganache/node_modules/napi-macros": { + "version": "2.0.0", + "extraneous": true, + "inBundle": true, + "license": "MIT" + }, + "packages/reputation-miner/node_modules/ganache/node_modules/node-addon-api": { + "version": "2.0.2", + "extraneous": true, + "inBundle": true, + "license": "MIT" + }, + "packages/reputation-miner/node_modules/ganache/node_modules/node-gyp-build": { + "version": "4.5.0", + "extraneous": true, + "inBundle": true, + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/queue-microtask": { + "version": "1.2.3", + "extraneous": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT" + }, + "packages/reputation-miner/node_modules/ganache/node_modules/readable-stream": { + "version": "3.6.0", + "extraneous": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/safe-buffer": { + "version": "5.2.1", + "extraneous": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT" + }, + "packages/reputation-miner/node_modules/ganache/node_modules/secp256k1": { + "version": "4.0.3", + "extraneous": true, + "hasInstallScript": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "elliptic": "^6.5.4", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/string_decoder": { + "version": "1.3.0", + "extraneous": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "packages/reputation-miner/node_modules/ganache/node_modules/util-deprecate": { + "version": "1.0.2", + "extraneous": true, + "inBundle": true, + "license": "MIT" + }, "packages/reputation-miner/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -25693,6 +26200,19 @@ "node": ">=8" } }, + "packages/reputation-miner/node_modules/utf-8-validate": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", + "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "packages/reputation-miner/node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -25727,13 +26247,13 @@ "dev": true }, "@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dev": true, "requires": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" } }, "@babel/helper-validator-identifier": { @@ -25743,14 +26263,15 @@ "dev": true }, "@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "dependencies": { "js-tokens": { @@ -25782,9 +26303,9 @@ } }, "@babel/runtime": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", - "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", + "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", "dev": true, "requires": { "regenerator-runtime": "^0.14.0" @@ -25948,11 +26469,39 @@ "yargs": "^17.5.1" }, "dependencies": { + "abstract-level": { + "version": "1.0.3", + "requires": { + "buffer": "^6.0.3", + "catering": "^2.1.0", + "is-buffer": "^2.0.5", + "level-supports": "^4.0.0", + "level-transcoder": "^1.0.1", + "module-error": "^1.0.1", + "queue-microtask": "^1.2.3" + } + }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, + "buffer": { + "version": "6.0.3", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "bufferutil": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", + "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", + "optional": true, + "requires": { + "node-gyp-build": "^4.3.0" + } + }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -25963,6 +26512,235 @@ "wrap-ansi": "^7.0.0" } }, + "ganache": { + "version": "7.9.2", + "requires": { + "@trufflesuite/bigint-buffer": "1.1.10", + "@trufflesuite/uws-js-unofficial": "20.30.0-unofficial.0", + "@types/bn.js": "^5.1.0", + "@types/lru-cache": "5.1.1", + "@types/seedrandom": "3.0.1", + "abstract-level": "1.0.3", + "abstract-leveldown": "7.2.0", + "async-eventemitter": "0.2.4", + "bufferutil": "4.0.5", + "emittery": "0.10.0", + "keccak": "3.0.2", + "leveldown": "6.1.0", + "secp256k1": "4.0.3", + "utf-8-validate": "5.0.7" + }, + "dependencies": { + "@trufflesuite/bigint-buffer": { + "version": "1.1.10", + "bundled": true, + "extraneous": true, + "requires": { + "node-gyp-build": "4.4.0" + }, + "dependencies": { + "node-gyp-build": { + "version": "4.4.0", + "bundled": true, + "extraneous": true + } + } + }, + "abstract-leveldown": { + "version": "7.2.0", + "bundled": true, + "extraneous": true, + "requires": { + "buffer": "^6.0.3", + "catering": "^2.0.0", + "is-buffer": "^2.0.5", + "level-concat-iterator": "^3.0.0", + "level-supports": "^2.0.1", + "queue-microtask": "^1.2.3" + } + }, + "base64-js": { + "version": "1.5.1", + "bundled": true, + "extraneous": true + }, + "brorand": { + "version": "1.1.0", + "bundled": true, + "extraneous": true + }, + "buffer": { + "version": "6.0.3", + "bundled": true, + "extraneous": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "catering": { + "version": "2.1.1", + "bundled": true, + "extraneous": true + }, + "elliptic": { + "version": "6.5.4", + "bundled": true, + "extraneous": true, + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "bundled": true, + "extraneous": true + } + } + }, + "hash.js": { + "version": "1.1.7", + "bundled": true, + "extraneous": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "bundled": true, + "extraneous": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "ieee754": { + "version": "1.2.1", + "bundled": true, + "extraneous": true + }, + "inherits": { + "version": "2.0.4", + "bundled": true, + "extraneous": true + }, + "is-buffer": { + "version": "2.0.5", + "bundled": true, + "extraneous": true + }, + "keccak": { + "version": "3.0.2", + "bundled": true, + "extraneous": true, + "requires": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + } + }, + "level-concat-iterator": { + "version": "3.1.0", + "bundled": true, + "extraneous": true, + "requires": { + "catering": "^2.1.0" + } + }, + "level-supports": { + "version": "2.1.0", + "bundled": true, + "extraneous": true + }, + "leveldown": { + "version": "6.1.0", + "bundled": true, + "extraneous": true, + "requires": { + "abstract-leveldown": "^7.2.0", + "napi-macros": "~2.0.0", + "node-gyp-build": "^4.3.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "bundled": true, + "extraneous": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "bundled": true, + "extraneous": true + }, + "napi-macros": { + "version": "2.0.0", + "bundled": true, + "extraneous": true + }, + "node-addon-api": { + "version": "2.0.2", + "bundled": true, + "extraneous": true + }, + "node-gyp-build": { + "version": "4.5.0", + "bundled": true, + "extraneous": true + }, + "queue-microtask": { + "version": "1.2.3", + "bundled": true, + "extraneous": true + }, + "readable-stream": { + "version": "3.6.0", + "bundled": true, + "extraneous": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "bundled": true, + "extraneous": true + }, + "secp256k1": { + "version": "4.0.3", + "bundled": true, + "extraneous": true, + "requires": { + "elliptic": "^6.5.4", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "bundled": true, + "extraneous": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "extraneous": true + } + } + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -25986,6 +26764,15 @@ "ansi-regex": "^5.0.1" } }, + "utf-8-validate": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", + "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", + "optional": true, + "requires": { + "node-gyp-build": "^4.3.0" + } + }, "yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -28695,9 +29482,9 @@ } }, "@scure/base": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", - "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==" + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", + "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==" }, "@scure/bip32": { "version": "1.1.5", @@ -29826,8 +30613,6 @@ "resolved": "https://registry.npmjs.org/@trufflesuite/bigint-buffer/-/bigint-buffer-1.1.9.tgz", "integrity": "sha512-bdM5cEGCOhDSwminryHJbRmXc1x7dPKg6Pqns3qyTwFlxsqUgxE29lsERS3PlIW1HTjoIGMUqsk1zQQwST1Yxw==", "dev": true, - "optional": true, - "peer": true, "requires": { "node-gyp-build": "4.3.0" } @@ -29848,6 +30633,42 @@ "strip-indent": "^2.0.0" } }, + "@trufflesuite/uws-js-unofficial": { + "version": "20.30.0-unofficial.0", + "resolved": "https://registry.npmjs.org/@trufflesuite/uws-js-unofficial/-/uws-js-unofficial-20.30.0-unofficial.0.tgz", + "integrity": "sha512-r5X0aOQcuT6pLwTRLD+mPnAM/nlKtvIK4Z+My++A8tTOR0qTjNRx8UB8jzRj3D+p9PMAp5LnpCUUGmz7/TppwA==", + "requires": { + "bufferutil": "4.0.7", + "utf-8-validate": "6.0.3", + "ws": "8.13.0" + }, + "dependencies": { + "bufferutil": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", + "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", + "optional": true, + "requires": { + "node-gyp-build": "^4.3.0" + } + }, + "utf-8-validate": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.3.tgz", + "integrity": "sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==", + "optional": true, + "requires": { + "node-gyp-build": "^4.3.0" + } + }, + "ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "requires": {} + } + } + }, "@typechain/ethers-v5": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-10.2.1.tgz", @@ -29889,7 +30710,6 @@ "version": "5.1.5", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", - "dev": true, "requires": { "@types/node": "*" } @@ -29907,9 +30727,9 @@ } }, "@types/chai": { - "version": "4.3.12", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.12.tgz", - "integrity": "sha512-zNKDHG/1yxm8Il6uCCVsm+dRdEsJlFoDu73X17y09bId6UwoYww+vFBsAcRzl8knM1sab3Dp1VRikFQwDOtDDw==", + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz", + "integrity": "sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w==", "dev": true }, "@types/concat-stream": { @@ -29983,8 +30803,7 @@ "@types/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", - "dev": true + "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==" }, "@types/minimatch": { "version": "5.1.2", @@ -30003,10 +30822,9 @@ } }, "@types/node": { - "version": "20.11.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.25.tgz", - "integrity": "sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw==", - "dev": true, + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", "requires": { "undici-types": "~5.26.4" } @@ -30039,9 +30857,9 @@ "peer": true }, "@types/qs": { - "version": "6.9.12", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.12.tgz", - "integrity": "sha512-bZcOkJ6uWrL0Qb2NAWKa7TBU+mJHPzhx9jjLL1KHF+XpzEcR7EXHvjbHlGtR/IsP1vyPrehuS6XqkmaePy//mg==", + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", + "integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==", "dev": true }, "@types/readable-stream": { @@ -30080,6 +30898,11 @@ "@types/node": "*" } }, + "@types/seedrandom": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.1.tgz", + "integrity": "sha512-giB9gzDeiCeloIXDgzFBCgjj1k4WxcDrZtGl6h1IqmUPlxF+Nx8Ve+96QCyDZ/HseB/uvDsKbpib9hU5cU53pw==" + }, "@types/sinon": { "version": "17.0.3", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", @@ -30475,9 +31298,9 @@ "dev": true }, "antlr4": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.1.tgz", - "integrity": "sha512-kiXTspaRYvnIArgE97z5YVVf/cDVQABr3abFRR6mE7yesLMkgu4ujuyV/sgxafQ8wgve0DJQUJ38Z8tkgA2izA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.11.0.tgz", + "integrity": "sha512-GUGlpE2JUjAN+G8G5vY+nOoeyNhHsXoIJwP1XF1oRw89vifA1K46T6SEkwLwr7drihN7I/lf0DIjKc4OZvBX8w==", "dev": true }, "antlr4ts": { @@ -30589,15 +31412,16 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "array-includes": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", - "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" } }, @@ -30613,29 +31437,17 @@ "integrity": "sha512-G2n5bG5fSUCpnsXz4+8FUkYsGPkNfLn9YvS66U5qbTIXI2Ynnlo4Bi42bWv+omKUCqz+ejzfClwne0alJWJPhg==", "dev": true }, - "array.prototype.filter": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz", - "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - } - }, "array.prototype.findlastindex": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz", - "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", "dev": true, "requires": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", + "es-abstract": "^1.23.2", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, @@ -30728,7 +31540,6 @@ "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, "requires": { "lodash": "^4.17.14" } @@ -30743,7 +31554,6 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", "integrity": "sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==", - "dev": true, "requires": { "async": "^2.4.0" } @@ -32287,9 +33097,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001598", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001598.tgz", - "integrity": "sha512-j8mQRDziG94uoBfeFuqsJUNECW37DXpnvhcMJMdlH2u3MRkq1sAI0LJcXP1i/Py0KbSIC4UDj8YHPrTn5YsL+Q==", + "version": "1.0.30001600", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz", + "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==", "dev": true, "optional": true }, @@ -32318,8 +33128,7 @@ "catering": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", - "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", - "dev": true + "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==" }, "cbor": { "version": "5.2.0", @@ -32893,9 +33702,9 @@ "optional": true }, "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" }, "cookie-signature": { "version": "1.0.6", @@ -32916,9 +33725,9 @@ "optional": true }, "core-js-pure": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.0.tgz", - "integrity": "sha512-cN28qmhRNgbMZZMc/RFu5w8pK9VJzpb2rJVR/lHuZJKwmXnoWOpXmMkxqBB514igkp1Hu8WGROsiOAzUcKdHOQ==", + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.1.tgz", + "integrity": "sha512-NXCvHvSVYSrewP0L5OhltzXeWFJLo2AL2TYnj6iLV3Bw8mM62wAQMNgUCRI6EBu6hVVpbCxmOPlxh1Ikw2PfUA==", "dev": true, "peer": true }, @@ -33136,9 +33945,9 @@ } }, "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, "decode-uri-component": { @@ -33307,9 +34116,9 @@ "dev": true }, "detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==" }, "diff": { "version": "3.5.0", @@ -33453,9 +34262,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { - "version": "1.4.708", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.708.tgz", - "integrity": "sha512-iWgEEvREL4GTXXHKohhh33+6Y8XkPI5eHihDmm8zUk5Zo7HICEW+wI/j5kJ2tbuNUCXJ/sNXa03ajW635DiJXA==", + "version": "1.4.715", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.715.tgz", + "integrity": "sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==", "dev": true, "optional": true }, @@ -33483,9 +34292,7 @@ "emittery": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.0.tgz", - "integrity": "sha512-AGvFfs+d0JKCJQ4o01ASQLGPmSCxgfU9RFXvzPvZdjKK8oscynksuJhWrSTSw7j7Ep/sZct5b5ZhYCi8S/t0HQ==", - "dev": true, - "peer": true + "integrity": "sha512-AGvFfs+d0JKCJQ4o01ASQLGPmSCxgfU9RFXvzPvZdjKK8oscynksuJhWrSTSw7j7Ep/sZct5b5ZhYCi8S/t0HQ==" }, "emoji-regex": { "version": "8.0.0", @@ -33608,9 +34415,9 @@ } }, "es-abstract": { - "version": "1.23.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.1.tgz", - "integrity": "sha512-r+YVn6hTqQb+P5kK0u3KeDqrmhHKm+OhU/Mw4jSL4eQtOxXmp75fXIUUb3sUqFZOlb/YtW5JRaIfEC3UyjYUZQ==", + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.2.tgz", + "integrity": "sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w==", "dev": true, "requires": { "array-buffer-byte-length": "^1.0.1", @@ -33650,8 +34457,8 @@ "regexp.prototype.flags": "^1.5.2", "safe-array-concat": "^1.1.2", "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.8", - "string.prototype.trimend": "^1.0.7", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", "string.prototype.trimstart": "^1.0.7", "typed-array-buffer": "^1.0.2", "typed-array-byte-length": "^1.0.1", @@ -33661,12 +34468,6 @@ "which-typed-array": "^1.1.15" } }, - "es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, "es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -35934,16 +36735,16 @@ "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==" }, "express": { - "version": "4.18.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", - "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.1.tgz", + "integrity": "sha512-K4w1/Bp7y8iSiVObmCrtq8Cs79XjJc/RU2YYkZQ7wpUu5ZyZ7MtPHkqoMz4pf+mgXfNvo2qft8D9OnrH2ABk9w==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -36514,6 +37315,7 @@ "version": "7.5.0", "resolved": "https://registry.npmjs.org/ganache/-/ganache-7.5.0.tgz", "integrity": "sha512-afNTJYBEaFrLPRrn7eUxH39TgnrffvHn/4T6THzQrc3rpfe4DOxw2nY2XEQxfsq1t4OqKSXtxomzyo26RZiOzw==", + "dev": true, "requires": { "@trufflesuite/bigint-buffer": "1.1.10", "@types/bn.js": "^5.1.0", @@ -36532,6 +37334,7 @@ "resolved": "https://registry.npmjs.org/@trufflesuite/bigint-buffer/-/bigint-buffer-1.1.10.tgz", "integrity": "sha512-pYIQC5EcMmID74t26GCC67946mgTJFiLXOT/BYozgrd4UEY2JHEGLhWi9cMiQCt5BSqFEvKkCHNnoj82SRjiEw==", "bundled": true, + "dev": true, "requires": { "node-gyp-build": "4.4.0" }, @@ -36540,7 +37343,8 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz", "integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==", - "bundled": true + "bundled": true, + "dev": true } } }, @@ -36549,6 +37353,7 @@ "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz", "integrity": "sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==", "bundled": true, + "dev": true, "requires": { "@types/node": "*" } @@ -36557,37 +37362,43 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", - "bundled": true + "bundled": true, + "dev": true }, "@types/node": { "version": "17.0.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.0.tgz", "integrity": "sha512-eMhwJXc931Ihh4tkU+Y7GiLzT/y/DBNpNtr4yU9O2w3SYBsr9NaOPhQlLKRmoWtI54uNwuo0IOUFQjVOTZYRvw==", - "bundled": true + "bundled": true, + "dev": true }, "@types/seedrandom": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.1.tgz", "integrity": "sha512-giB9gzDeiCeloIXDgzFBCgjj1k4WxcDrZtGl6h1IqmUPlxF+Nx8Ve+96QCyDZ/HseB/uvDsKbpib9hU5cU53pw==", - "bundled": true + "bundled": true, + "dev": true }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "bundled": true + "bundled": true, + "dev": true }, "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "bundled": true + "bundled": true, + "dev": true }, "buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "bundled": true, + "dev": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -36597,6 +37408,7 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", + "dev": true, "optional": true, "requires": { "node-gyp-build": "^4.3.0" @@ -36607,6 +37419,7 @@ "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.0.tgz", "integrity": "sha512-M5imwzQn6y+ODBfgi+cfgZv2hIUI6oYU/0f35Mdb1ujGeqeoI5tOnl9Q13DTH7LW+7er+NYq8stNOKZD/Z3U/A==", "bundled": true, + "dev": true, "requires": { "queue-tick": "^1.0.0" } @@ -36616,6 +37429,7 @@ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "bundled": true, + "dev": true, "requires": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -36630,7 +37444,8 @@ "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "bundled": true + "bundled": true, + "dev": true } } }, @@ -36638,13 +37453,15 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.0.tgz", "integrity": "sha512-AGvFfs+d0JKCJQ4o01ASQLGPmSCxgfU9RFXvzPvZdjKK8oscynksuJhWrSTSw7j7Ep/sZct5b5ZhYCi8S/t0HQ==", - "bundled": true + "bundled": true, + "dev": true }, "hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "bundled": true, + "dev": true, "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -36655,6 +37472,7 @@ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "bundled": true, + "dev": true, "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -36665,25 +37483,29 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "bundled": true + "bundled": true, + "dev": true }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "bundled": true + "bundled": true, + "dev": true }, "is-buffer": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "bundled": true + "bundled": true, + "dev": true }, "keccak": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz", "integrity": "sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==", "bundled": true, + "dev": true, "requires": { "node-addon-api": "^2.0.0", "node-gyp-build": "^4.2.0", @@ -36695,6 +37517,7 @@ "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-6.1.0.tgz", "integrity": "sha512-8C7oJDT44JXxh04aSSsfcMI8YiaGRhOFI9/pMEL7nWJLVsWajDPTRxsSHTM2WcTVY5nXM+SuRHzPPi0GbnDX+w==", "bundled": true, + "dev": true, "requires": { "abstract-leveldown": "^7.2.0", "napi-macros": "~2.0.0", @@ -36706,6 +37529,7 @@ "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz", "integrity": "sha512-DnhQwcFEaYsvYDnACLZhMmCWd3rkOeEvglpa4q5i/5Jlm3UIsWaxVzuXvDLFCSCWRO3yy2/+V/G7FusFgejnfQ==", "bundled": true, + "dev": true, "requires": { "buffer": "^6.0.3", "catering": "^2.0.0", @@ -36720,6 +37544,7 @@ "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-3.1.0.tgz", "integrity": "sha512-BWRCMHBxbIqPxJ8vHOvKUsaO0v1sLYZtjN3K2iZJsRBYtp+ONsY6Jfi6hy9K3+zolgQRryhIn2NRZjZnWJ9NmQ==", "bundled": true, + "dev": true, "requires": { "catering": "^2.1.0" } @@ -36728,7 +37553,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-2.1.0.tgz", "integrity": "sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA==", - "bundled": true + "bundled": true, + "dev": true } } }, @@ -36736,49 +37562,57 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "bundled": true + "bundled": true, + "dev": true }, "minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "bundled": true + "bundled": true, + "dev": true }, "napi-macros": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", - "bundled": true + "bundled": true, + "dev": true }, "node-addon-api": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", - "bundled": true + "bundled": true, + "dev": true }, "node-gyp-build": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", - "bundled": true + "bundled": true, + "dev": true }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "bundled": true + "bundled": true, + "dev": true }, "queue-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.0.tgz", "integrity": "sha512-ULWhjjE8BmiICGn3G8+1L9wFpERNxkf8ysxkAer4+TFdRefDaXOCV5m92aMB9FtBVmn/8sETXLXY6BfW7hyaWQ==", - "bundled": true + "bundled": true, + "dev": true }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "bundled": true, + "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -36789,13 +37623,15 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "bundled": true + "bundled": true, + "dev": true }, "secp256k1": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", "bundled": true, + "dev": true, "requires": { "elliptic": "^6.5.4", "node-addon-api": "^2.0.0", @@ -36807,6 +37643,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "bundled": true, + "dev": true, "requires": { "safe-buffer": "~5.2.0" } @@ -36815,6 +37652,7 @@ "version": "5.0.7", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", + "dev": true, "optional": true, "requires": { "node-gyp-build": "^4.3.0" @@ -36824,7 +37662,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "bundled": true + "bundled": true, + "dev": true } } }, @@ -37405,12 +38244,6 @@ "p-limit": "^1.1.0" } }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true - }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -38204,8 +39037,7 @@ "is-buffer": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" }, "is-callable": { "version": "1.2.7", @@ -39124,14 +39956,12 @@ "level-supports": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz", - "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==", - "dev": true + "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==" }, "level-transcoder": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", - "dev": true, "requires": { "buffer": "^6.0.3", "module-error": "^1.0.1" @@ -39141,7 +39971,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -39262,8 +40091,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.assign": { "version": "4.2.0", @@ -40371,8 +41199,7 @@ "module-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", - "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", - "dev": true + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==" }, "ms": { "version": "2.1.3", @@ -40838,38 +41665,37 @@ } }, "object.entries": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", - "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" } }, "object.fromentries": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", - "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" } }, "object.groupby": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz", - "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, "requires": { - "array.prototype.filter": "^1.0.3", - "call-bind": "^1.0.5", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.0.0" + "es-abstract": "^1.23.2" } }, "object.omit": { @@ -40900,14 +41726,14 @@ } }, "object.values": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", - "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" } }, "obliterator": { @@ -41022,6 +41848,12 @@ "aggregate-error": "^3.0.0" } }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true + }, "package-utils": { "version": "file:packages/package-utils", "requires": { @@ -41243,6 +42075,12 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -41566,8 +42404,7 @@ "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, "quick-lru": { "version": "5.1.1", @@ -42271,6 +43108,12 @@ "integrity": "sha512-H7AkJWMobeskkttHyhTVtS0fxpFLjxhbfMa6Bk3wimP7sdPRGL3EyCg3sAQenFfAe+xQ+oAc85Nmtvq0ROM83Q==", "dev": true }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", + "dev": true + }, "resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -43111,6 +43954,12 @@ "wrap-ansi": "^2.0.0" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true + }, "fs-extra": { "version": "0.30.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", @@ -43148,12 +43997,6 @@ "graceful-fs": "^4.1.6" } }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true - }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -43189,12 +44032,6 @@ "ansi-regex": "^2.0.0" } }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", - "dev": true - }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", @@ -43598,6 +44435,12 @@ "ms": "2.0.0" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true + }, "diff": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", @@ -43769,24 +44612,12 @@ "p-limit": "^1.1.0" } }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true - }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "dev": true }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true - }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -43820,6 +44651,12 @@ "isexe": "^2.0.0" } }, + "which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true + }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", @@ -44017,9 +44854,9 @@ }, "dependencies": { "node-addon-api": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz", - "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==" } } }, @@ -44184,14 +45021,14 @@ } }, "string.prototype.trimstart": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", - "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" } }, "strip-ansi": { @@ -44203,6 +45040,12 @@ "ansi-regex": "^3.0.0" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -44564,9 +45407,9 @@ } }, "tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -44920,12 +45763,6 @@ "requires": { "minimist": "^1.2.0" } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true } } }, @@ -45086,9 +45923,9 @@ } }, "typed-array-length": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", - "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", "dev": true, "requires": { "call-bind": "^1.0.7", @@ -45115,9 +45952,9 @@ } }, "typescript": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", - "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", "dev": true, "peer": true }, @@ -45178,8 +46015,7 @@ "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "union-value": { "version": "1.0.1", @@ -45918,9 +46754,9 @@ } }, "which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", "dev": true }, "which-pm-runs": { @@ -46285,12 +47121,6 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true } } }, diff --git a/package.json b/package.json index 0e3f2f777e..870fb1bfdb 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "clean:ganache": "rimraf ./ganache-chain-db/", "start:bridging": "cd ./lib/safe-contracts && npm i || true && cd ../../ && bash ./scripts/setup-foreign-chain.sh && node ./scripts/setup-bridging-contracts.js &", "start:blockchain:client": "bash ./scripts/start-blockchain-client.sh", - "start:blockchain:client:2": "CHAIN_ID=2656692 PORT=8546 DBPATH=./ganache-chain-db-2 bash ./scripts/start-blockchain-client.sh", + "start:blockchain:client:2": "PORT=8546 DBPATH=./ganache-chain-db-2 bash ./scripts/start-blockchain-client.sh", + "start:blockchain:client:both": "CHAIN_ID=$CHAIN_ID_1 npm run start:blockchain:client & sleep 1 && CHAIN_ID=$CHAIN_ID_2 npm run start:blockchain:client:2", "start:reputation:oracle": "./scripts/start-reputation-oracle.js", "reset:blockchain:client": "npm run stop:blockchain:client && rm -rf ./ganache-chain-db*", "stop:blockchain:client": "bash ./scripts/stop-blockchain-client.sh", @@ -37,12 +38,14 @@ "flatten:contracts": "mkdir -p ./build/flattened/ && steamroller contracts/colonyNetwork/IColonyNetwork.sol > build/flattened/flatIColonyNetwork.sol && steamroller contracts/colony/IColony.sol > build/flattened/flatIColony.sol && steamroller contracts/reputationMiningCycle/IReputationMiningCycle.sol > build/flattened/flatIReputationMiningCycle.sol && steamroller contracts/colony/IMetaColony.sol > build/flattened/flatIMetaColony.sol && steamroller contracts/common/IRecovery.sol > build/flattened/flatIRecovery.sol && steamroller contracts/common/IEtherRouter.sol > build/flattened/flatIEtherRouter.sol", "test:reputation:1": "npm run start:blockchain:client && npx hardhat test ./test/reputation-system/*.js --network development", "test:reputation:2": "npm run start:blockchain:client && npx hardhat test ./test/reputation-system/reputation-mining-client/* --network development", + "test:reputation:1:anotherChain": "CHAIN_ID=101010101 npm run start:blockchain:client && MINING_CHAIN_ID=101010101 CHAIN_ID=101010101 npx hardhat test ./test/reputation-system/*.js --network development", + "test:reputation:2:anotherChain": "CHAIN_ID=101010101 npm run start:blockchain:client && MINING_CHAIN_ID=101010101 CHAIN_ID=101010101 npx hardhat test ./test/reputation-system/reputation-mining-client/* --network development", "test:reputation:coverage": "npx hardhat coverage --solcoverjs ./.solcover.reputation.js --temp build-coverage --testfiles './test/reputation-system/**/*'", "test:contracts": "npm run start:blockchain:client && npx hardhat test ./test/contracts-network/* ./test/packages/* --network development", - "test:contracts:bridging:1": "npm run start:blockchain:client && npm run start:blockchain:client:2 && HARDHAT_FOREIGN=false npx hardhat test ./test/cross-chain/* --network development", - "test:contracts:bridging:2": "npm run start:blockchain:client && npm run start:blockchain:client:2 && HARDHAT_FOREIGN=true npx hardhat test ./test/cross-chain/* --network development", - "test:contracts:bridging:1:coverage": "HARDHAT_FOREIGN=false npx hardhat coverage --solcoverjs ./.solcover.crosschain.js --temp build-coverage --testfiles './test/cross-chain/**/*'", - "test:contracts:bridging:2:coverage": "npm run start:blockchain:client & npm run start:blockchain:client:2 & HARDHAT_FOREIGN=true npx hardhat coverage --solcoverjs ./.solcover.crosschain.js --temp build-coverage --testfiles './test/cross-chain/**/*'", + "test:contracts:bridging:1": "CHAIN_ID_1=265669100 CHAIN_ID_2=265669101 npm run start:blockchain:client:both && HARDHAT_FOREIGN=false npx hardhat test ./test/cross-chain/* --network development", + "test:contracts:bridging:2": "CHAIN_ID_1=265669101 CHAIN_ID_2=265669100 npm run start:blockchain:client:both && HARDHAT_FOREIGN=true npx hardhat test ./test/cross-chain/* --network development", + "test:contracts:bridging:1:coverage": "CHAIN_ID=265669101 npm run start:blockchain:client:2 && HARDHAT_FOREIGN=false MINING_CHAIN_ID=265669100 npx hardhat --show-stack-traces coverage --solcoverjs ./.solcover.crosschain.js --temp build-coverage --testfiles './test/cross-chain/**/*'", + "test:contracts:bridging:2:coverage": "CHAIN_ID=265669100 npm run start:blockchain:client:2 && HARDHAT_FOREIGN=true MINING_CHAIN_ID=265669100 CHAIN_ID=265669101 npx hardhat coverage --solcoverjs ./.solcover.crosschain.js --temp build-coverage --testfiles './test/cross-chain/**/*'", "test:contracts:extensions": "npm run start:blockchain:client && npx hardhat test ./test/extensions/* --network development", "test:contracts:chainid": "npm run stop:blockchain:client && npm run start:blockchain:client && npx hardhat test ./test-chainid/*", "test:contracts:chainid:coverage": "npx hardhat coverage --solcoverjs ./.solcover.chainid.js --temp build-coverage --testfiles './test-chainid/**/*'", diff --git a/packages/reputation-miner/ReputationMiner.js b/packages/reputation-miner/ReputationMiner.js index 14d76c24ea..159a4e55e9 100644 --- a/packages/reputation-miner/ReputationMiner.js +++ b/packages/reputation-miner/ReputationMiner.js @@ -147,28 +147,31 @@ class ReputationMiner { INNER JOIN colonies ON colonies.rowid=reputations.colony_rowid INNER JOIN users ON users.rowid=reputations.user_rowid INNER JOIN reputation_states ON reputation_states.rowid=reputations.reputation_rowid + INNER JOIN skills ON skills.rowid=reputations.skill_rowid WHERE reputation_states.root_hash=? AND colonies.address=? - AND reputations.skill_id=? + AND skills.skill_id=? AND users.address=?` ); this.queries.insertReputation = this.db.prepare( - `INSERT OR IGNORE INTO reputations (reputation_rowid, colony_rowid, skill_id, user_rowid, value) + `INSERT OR IGNORE INTO reputations (reputation_rowid, colony_rowid, skill_rowid, user_rowid, value) SELECT (SELECT reputation_states.rowid FROM reputation_states WHERE reputation_states.root_hash=?), (SELECT colonies.rowid FROM colonies WHERE colonies.address=?), - ?, + (SELECT skills.rowid FROM skills WHERE skills.skill_id=?), (SELECT users.rowid FROM users WHERE users.address=?), ?` ); this.queries.getAllReputationsInHash = this.db.prepare( - `SELECT reputations.skill_id, reputations.value, reputation_states.root_hash, colonies.address as colony_address, users.address as user_address + // eslint-disable-next-line max-len + `SELECT skills.skill_id, reputations.value, reputation_states.root_hash, colonies.address as colony_address, users.address as user_address FROM reputations INNER JOIN colonies ON colonies.rowid=reputations.colony_rowid INNER JOIN users ON users.rowid=reputations.user_rowid INNER JOIN reputation_states ON reputation_states.rowid=reputations.reputation_rowid + INNER JOIN skills ON skills.rowid=reputations.skill_rowid WHERE reputation_states.root_hash=? ORDER BY substr(reputations.value, 67) ASC` ); @@ -179,9 +182,10 @@ class ReputationMiner { INNER JOIN colonies ON colonies.rowid=reputations.colony_rowid INNER JOIN users ON users.rowid=reputations.user_rowid INNER JOIN reputation_states ON reputation_states.rowid=reputations.reputation_rowid + INNER JOIN skills ON skills.rowid=reputations.skill_rowid WHERE reputation_states.root_hash=? AND users.address=? - AND reputations.skill_id=? + AND skills.skill_id=? AND colonies.address=?` ); @@ -191,19 +195,21 @@ class ReputationMiner { INNER JOIN colonies ON colonies.rowid=reputations.colony_rowid INNER JOIN users ON users.rowid=reputations.user_rowid INNER JOIN reputation_states ON reputation_states.rowid=reputations.reputation_rowid + INNER JOIN skills ON skills.rowid=reputations.skill_rowid WHERE reputation_states.root_hash=? AND colonies.address=? - AND reputations.skill_id=? + AND skills.skill_id=? AND users.address!='0x0000000000000000000000000000000000000000' ORDER BY reputations.value DESC` ); this.queries.getReputationsForAddress = this.db.prepare( - `SELECT DISTINCT reputations.skill_id as skill_id, reputations.value as value + `SELECT DISTINCT skills.skill_id as skill_id, reputations.value as value FROM reputations INNER JOIN colonies ON colonies.rowid=reputations.colony_rowid INNER JOIN users ON users.rowid=reputations.user_rowid INNER JOIN reputation_states ON reputation_states.rowid=reputations.reputation_rowid + INNER JOIN skills ON skills.rowid=reputations.skill_rowid WHERE reputation_states.root_hash=? AND colonies.address=? AND users.address=? @@ -921,7 +927,8 @@ class ReputationMiner { const keyElements = ReputationMiner.breakKeyInToElements(key); const [colonyAddress, , userAddress] = keyElements; - const skillId = parseInt(keyElements[1], 16); + // const skillId = parseInt(keyElements[1], 16); + const skillId = ethers.BigNumber.from(keyElements[1]).toString(); const reputationValue = await this.queries.getReputationValue.all(rootHash, userAddress, skillId, colonyAddress); if (reputationValue.length === 0) { @@ -977,7 +984,8 @@ class ReputationMiner { const keyElements = ReputationMiner.breakKeyInToElements(key); const [colonyAddress, , userAddress] = keyElements; - const skillId = parseInt(keyElements[1], 16); + // const skillId = parseInt(keyElements[1], 16); + const skillId = ethers.BigNumber.from(keyElements[1]).toString(); res = await this.queries.getReputationValue.all(rootHash, userAddress, skillId, colonyAddress); @@ -1486,7 +1494,7 @@ class ReputationMiner { const decimalValue = new BN(this.reputations[key].slice(2, 66), 16); const keyElements = ReputationMiner.breakKeyInToElements(key); const [colonyAddress, , userAddress] = keyElements; - const skillId = parseInt(keyElements[1], 16); + const skillId = ethers.BigNumber.from(keyElements[1]); console.log("colonyAddress", colonyAddress); console.log("userAddress", userAddress); @@ -1508,7 +1516,7 @@ class ReputationMiner { const value = this.reputations[key]; const keyElements = ReputationMiner.breakKeyInToElements(key); const [colonyAddress, , userAddress] = keyElements; - const skillId = parseInt(keyElements[1], 16); + const skillId = ethers.BigNumber.from(keyElements[1]).toString(); this.queries.saveColony.run(colonyAddress); this.queries.saveUser.run(userAddress); this.queries.saveSkill.run(skillId); @@ -1691,11 +1699,11 @@ class ReputationMiner { const saveSkill = db.prepare(`INSERT OR IGNORE INTO skills (skill_id) VALUES (?)`); const insertReputation = db.prepare( - `INSERT OR IGNORE INTO reputations (reputation_rowid, colony_rowid, skill_id, user_rowid, value) + `INSERT OR IGNORE INTO reputations (reputation_rowid, colony_rowid, skill_rowid, user_rowid, value) SELECT (SELECT reputation_states.rowid FROM reputation_states WHERE reputation_states.root_hash=?), (SELECT colonies.rowid FROM colonies WHERE colonies.address=?), - ?, + (SELECT skills.rowid FROM skills WHERE skills.skill_id=?), (SELECT users.rowid FROM users WHERE users.address=?), ?` ); @@ -1720,15 +1728,15 @@ class ReputationMiner { "CREATE TABLE IF NOT EXISTS reputation_states ( rowid INTEGER PRIMARY KEY, root_hash text NOT NULL UNIQUE, n_leaves INTEGER NOT NULL)" ).run(); await db.prepare("CREATE TABLE IF NOT EXISTS colonies ( rowid INTEGER PRIMARY KEY, address text NOT NULL UNIQUE )").run(); - await db.prepare("CREATE TABLE IF NOT EXISTS skills ( skill_id INTEGER PRIMARY KEY )").run(); + await db.prepare("CREATE TABLE IF NOT EXISTS skills ( rowid INTEGER PRIMARY KEY, skill_id text NOT NULL UNIQUE )").run(); await db.prepare( `CREATE TABLE IF NOT EXISTS reputations ( reputation_rowid INTEGER NOT NULL, colony_rowid INTEGER NOT NULL, - skill_id INTEGER NOT NULL, + skill_rowid INTEGER NOT NULL, user_rowid INTEGER NOT NULL, value text NOT NULL, - PRIMARY KEY("reputation_rowid","colony_rowid","skill_id","user_rowid") + PRIMARY KEY("reputation_rowid","colony_rowid","skill_rowid","user_rowid") )` ).run(); @@ -1747,7 +1755,7 @@ class ReputationMiner { await db.pragma('journal_mode = WAL'); await db.prepare('CREATE INDEX IF NOT EXISTS reputation_states_root_hash ON reputation_states (root_hash)').run(); await db.prepare('CREATE INDEX IF NOT EXISTS users_address ON users (address)').run(); - await db.prepare('CREATE INDEX IF NOT EXISTS reputation_skill_id ON reputations (skill_id)').run(); + await db.prepare('CREATE INDEX IF NOT EXISTS reputation_skill_id ON reputations (skill_rowid)').run(); await db.prepare('CREATE INDEX IF NOT EXISTS colonies_address ON colonies (address)').run(); // We added a composite key to reputations - do we need to port it over? @@ -1825,6 +1833,54 @@ class ReputationMiner { console.log('Explicit primary keys added to secondary tables'); } + // TODO: Migration from when we moved skillid from integer to text + res = await db.prepare("SELECT type FROM PRAGMA_TABLE_INFO('reputations') WHERE name='skill_id'").all(); + if (res.length === 1) { + console.log("reputations.skill_id exists, so need to migrate to skill_rowid"); + await db.prepare( + `CREATE TABLE reputations2 ( + reputation_rowid INTEGER NOT NULL, + colony_rowid INTEGER NOT NULL, + skill_rowid INTEGER NOT NULL, + user_rowid INTEGER NOT NULL, + value text NOT NULL, + PRIMARY KEY("reputation_rowid","colony_rowid","skill_rowid","user_rowid") + )` + ).run(); + + await db.prepare( + `CREATE TABLE skills2 ( + rowid INTEGER PRIMARY KEY, + skill_id text NOT NULL UNIQUE + )` + ).run() + + await db.prepare(`INSERT INTO skills2 (rowid, skill_id) SELECT rowid, skill_id FROM skills;`).run() + await db.prepare(`DROP TABLE skills`).run() + await db.prepare(`ALTER TABLE skills2 RENAME TO skills`).run() + + + // Open another connection to write in to reputations2 while iterating over reputations + // This is generally prevented to avoid race conditions etc but is safe here - we are exclusively + // reading from a table we are not writing to. + const db2 = new Database(db.name, { }); + const statementToIterate = db2.prepare('SELECT * FROM reputations'); + // eslint-disable-next-line no-restricted-syntax + for (const row of statementToIterate.iterate()) { + const skill = await db.prepare(`SELECT rowid FROM skills WHERE skill_id = ?`).get(row.skill_id.toString()); + await db.prepare( + `INSERT INTO reputations2 (reputation_rowid, colony_rowid, skill_rowid, user_rowid, value) + VALUES (?, ?, ?, ?, ?)`).run(row.reputation_rowid, row.colony_rowid, skill.rowid, row.user_rowid, row.value); + } + + db2.close() + + await db.prepare(`DROP TABLE reputations`).run() + await db.prepare(`ALTER TABLE reputations2 RENAME TO reputations`).run() + + await db.prepare('CREATE INDEX IF NOT EXISTS reputation_skill_id ON reputations (skill_rowid)').run(); + console.log("skill_id -> skill_rowid migration complete"); + } } async resetDB() { diff --git a/packages/reputation-miner/ReputationMinerClient.js b/packages/reputation-miner/ReputationMinerClient.js index 359b34b8e9..4bec46eb52 100644 --- a/packages/reputation-miner/ReputationMinerClient.js +++ b/packages/reputation-miner/ReputationMinerClient.js @@ -392,7 +392,7 @@ class ReputationMinerClient { // Let's process the reputation log if it's been this._processingDelay blocks if (this.blocksSinceCycleCompleted < this._processingDelay) { this.blocksSinceCycleCompleted += 1; - if (this.blocksSinceCycleCompleted === 1) { + if (this.blocksSinceCycleCompleted === 1) { this._adapter.log(`⏰ Waiting for ${this._processingDelay} blocks before processing next log`) }; this.endDoBlockChecks(); diff --git a/parity-genesis.template.json b/parity-genesis.template.json index 2aa9b6ce51..64e2b477fa 100644 --- a/parity-genesis.template.json +++ b/parity-genesis.template.json @@ -8,7 +8,7 @@ "accountStartNonce": "0x0", "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", - "networkID" : "0x7CE", + "networkID" : "0xFD5C9EC", "registrar" : "0x0000000000000000000000000000000000001337", "eip150Transition": "0x0", "eip160Transition": "0x0", @@ -25,7 +25,14 @@ "eip145Transition": "0x0", "eip1014Transition": "0x0", "eip1052Transition": "0x0", - "wasmActivationTransition": "0x0" + "wasmActivationTransition": "0x0", + "eip1283Transition": "0x0", + "eip1283DisableTransition": "0x0", + "eip1283ReenableTransition": "0x0", + "eip1344Transition": "0x0", + "eip1706Transition": "0x0", + "eip1884Transition": "0x0", + "eip2028Transition": "0x0" }, "genesis": { "seal": { diff --git a/scripts/check-recovery.js b/scripts/check-recovery.js index acbfb43557..b493e0fd35 100755 --- a/scripts/check-recovery.js +++ b/scripts/check-recovery.js @@ -25,6 +25,8 @@ walkSync("./contracts/").forEach((contractName) => { // ColonyNetwork, ColonyNetworkAuction, ColonyNetworkENS, ColonyNetworkMining if ( [ + "contracts/bridging/IColonyBridge.sol", + "contracts/bridging/WormholeBridgeForColony.sol", "contracts/colony/ColonyAuthority.sol", "contracts/colony/ColonyStorage.sol", "contracts/colony/IColony.sol", @@ -96,6 +98,7 @@ walkSync("./contracts/").forEach((contractName) => { "contracts/testHelpers/TransferTest.sol", "contracts/testHelpers/RequireExecuteCall.sol", "contracts/testHelpers/VotingReputationMisaligned.sol", + "contracts/testHelpers/WormholeMock.sol", "contracts/testHelpers/ZodiacBridgeModuleMock.sol", "contracts/tokenLocking/ITokenLocking.sol", "contracts/tokenLocking/TokenLocking.sol", diff --git a/scripts/check-storage.js b/scripts/check-storage.js index 9274e2345e..b4770dabf4 100755 --- a/scripts/check-storage.js +++ b/scripts/check-storage.js @@ -16,6 +16,7 @@ walkSync("./contracts/").forEach((contractName) => { // Contracts listed here are allowed to have storage variables if ( [ + "contracts/bridging/WormholeBridgeForColony.sol", "contracts/colony/ColonyAuthority.sol", "contracts/colony/ColonyStorage.sol", "contracts/colonyNetwork/ColonyNetworkAuthority.sol", @@ -44,6 +45,7 @@ walkSync("./contracts/").forEach((contractName) => { "contracts/gnosis/MultiSigWallet.sol", // Not directly used by any colony contracts "contracts/patriciaTree/PatriciaTreeBase.sol", // Only used by mining clients "contracts/reputationMiningCycle/ReputationMiningCycleStorage.sol", + "contracts/testHelpers/BridgeMock.sol", "contracts/testHelpers/ERC721Mock.sol", "contracts/testHelpers/ToggleableToken.sol", "contracts/testHelpers/testExtensions/TestExtensionBase.sol", @@ -54,6 +56,7 @@ walkSync("./contracts/").forEach((contractName) => { "contracts/testHelpers/testExtensions/TestVotingToken.sol", "contracts/testHelpers/GasGuzzler.sol", "contracts/testHelpers/VotingReputationMisaligned.sol", + "contracts/testHelpers/WormholeMock.sol", "contracts/tokenLocking/TokenLockingStorage.sol", "contracts/Migrations.sol", "contracts/Token.sol", // Imported from colonyToken repo diff --git a/scripts/deployOldUpgradeableVersion.js b/scripts/deployOldUpgradeableVersion.js index ccc92cf9fe..d5d88faf79 100644 --- a/scripts/deployOldUpgradeableVersion.js +++ b/scripts/deployOldUpgradeableVersion.js @@ -79,6 +79,22 @@ module.exports.deployColonyNetworkVersionGLWSS4 = () => { ); }; +module.exports.registerOldColonyVersion = async (colonyVersionResolverAddress, colonyNetwork) => { + const colonyVersionResolver = await artifacts.require("Resolver").at(colonyVersionResolverAddress); + const versionImplementationAddress = await colonyVersionResolver.lookup(web3.utils.soliditySha3("version()").slice(0, 10)); + const versionImplementation = await artifacts.require("IMetaColony").at(versionImplementationAddress); + const version = await versionImplementation.version(); + + const registeredResolver = await colonyNetwork.getColonyVersionResolver(version); + if (registeredResolver !== ADDRESS_ZERO && registeredResolver !== colonyVersionResolverAddress) { + throw new Error(`Version ${version} already registered at unexpected address`); + } else if (registeredResolver === ADDRESS_ZERO) { + const metaColonyAddress = await colonyNetwork.getMetaColony(); + const metaColony = await artifacts.require("IMetaColony").at(metaColonyAddress); + await metaColony.addNetworkColonyVersion(version, colonyVersionResolverAddress); + } +}; + module.exports.deployOldColonyVersion = async (contractName, interfaceName, implementationNames, versionTag, colonyNetwork) => { if (versionTag.indexOf(" ") !== -1) { throw new Error("Version tag cannot contain spaces"); @@ -90,19 +106,9 @@ module.exports.deployOldColonyVersion = async (contractName, interfaceName, impl // Already deployed... if truffle's not snapshotted it away. See if there's any code there. const { resolverAddress } = colonyDeployed[interfaceName][versionTag]; const code = await web3GetCode(resolverAddress); - console.log(versionTag, "code", code); if (code !== "0x") { - // Could be a different colony network. Check it's registered with the network. - const resolver = await artifacts.require("Resolver").at(resolverAddress); - const versionImplementationAddress = await resolver.lookup(web3.utils.soliditySha3("version()").slice(0, 10)); - const versionImplementation = await artifacts.require("IMetaColony").at(versionImplementationAddress); - const version = await versionImplementation.version(); - const registeredResolverAddress = await colonyNetwork.getColonyVersionResolver(version); - if (registeredResolverAddress === ADDRESS_ZERO) { - const metaColonyAddress = await colonyNetwork.getMetaColony(); - const metaColony = await artifacts.require("IMetaColony").at(metaColonyAddress); - await metaColony.addNetworkColonyVersion(version, resolverAddress); - } + // Must also check it's registered + await module.exports.registerOldColonyVersion(resolverAddress, colonyNetwork); return colonyDeployed[interfaceName][versionTag]; } } @@ -116,14 +122,7 @@ module.exports.deployOldColonyVersion = async (contractName, interfaceName, impl colonyNetwork, ); - const colonyVersionResolver = await artifacts.require("Resolver").at(colonyVersionResolverAddress); - const versionImplementationAddress = await colonyVersionResolver.lookup(web3.utils.soliditySha3("version()").slice(0, 10)); - const versionImplementation = await artifacts.require("IMetaColony").at(versionImplementationAddress); - const version = await versionImplementation.version(); - - const metaColonyAddress = await colonyNetwork.getMetaColony(); - const metaColony = await artifacts.require("IMetaColony").at(metaColonyAddress); - await metaColony.addNetworkColonyVersion(version, colonyVersionResolverAddress); + await module.exports.registerOldColonyVersion(colonyVersionResolverAddress, colonyNetwork); const interfaceArtifact = fs.readFileSync(`./colonyNetwork-${versionTag}/build/contracts/${interfaceName}.json`); const OldInterface = contract(JSON.parse(interfaceArtifact)); @@ -140,7 +139,7 @@ module.exports.deployOldColonyVersion = async (contractName, interfaceName, impl colonyDeployed[interfaceName] = colonyDeployed[interfaceName] || {}; colonyDeployed[interfaceName][versionTag] = { OldInterface, OldAuthority, resolverAddress: colonyVersionResolverAddress }; - + console.log("Deployed", interfaceName, "at version", versionTag, "with resolver", colonyVersionResolverAddress); return colonyDeployed[interfaceName][versionTag]; } catch (e) { console.log(e); @@ -281,6 +280,7 @@ module.exports.deployOldUpgradeableVersion = async (contractName, interfaceName, const resolverAddress = res.split("\n").slice(-2)[0].trim(); deployedResolverAddresses[interfaceName] = deployedResolverAddresses[interfaceName] || {}; deployedResolverAddresses[interfaceName][versionTag] = resolverAddress; + console.log("Deployed", interfaceName, "at version", versionTag, "with resolver", resolverAddress); return resolverAddress; }; diff --git a/scripts/mockBridgeMonitor.js b/scripts/mockBridgeMonitor.js index 8935b98264..c9371557df 100644 --- a/scripts/mockBridgeMonitor.js +++ b/scripts/mockBridgeMonitor.js @@ -1,7 +1,13 @@ const ethers = require("ethers"); // eslint-disable-next-line import/no-unresolved -const bridgeAbi = require("../artifacts/contracts/testHelpers/BridgeMock.sol/BridgeMock.json").abi; +const bridgeAbi = require("../artifacts/contracts/testHelpers/WormholeMock.sol/WormholeMock.json").abi; +// eslint-disable-next-line import/no-unresolved +const wormholeBridgeForColonyAbi = require("../artifacts/contracts/bridging/WormholeBridgeForColony.sol/WormholeBridgeForColony.json").abi; + +function ethereumAddressToWormholeAddress(address) { + return ethers.utils.hexZeroPad(ethers.utils.hexStripZeros(ethers.utils.hexlify(address)), 32); +} class MockBridgeMonitor { /** @@ -12,28 +18,173 @@ class MockBridgeMonitor { * @param {string} foreignRpc The endpoint that the foreign chain can be queried on * @param {string} homeBridgeAddress The address of the home bridge contract * @param {string} foreignBridgeAddress The address of the foreign bridge contract + * @param {string} homeColonyBridgeAddress The address of the home colony bridge contract + * @param {string} foreignColonyBridgeAddress The address of the foreign colony bridge contract */ - constructor(homeRpc, foreignRpc, homeBridgeAddress, foreignBridgeAddress) { - const providerHome = new ethers.providers.JsonRpcProvider(homeRpc).getSigner(); - const providerForeign = new ethers.providers.JsonRpcProvider(foreignRpc).getSigner(); + constructor(homeRpc, foreignRpc, homeBridgeAddress, foreignBridgeAddress, homeColonyBridgeAddress, foreignColonyBridgeAddress) { + this.homeRpc = homeRpc; + this.foreignRpc = foreignRpc; + this.homeBridgeAddress = homeBridgeAddress; + this.foreignBridgeAddress = foreignBridgeAddress; + this.homeColonyBridgeAddress = homeColonyBridgeAddress; + this.foreignColonyBridgeAddress = foreignColonyBridgeAddress; - const homeBridge = new ethers.Contract(homeBridgeAddress, bridgeAbi, providerHome); - const foreignBridge = new ethers.Contract(foreignBridgeAddress, bridgeAbi, providerForeign); + this.setupListeners(); + } - homeBridge.on("UserRequestForSignature", async (messageId, encodedData) => { - const [target, data, gasLimit, sender] = ethers.utils.defaultAbiCoder.decode(["address", "bytes", "uint256", "address"], encodedData); - await foreignBridge.execute(target, data, gasLimit, messageId, sender); + getPromiseForNextBridgedTransaction(_count = 1) { + return new Promise((resolve) => { + this.bridgingPromiseCount = _count; + this.resolveBridgingPromise = resolve; }); + } + + // VAAs are the primitives used on wormhole (Verified Action Approvals) + // See https://docs.wormhole.com/wormhole/explore-wormhole/vaa for more details + // Note that the documentation sometimes also calls them VMs (as does IWormhole) + // I believe VM stands for 'Verified Message' + async encodeMockVAA(sender, sequence, nonce, payload, consistencyLevel, chainId) { + const version = 1; + const timestamp = Math.floor(Date.now() / 1000); + const emitterChainId = chainId; + const emitterAddress = ethereumAddressToWormholeAddress(sender); + const guardianSetIndex = 0; + const signatures = []; + const hash = ethers.utils.id("something"); + + const vaa = await this.homeBridge.buildVM( + version, + timestamp, + nonce, + emitterChainId, + emitterAddress, + sequence.toString(), + consistencyLevel, + payload, + guardianSetIndex, + signatures, + hash, + ); + return vaa; + } + + setupListeners() { + if (this.homeBridge) { + this.homeBridge.removeAllListeners("LogMessagePublished"); + } + if (this.foreignBridge) { + this.foreignBridge.removeAllListeners("LogMessagePublished"); + } + + this.signerHome = new ethers.providers.JsonRpcProvider(this.homeRpc).getSigner(); + this.signerForeign = new ethers.providers.JsonRpcProvider(this.foreignRpc).getSigner(); + this.homeBridge = new ethers.Contract(this.homeBridgeAddress, bridgeAbi, this.signerHome); + this.foreignBridge = new ethers.Contract(this.foreignBridgeAddress, bridgeAbi, this.signerForeign); + this.homeWormholeBridgeForColony = new ethers.Contract(this.homeColonyBridgeAddress, wormholeBridgeForColonyAbi, this.signerHome); + this.foreignWormholeBridgeForColony = new ethers.Contract(this.foreignColonyBridgeAddress, wormholeBridgeForColonyAbi, this.signerForeign); + + this.skipCount = 0; - foreignBridge.on("UserRequestForSignature", async (messageId, encodedData) => { - const [target, data, gasLimit, sender] = ethers.utils.defaultAbiCoder.decode(["address", "bytes", "uint256", "address"], encodedData); - await homeBridge.execute(target, data, gasLimit, messageId, sender); + this.queue = []; + this.skipped = []; + this.locked = false; + this.homeBridge.on("LogMessagePublished", async (sender, sequence, nonce, payload, consistencyLevel) => { + const { chainId } = await this.signerHome.provider.getNetwork(); + // For our local test chains, I've decreed that the wormhole chain ID is the evmChain ID modulo 265669, times 2 + const wormholeChainId = (chainId % 265669) * 2; + + if (this.skipCount > 0) { + this.skipped.push([this.foreignWormholeBridgeForColony, sender, sequence, nonce, payload, consistencyLevel, wormholeChainId]); + this.skipCount -= 1; + return; + } + this.queue.push([this.foreignWormholeBridgeForColony, sender, sequence, nonce, payload, consistencyLevel, wormholeChainId]); + await this.processQueue(); + }); + + this.foreignBridge.on("LogMessagePublished", async (sender, sequence, nonce, payload, consistencyLevel) => { + const { chainId } = await this.signerForeign.provider.getNetwork(); + const wormholeChainId = (chainId % 265669) * 2; + + if (this.skipCount > 0) { + this.skipped.push([this.homeWormholeBridgeForColony, sender, sequence, nonce, payload, consistencyLevel, wormholeChainId]); + this.skipCount -= 1; + return; + } + this.queue.push([this.homeWormholeBridgeForColony, sender, sequence, nonce, payload, consistencyLevel, wormholeChainId]); + + await this.processQueue(); }); console.log("Mock Bridge Monitor running"); + console.log("Home bridge address: ", this.homeBridgeAddress); + console.log("Foreign bridge address: ", this.foreignBridgeAddress); } close() {} // eslint-disable-line class-methods-use-this + + async processQueue() { + if (this.locked) { + return; + } + if (this.queue.length === 0) { + return; + } + this.locked = true; + const [bridge, sender, sequence, nonce, payload, consistencyLevel, wormholeChainID] = this.queue.shift(); + const vaa = await this.encodeMockVAA(sender, sequence, nonce, payload, consistencyLevel, wormholeChainID); + const tx = await bridge.receiveMessage(vaa, { gasLimit: 1000000 }); + try { + await tx.wait(); + } catch (err) { + // We don't need to do anything here, we just want to make sure the transaction is mined + } + this.bridgingPromiseCount -= 1; + + if (this.bridgingPromiseCount === 0) { + this.resolveBridgingPromise(tx); + } + if (this.locked) { + this.locked = false; + } + if (this.queue.length > 0) { + await this.processQueue(); + } + } + + async bridgeSkipped() { + const [bridge, sender, sequence, nonce, payload, consistencyLevel, homeWormholeChainId] = this.skipped.shift(); + const vaa = await this.encodeMockVAA(sender, sequence, nonce, payload, consistencyLevel, homeWormholeChainId); + const tx = await bridge.receiveMessage(vaa, { gasLimit: 1000000 }); + try { + await tx.wait(); + } catch (err) { + // We don't need to do anything here, we just want to make sure the transaction is mined + } + this.bridgingPromiseCount -= 1; + + if (this.bridgingPromiseCount === 0) { + this.resolveBridgingPromise(tx); + } + } + + async waitUntilSkipped() { + return new Promise((resolve) => { + setInterval(() => { + if (this.skipCount === 0) { + resolve(); + } + }, 1000); + }); + } + + reset() { + this.skipCount = 0; + this.queue = []; + this.skipped = []; + this.locked = false; + this.setupListeners(); + } } module.exports = MockBridgeMonitor; diff --git a/scripts/setup-bridging-contracts.js b/scripts/setup-bridging-contracts.js index 00ac012948..9f9866a3ed 100644 --- a/scripts/setup-bridging-contracts.js +++ b/scripts/setup-bridging-contracts.js @@ -11,7 +11,7 @@ const { TruffleLoader } = require("../packages/package-utils"); const { WAD } = require("../helpers/constants"); const loader = new TruffleLoader({ - contractRoot: path.resolve(__dirname, "..", "artifacts", "contracts"), + contractRoot: path.resolve(__dirname, "..", `artifacts${process.env.SOLIDITY_COVERAGE ? "-coverage" : ""}`, "contracts"), }); const ADDRESS_ZERO = ethers.constants.AddressZero; @@ -39,7 +39,6 @@ async function setupBridging(homeRpcUrl, foreignRpcUrl) { contractDir = path.resolve(__dirname, "..", "artifacts", "contracts", "testHelpers"); const ZodiacBridgeModuleMock = await loader.load({ contractDir, contractName: "ZodiacBridgeModuleMock" }); - const BridgeMock = await loader.load({ contractDir, contractName: "BridgeMock" }); const Erc721Mock = await loader.load({ contractDir, contractName: "ERC721Mock" }); contractDir = path.resolve(__dirname, "..", "artifacts", "colonyToken"); @@ -119,24 +118,31 @@ async function setupBridging(homeRpcUrl, foreignRpcUrl) { } // Deploy a foreign bridge - const foreignBridgeFactory = new ethers.ContractFactory(BridgeMock.abi, BridgeMock.bytecode, ethersForeignSigner); - const foreignBridge = await foreignBridgeFactory.deploy(); - await foreignBridge.deployTransaction.wait(); + const [foreignBridge, foreignColonyBridge] = await deployBridge(ethersForeignSigner); // Deploy a home bridge - const homeBridgeFactory = new ethers.ContractFactory(BridgeMock.abi, BridgeMock.bytecode, ethersHomeSigner); - const homeBridge = await homeBridgeFactory.deploy(); - await homeBridge.deployTransaction.wait(); + const [homeBridge, homeColonyBridge] = await deployBridge(ethersHomeSigner); // Start the bridge service - const bridgeMonitor = new MockBridgeMonitor(homeRpcUrl, foreignRpcUrl, homeBridge.address, foreignBridge.address); // eslint-disable-line no-unused-vars + console.log(`Home RPC Url: ${homeRpcUrl}`); + console.log(`Foreign RPC Url: ${foreignRpcUrl}`); + const bridgeMonitor = new MockBridgeMonitor( + homeRpcUrl, + foreignRpcUrl, + homeBridge.address, + foreignBridge.address, + homeColonyBridge.address, + foreignColonyBridge.address, + ); // eslint-disable-line no-unused-vars console.log(`Home bridge address: ${homeBridge.address}`); console.log(`Foreign bridge address: ${foreignBridge.address}`); + console.log(`Home colony bridge address: ${homeColonyBridge.address}`); + console.log(`Foreign colony bridge address: ${foreignColonyBridge.address}`); console.log(`Gnosis Safe address: ${gnosisSafe.address}`); console.log(`Zodiac Bridge module address: ${zodiacBridge.address}`); console.log(`ERC721 address: ${erc721.address}`); console.log(`Token address: ${token.address}`); - return { gnosisSafe, bridgeMonitor, zodiacBridge, homeBridge, foreignBridge }; + return { gnosisSafe, bridgeMonitor, zodiacBridge, homeBridge, foreignBridge, homeColonyBridge, foreignColonyBridge }; } async function getSig(provider, account, dataHash) { @@ -153,8 +159,29 @@ async function getSig(provider, account, dataHash) { return modifiedSig; } +async function deployBridge(signer) { + let contractDir = path.resolve(__dirname, "..", "artifacts", "contracts", "bridging"); + const WormholeBridgeForColony = await loader.load({ contractDir, contractName: "WormholeBridgeForColony" }, { abi: true, address: false }); + const bridgeFactory = new ethers.ContractFactory(WormholeBridgeForColony.abi, WormholeBridgeForColony.bytecode, signer); + const bridge = await bridgeFactory.deploy(); + await bridge.deployTransaction.wait(); + + contractDir = path.resolve(__dirname, "..", "artifacts", "contracts", "testHelpers"); + const WormholeMock = await loader.load({ contractDir, contractName: "WormholeMock" }, { abi: true, address: false }); + const wormholeFactory = new ethers.ContractFactory(WormholeMock.abi, WormholeMock.bytecode, signer); + const wormhole = await wormholeFactory.deploy(); + await wormhole.deployTransaction.wait(); + + let tx = await bridge.setWormholeAddress(wormhole.address); + await tx.wait(); + tx = await bridge.setChainIdMapping([100, 265669100, 265669101], [200, 200, 202]); + await tx.wait(); + + return [wormhole, bridge]; +} + if (process.argv.includes("start-bridging-environment")) { setupBridging("http://127.0.0.1:8545", "http://127.0.0.1:8546"); } -module.exports = setupBridging; +module.exports = { setupBridging, deployBridge }; diff --git a/scripts/start-blockchain-client.sh b/scripts/start-blockchain-client.sh index 162b52511d..15ac35bb02 100644 --- a/scripts/start-blockchain-client.sh +++ b/scripts/start-blockchain-client.sh @@ -3,7 +3,7 @@ # Exit script as soon as a command fails. set -o errexit -CHAIN_ID=${CHAIN_ID:-2656691} +CHAIN_ID=${CHAIN_ID:-265669100} PORT=${PORT:-8545} DBPATH=${DBPATH:-./ganache-chain-db/} diff --git a/slither.config.json b/slither.config.json index 0ace032831..6d60f9bce3 100644 --- a/slither.config.json +++ b/slither.config.json @@ -1,5 +1,5 @@ { - "detectors_to_exclude": "unused-state,naming-convention,solc-version,external-function,pragma,constable-states,missing-inheritance,unintialized-state,assembly,boolean-equal,costly-loop,low-level-calls,missing-inheritance,similar-names,too-many-digits,uninitialized-state-variables,uninitialized-local-variables", + "detectors_to_exclude": "unused-state,naming-convention,solc-version,external-function,pragma,constable-states,missing-inheritance,unintialized-state,assembly,boolean-equal,costly-loop,low-level-calls,missing-inheritance,similar-names,too-many-digits,uninitialized-state-variables,uninitialized-local-variables,incorrect-equality", "solc_args": "--allow-paths .", "solc_disable_warnings": true } diff --git a/test-chainid/chainid-dependent-behaviour.js b/test-chainid/chainid-dependent-behaviour.js index 53aee43526..aac1b53085 100644 --- a/test-chainid/chainid-dependent-behaviour.js +++ b/test-chainid/chainid-dependent-behaviour.js @@ -2,6 +2,7 @@ const chai = require("chai"); const bnChai = require("bn-chai"); const BN = require("bn.js"); +const ethers = require("ethers"); const { setupENSRegistrar } = require("../helpers/upgradable-contracts"); const { @@ -15,35 +16,26 @@ const { forwardTime, getActiveRepCycle, advanceMiningCycleNoContest, - getValidEntryNumber, checkErrorRevert, expectEvent, expectNoEvent, getTokenArgs, + isMainnet, + isXdai, + getChainId, } = require("../helpers/test-helper"); -const { - MINING_CYCLE_DURATION, - DEFAULT_STAKE, - MIN_STAKE, - CHALLENGE_RESPONSE_WINDOW_DURATION, - WAD, - ALL_ENTRIES_ALLOWED_END_OF_WINDOW, -} = require("../helpers/constants"); +const { MINING_CYCLE_DURATION, MIN_STAKE, CHALLENGE_RESPONSE_WINDOW_DURATION, WAD, DEFAULT_STAKE, XDAI_CHAINID } = require("../helpers/constants"); const { expect } = chai; const ENSRegistry = artifacts.require("ENSRegistry"); -const MultiChain = artifacts.require("MultiChain"); const DutchAuction = artifacts.require("DutchAuction"); +const ITokenLocking = artifacts.require("ITokenLocking"); const Token = artifacts.require("Token"); chai.use(bnChai(web3.utils.BN)); -const MAINNET = 1; -const FORKED_MAINNET = 2656691; const GOERLI = 5; const FORKED_GOERLI = 2656695; -const XDAI = 100; -const FORKED_XDAI = 265669100; contract("Contract Storage", (accounts) => { const MINER1 = accounts[5]; @@ -56,10 +48,7 @@ contract("Contract Storage", (accounts) => { let chainId; before(async () => { - const multiChain = await MultiChain.new(); - chainId = await multiChain.getChainId(); - chainId = chainId.toNumber(); - + chainId = await getChainId(); console.log(`chainId: ${chainId}`); // eslint-disable-line no-console }); @@ -68,24 +57,27 @@ contract("Contract Storage", (accounts) => { ({ metaColony, clnyToken } = await setupMetaColonyWithLockedCLNYToken(colonyNetwork)); const ensRegistry = await ENSRegistry.new(); await setupENSRegistrar(colonyNetwork, ensRegistry, accounts[0]); - await giveUserCLNYTokensAndStake(colonyNetwork, MINER1, DEFAULT_STAKE); - await giveUserCLNYTokensAndStake(colonyNetwork, MINER2, DEFAULT_STAKE); - await giveUserCLNYTokensAndStake(colonyNetwork, MINER3, DEFAULT_STAKE); - - await colonyNetwork.initialiseReputationMining(); - await colonyNetwork.startNextCycle(); + if (await isXdai()) { + await giveUserCLNYTokensAndStake(colonyNetwork, MINER1, DEFAULT_STAKE); + await giveUserCLNYTokensAndStake(colonyNetwork, MINER2, DEFAULT_STAKE); + await giveUserCLNYTokensAndStake(colonyNetwork, MINER3, DEFAULT_STAKE); + + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); + } else { + await metaColony.initialiseReputationMining(XDAI_CHAINID, ethers.constants.HashZero, 0); + } }); describe("Should behave differently based on the network deployed to", () => { it("should be able to get the domain name", async () => { await metaColony.registerColonyLabel("meta", "", { from: accounts[0] }); - if (chainId === MAINNET || chainId === FORKED_MAINNET) { + if (await isMainnet()) { const name = await colonyNetwork.lookupRegisteredENSDomain(metaColony.address); expect(name).to.equal("meta.colony.joincolony.eth"); } else if (chainId === GOERLI || chainId === FORKED_GOERLI) { const name = await colonyNetwork.lookupRegisteredENSDomain(metaColony.address); expect(name).to.equal("meta.colony.joincolony.test"); - } else if (chainId === XDAI || chainId === FORKED_XDAI) { + } else if (await isXdai()) { const name = await colonyNetwork.lookupRegisteredENSDomain(metaColony.address); expect(name).to.equal("meta.colony.joincolony.colonyxdai"); } else { @@ -93,67 +85,26 @@ contract("Contract Storage", (accounts) => { } }); - it("Reputation mining rewards should come from different places depending on network", async () => { - await clnyToken.mint(colonyNetwork.address, 100, { from: accounts[11] }); - // Advance two cycles to clear active and inactive state. - await advanceMiningCycleNoContest({ colonyNetwork, test: this }); - await advanceMiningCycleNoContest({ colonyNetwork, test: this }); - - await metaColony.setReputationMiningCycleReward(100); - await forwardTime(MINING_CYCLE_DURATION / 2, this); - - const MINER1HASH = "0x01"; - const MINER2HASH = "0x02"; - const MINER3HASH = "0x03"; - - const entryNumber1 = await getValidEntryNumber(colonyNetwork, MINER1, MINER1HASH); - const entryNumber2 = await getValidEntryNumber(colonyNetwork, MINER2, MINER2HASH); - const entryNumber3 = await getValidEntryNumber(colonyNetwork, MINER3, MINER3HASH); - const repCycle = await getActiveRepCycle(colonyNetwork); - - await repCycle.submitRootHash(MINER1HASH, 10, "0x00", entryNumber1, { from: MINER1 }); - await repCycle.submitRootHash(MINER2HASH, 10, "0x00", entryNumber2, { from: MINER2 }); - await repCycle.submitRootHash(MINER3HASH, 10, "0x00", entryNumber3, { from: MINER3 }); - - const nUniqueSubmittedHashes = await repCycle.getNUniqueSubmittedHashes(); - expect(nUniqueSubmittedHashes).to.eq.BN(3); - - const rewardSize = await repCycle.getDisputeRewardSize(); + it("can only stake tokens for mining (and therefore can only mine) on the mining chain", async () => { + await giveUserCLNYTokens(colonyNetwork, MINER1, DEFAULT_STAKE); + const tokenLockingAddress = await colonyNetwork.getTokenLocking(); + const tokenLocking = await ITokenLocking.at(tokenLockingAddress); + await clnyToken.approve(tokenLocking.address, DEFAULT_STAKE, { from: MINER1 }); + await tokenLocking.methods["deposit(address,uint256,bool)"](clnyToken.address, DEFAULT_STAKE, true, { from: MINER1 }); + const tx = colonyNetwork.stakeForMining(DEFAULT_STAKE, { from: MINER1 }); - await forwardTime(MINING_CYCLE_DURATION / 2 + CHALLENGE_RESPONSE_WINDOW_DURATION * 2 + 1, this); - - await repCycle.invalidateHash(0, 0, { from: MINER1 }); - await repCycle.invalidateHash(0, 3, { from: MINER1 }); - - await forwardTime(CHALLENGE_RESPONSE_WINDOW_DURATION - ALL_ENTRIES_ALLOWED_END_OF_WINDOW + 1, this); - const networkBalanceBefore = await clnyToken.balanceOf(colonyNetwork.address); - const tx = await repCycle.confirmNewHash(1, { from: MINER1 }); - - if (chainId === XDAI || chainId === FORKED_XDAI) { - // tokens should be paid from the network balance - const networkBalanceAfter = await clnyToken.balanceOf(colonyNetwork.address); - expect(networkBalanceBefore.sub(networkBalanceAfter)).to.eq.BN(100); + if (await isXdai()) { + await tx; } else { - // tokens should be newly minted, so balance of network doesn't change. - const networkBalanceAfter = await clnyToken.balanceOf(colonyNetwork.address); - expect(networkBalanceBefore).to.eq.BN(networkBalanceAfter); - } - - // Two people are getting MIN_STAKE slashed, from which two rewards are paid out. - // We expect the remaineder to be burned - const expectedBurned = MIN_STAKE.muln(2).sub(rewardSize.muln(2)); - // Unneeded rewards should be dealt with differently as well. - - if (chainId === XDAI || chainId === FORKED_XDAI) { - // tokens should be transferred to metacolony - await expectEvent(tx, "Transfer(address indexed,address indexed,uint256)", [colonyNetwork.address, metaColony.address, expectedBurned]); - } else { - // tokens should be burned. - await expectEvent(tx, "Burn(address indexed,uint256)", [colonyNetwork.address, expectedBurned]); + await checkErrorRevert(tx, "colony-only-valid-on-mining-chain-or-during-setup"); } }); - it("should not make 0-value transfers to 'burn' unneeded rewards on xdai", async () => { + it("should not make 0-value transfers to 'burn' unneeded mining rewards on xdai", async function () { + if (!(await isXdai())) { + // We don't mine anywhere else, so skip + this.skip(); + } await giveUserCLNYTokensAndStake(colonyNetwork, MINER1, MIN_STAKE); await advanceMiningCycleNoContest({ colonyNetwork, test: this }); @@ -178,16 +129,16 @@ contract("Contract Storage", (accounts) => { const supplyAfter = await clnyToken.totalSupply(); const balanceAfter = await clnyToken.balanceOf(colonyNetwork.address); - if (chainId === XDAI || chainId === FORKED_XDAI) { - // tokens should be transferred to metacolony - expect(supplyBefore).to.eq.BN(supplyAfter); - await expectEvent(tx, "Transfer(address indexed,address indexed,uint256)", [colonyNetwork.address, metaColony.address, WAD]); - } else { + if (await isMainnet()) { // tokens should be burned. expect(supplyBefore.sub(supplyAfter)).to.eq.BN(balanceBefore); await expectEvent(tx, "Burn(address indexed,uint256)", [colonyNetwork.address, WAD]); expect(balanceAfter).to.be.zero; expect(supplyBefore.sub(balanceBefore)).to.eq.BN(supplyAfter); + } else { + // tokens should be transferred to metacolony + expect(supplyBefore).to.eq.BN(supplyAfter); + await expectEvent(tx, "Transfer(address indexed,address indexed,uint256)", [colonyNetwork.address, metaColony.address, WAD]); } }); @@ -217,15 +168,34 @@ contract("Contract Storage", (accounts) => { const balanceAfter = await clnyToken.balanceOf(tokenAuction.address); expect(balanceAfter).to.be.zero; const supplyAfter = await clnyToken.totalSupply(); - if (chainId === XDAI || chainId === FORKED_XDAI) { - // tokens should be transferred to metacolony - expect(supplyBefore).to.eq.BN(supplyAfter); - await expectEvent(tx, "Transfer(address indexed,address indexed,uint256)", [tokenAuction.address, metaColony.address, receivedTotal]); - } else { + if (await isMainnet()) { // tokens should be burned. expect(supplyBefore.sub(supplyAfter)).to.eq.BN(balanceBefore); await expectEvent(tx, "Burn(address indexed,uint256)", [tokenAuction.address, receivedTotal]); + } else { + // tokens should be transferred to metacolony + expect(supplyBefore).to.eq.BN(supplyAfter); + await expectEvent(tx, "Transfer(address indexed,address indexed,uint256)", [tokenAuction.address, metaColony.address, receivedTotal]); + } + }); + + it("reputation mining chain can be changed on non-mining chain", async function () { + if (await isXdai()) { + // Not appropriate test on xdai + this.skip(); } + + const oldChainId = await colonyNetwork.getMiningChainId(); + + const otherChainId = oldChainId + 1; + await metaColony.initialiseReputationMining(otherChainId, ethers.constants.HashZero, 0); + + const newChainId = await colonyNetwork.getMiningChainId(); + expect(newChainId).to.eq.BN(oldChainId + 1); + }); + + it.skip("reputation mining chain can be changed on mining chain", async () => { + // Not a test, or contract code, that has been written yet }); }); }); diff --git a/test-smoke/colony-storage-consistent.js b/test-smoke/colony-storage-consistent.js index ce30a4717c..d6ec6f847b 100644 --- a/test-smoke/colony-storage-consistent.js +++ b/test-smoke/colony-storage-consistent.js @@ -156,11 +156,11 @@ contract("Contract Storage", (accounts) => { console.log("miningCycleStateHash:", miningCycleStateHash); console.log("tokenLockingStateHash:", tokenLockingStateHash); - expect(colonyNetworkStateHash).to.equal("0x7b43f3e7e6cda0d4828db085a635f3bfa5513595d3048b835eac558070b8980f"); - expect(colonyStateHash).to.equal("0x3fd9f27a6b7e09500e5ec9314027a47477d03d01b4a2f5c305cd98c74205c647"); - expect(metaColonyStateHash).to.equal("0x87a14b838f1db5f0bd5a883cfad2f1ef124cc822ea4c9a124531b54676843864"); - expect(miningCycleStateHash).to.equal("0xd59299ca385c8d9795a56de6dcaea40048712832669421091e132db492ee84bc"); - expect(tokenLockingStateHash).to.equal("0x871a5dedede31530886db450e3aaec934d643989910a7c225ded0127cecd65e9"); + expect(colonyNetworkStateHash).to.equal("0x66b279547b6eab5c688ac6b6a6bd7dedc0849a9e9d2218e5a1dcf0eeac8847f8"); + expect(colonyStateHash).to.equal("0x156061470e5ed4c0f091adb726dbb819692d23bc8338bd06a660a9a4cc48eea6"); + expect(metaColonyStateHash).to.equal("0x4c2b0dba6abe7feee2c052e3a5f07ccb71adb5d0bd5c32d21b16559b313ecf82"); + expect(miningCycleStateHash).to.equal("0x179caec9074f4db8b06afcb6dad20c8091b31d7b483bd1c6cb469d79d1bc3649"); + expect(tokenLockingStateHash).to.equal("0xd128da36044b6c399c522f379a78591a572394423814b8aeb511cf2a3a07701f"); }); }); }); diff --git a/test-system/end-to-end.js b/test-system/end-to-end.js index 1bc5dd8003..6f81480416 100644 --- a/test-system/end-to-end.js +++ b/test-system/end-to-end.js @@ -4,6 +4,7 @@ const path = require("path"); const BN = require("bn.js"); const chai = require("chai"); const bnChai = require("bn-chai"); +const ethers = require("ethers"); const { TruffleLoader } = require("../packages/package-utils"); const { @@ -14,6 +15,7 @@ const { makeReputationKey, makeReputationValue, removeSubdomainLimit, + getChainId, } = require("../helpers/test-helper"); const { @@ -67,8 +69,8 @@ contract("End to end Colony network and Reputation mining testing", function (ac await removeSubdomainLimit(colonyNetwork); await giveUserCLNYTokensAndStake(colonyNetwork, MINER1, DEFAULT_STAKE); - await colonyNetwork.initialiseReputationMining(); - await colonyNetwork.startNextCycle(); + const chainId = await getChainId(); + await colonyNetwork.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); goodClient = new ReputationMinerTestWrapper({ loader, realProviderPort, useJsTree, minerAddress: MINER1 }); await goodClient.resetDB(); diff --git a/test/contracts-network/colony-expenditure.js b/test/contracts-network/colony-expenditure.js index 4caadcea8f..25c1cf26d3 100644 --- a/test/contracts-network/colony-expenditure.js +++ b/test/contracts-network/colony-expenditure.js @@ -5,8 +5,18 @@ const { BN } = require("bn.js"); const { ethers } = require("ethers"); const { soliditySha3 } = require("web3-utils"); -const { UINT256_MAX, INT128_MAX, WAD, SECONDS_PER_DAY, MAX_PAYOUT, IPFS_HASH, ADDRESS_ZERO, HASHZERO } = require("../../helpers/constants"); -const { checkErrorRevert, expectEvent, getTokenArgs, forwardTime, getBlockTime, bn2bytes32 } = require("../../helpers/test-helper"); +const { + UINT256_MAX, + INT128_MAX, + WAD, + SECONDS_PER_DAY, + MAX_PAYOUT, + IPFS_HASH, + ADDRESS_ZERO, + HASHZERO, + CURR_VERSION, +} = require("../../helpers/constants"); +const { checkErrorRevert, expectEvent, getTokenArgs, forwardTime, getBlockTime, bn2bytes32, upgradeColonyTo } = require("../../helpers/test-helper"); const { fundColonyWithTokens, setupRandomColony } = require("../../helpers/test-data-generator"); const { setupEtherRouter } = require("../../helpers/upgradable-contracts"); const { @@ -338,8 +348,7 @@ contract("Colony Expenditure", (accounts) => { // Upgrade to current version await colonyNetworkAsEtherRouter.setResolver(latestResolver); - await metaColony.upgrade(14); - await metaColony.upgrade(15); + await upgradeColonyTo(metaColony, CURR_VERSION); await checkErrorRevert(colony.setExpenditureSkill(expenditureId, SLOT0, globalSkillId, { from: ADMIN }), "colony-not-valid-local-skill"); await checkErrorRevert(colony.setExpenditureSkill(expenditureId, SLOT0, globalSkillId2, { from: ADMIN }), "colony-not-valid-local-skill"); diff --git a/test/contracts-network/colony-network-auction.js b/test/contracts-network/colony-network-auction.js index 9fe4af3a3a..16ef122306 100644 --- a/test/contracts-network/colony-network-auction.js +++ b/test/contracts-network/colony-network-auction.js @@ -12,6 +12,8 @@ const { forwardTime, getBlockTime, getColonyEditable, + isMainnet, + getChainId, } = require("../../helpers/test-helper"); const { WAD, SECONDS_PER_DAY } = require("../../helpers/constants"); @@ -53,8 +55,8 @@ contract("Colony Network Auction", (accounts) => { ({ metaColony, clnyToken } = await setupMetaColonyWithLockedCLNYToken(colonyNetwork)); await unlockCLNYToken(metaColony); - await colonyNetwork.initialiseReputationMining(); - await colonyNetwork.startNextCycle(); + const chainId = await getChainId(); + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); const args = getTokenArgs(); token = await Token.new(...args); @@ -100,7 +102,12 @@ contract("Colony Network Auction", (accounts) => { const supplyAfter = await clnyToken.totalSupply(); const balanceAfter = await clnyToken.balanceOf(colonyNetwork.address); expect(balanceAfter).to.be.zero; - expect(supplyBefore.sub(balanceBefore)).to.eq.BN(supplyAfter); + if (await isMainnet()) { + expect(supplyBefore.sub(balanceBefore)).to.eq.BN(supplyAfter); + } else { + const metaColonyBalanceAfter = await clnyToken.balanceOf(metaColony.address); + expect(metaColonyBalanceAfter).to.eq.BN(balanceBefore); + } }); it("should fail with zero quantity", async () => { @@ -825,7 +832,7 @@ contract("Colony Network Auction", (accounts) => { expect(finalized).to.be.true; }); - it("all CLNY sent to the auction in bids is burned", async () => { + it("all CLNY sent to the auction in bids is burned in a chain-appropriate way", async () => { const balanceBefore = await clnyToken.balanceOf(tokenAuction.address); const supplyBefore = await clnyToken.totalSupply(); const receivedTotal = await tokenAuction.receivedTotal(); @@ -834,8 +841,13 @@ contract("Colony Network Auction", (accounts) => { const balanceAfter = await clnyToken.balanceOf(tokenAuction.address); expect(balanceAfter).to.be.zero; - const supplyAfter = await clnyToken.totalSupply(); - expect(supplyBefore.sub(supplyAfter)).to.eq.BN(balanceBefore); + if (await isMainnet()) { + const supplyAfter = await clnyToken.totalSupply(); + expect(supplyBefore.sub(supplyAfter)).to.eq.BN(balanceBefore); + } else { + const metaColonyBalanceAfter = await clnyToken.balanceOf(metaColony.address); + expect(metaColonyBalanceAfter).to.eq.BN(balanceBefore); + } }); it("cannot bid after finalized", async () => { diff --git a/test/contracts-network/colony-network-recovery.js b/test/contracts-network/colony-network-recovery.js index 3fe471baaf..e23d813fc5 100644 --- a/test/contracts-network/colony-network-recovery.js +++ b/test/contracts-network/colony-network-recovery.js @@ -136,6 +136,7 @@ contract("Colony Network Recovery", (accounts) => { it("should not be able to call normal functions while in recovery", async () => { await colonyNetwork.enterRecoveryMode(); await checkErrorRevert(colonyNetwork.createColony(clny.address), "colony-in-recovery-mode"); + await checkErrorRevert(colonyNetwork.createColonyForFrontend(ADDRESS_ZERO, "", "", 18, 0, "", ""), "colony-in-recovery-mode"); await checkErrorRevert(colonyNetwork.createMetaColony(clny.address), "colony-in-recovery-mode"); await checkErrorRevert(colonyNetwork.setTokenLocking(ADDRESS_ZERO), "colony-in-recovery-mode"); await checkErrorRevert(colonyNetwork.initialiseRootLocalSkill(), "colony-in-recovery-mode"); @@ -175,7 +176,7 @@ contract("Colony Network Recovery", (accounts) => { await checkErrorRevert(colonyNetwork.deployTokenAuthority(ADDRESS_ZERO, ADDRESS_ZERO, []), "colony-in-recovery-mode"); await checkErrorRevert(colonyNetwork.setMiningDelegate(ADDRESS_ZERO, true), "colony-in-recovery-mode"); await checkErrorRevert(colonyNetwork.setReputationRootHash(HASHZERO, 0, []), "colony-in-recovery-mode"); - await checkErrorRevert(colonyNetwork.initialiseReputationMining(), "colony-in-recovery-mode"); + await checkErrorRevert(colonyNetwork.initialiseReputationMining(1, HASHZERO, 0), "colony-in-recovery-mode"); await checkErrorRevert(colonyNetwork.startNextCycle(), "colony-in-recovery-mode"); await checkErrorRevert(colonyNetwork.punishStakers([], 0), "colony-in-recovery-mode"); await checkErrorRevert(colonyNetwork.reward(ADDRESS_ZERO, 0), "colony-in-recovery-mode"); @@ -192,6 +193,12 @@ contract("Colony Network Recovery", (accounts) => { await checkErrorRevert(colonyNetwork.setPayoutWhitelist(ADDRESS_ZERO, true), "colony-in-recovery-mode"); await checkErrorRevert(colonyNetwork.claimMiningReward(ADDRESS_ZERO), "colony-in-recovery-mode"); await checkErrorRevert(colonyNetwork.startTokenAuction(ADDRESS_ZERO), "colony-in-recovery-mode"); + await checkErrorRevert(colonyNetwork.bridgeSkillIfNotMiningChain(1), "colony-in-recovery-mode"); + await checkErrorRevert(colonyNetwork.bridgePendingReputationUpdate(ADDRESS_ZERO, 0), "colony-in-recovery-mode"); + await checkErrorRevert(colonyNetwork.bridgeCurrentRootHash(ADDRESS_ZERO), "colony-in-recovery-mode"); + await checkErrorRevert(colonyNetwork.addReputationUpdateLogFromBridge(ADDRESS_ZERO, ADDRESS_ZERO, 0, 0, 0), "colony-in-recovery-mode"); + await checkErrorRevert(colonyNetwork.addPendingReputationUpdate(0, ADDRESS_ZERO), "colony-in-recovery-mode"); + await checkErrorRevert(colonyNetwork.setReputationRootHashFromBridge(HASHZERO, 0, 0), "colony-in-recovery-mode"); await colonyNetwork.approveExitRecovery(); await colonyNetwork.exitRecoveryMode(); diff --git a/test/contracts-network/colony-network.js b/test/contracts-network/colony-network.js index 089c1fd803..f80d58db7c 100755 --- a/test/contracts-network/colony-network.js +++ b/test/contracts-network/colony-network.js @@ -22,6 +22,8 @@ const { expectEvent, expectNoEvent, getColonyEditable, + isXdai, + getChainId, } = require("../../helpers/test-helper"); const { CURR_VERSION, MIN_STAKE, IPFS_HASH, ADDRESS_ZERO, WAD } = require("../../helpers/constants"); @@ -39,6 +41,7 @@ const IColony = artifacts.require("IColony"); const Token = artifacts.require("Token"); const TokenLocking = artifacts.require("TokenLocking"); const MetaTxToken = artifacts.require("MetaTxToken"); +const IMetaColony = artifacts.require("IMetaColony"); const FunctionsNotAvailableOnColony = artifacts.require("FunctionsNotAvailableOnColony"); const TokenAuthority = artifacts.require("contracts/common/TokenAuthority.sol:TokenAuthority"); @@ -55,6 +58,7 @@ contract("Colony Network", (accounts) => { const OTHER_ACCOUNT = accounts[1]; let colonyNetwork; let metaColony; + let clnyToken; let createColonyGas; let version; @@ -66,7 +70,7 @@ contract("Colony Network", (accounts) => { beforeEach(async () => { colonyNetwork = await setupColonyNetwork(); version = await colonyNetwork.getCurrentColonyVersion(); - ({ metaColony } = await setupMetaColonyWithLockedCLNYToken(colonyNetwork)); + ({ metaColony, clnyToken } = await setupMetaColonyWithLockedCLNYToken(colonyNetwork)); // For upgrade tests, we need a resolver... const r = await Resolver.new(); newResolverAddress = r.address.toLowerCase(); @@ -145,12 +149,23 @@ contract("Colony Network", (accounts) => { const currentColonyVersion = await colonyNetworkNew.getCurrentColonyVersion(); expect(currentColonyVersion).to.eq.BN(79); }); + + it("does not allow other colonies to add a new colony version", async () => { + const colony = await setupColony(colonyNetwork, clnyToken.address); + const fakeMetaColony = await IMetaColony.at(colony.address); + + await checkErrorRevert(fakeMetaColony.addNetworkColonyVersion(1, ADDRESS_ZERO), "colony-caller-must-be-meta-colony"); + }); }); describe("when managing the mining process", () => { - it("should not allow reinitialisation of reputation mining process", async () => { - await colonyNetwork.initialiseReputationMining(); - await checkErrorRevert(colonyNetwork.initialiseReputationMining(), "colony-reputation-mining-already-initialised"); + it("should not allow reinitialisation of reputation mining process (if we're on the mining chain)", async () => { + const chainId = await getChainId(); + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); + await checkErrorRevert( + metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0), + "colony-reputation-mining-already-initialised", + ); }); it("should not allow setting the mining resolver to null", async () => { @@ -161,10 +176,22 @@ contract("Colony Network", (accounts) => { await checkErrorRevert(colonyNetwork.setMiningResolver(ethers.constants.AddressZero, { from: accounts[1] }), "ds-auth-unauthorized"); }); - it("should not allow initialisation if the clny token is 0", async () => { + it("should not allow initialisation of mining on this chain if the clny token is 0", async () => { const metaColonyUnderRecovery = await getColonyEditable(metaColony, colonyNetwork); await metaColonyUnderRecovery.setStorageSlot(7, ethers.constants.AddressZero); - await checkErrorRevert(colonyNetwork.initialiseReputationMining(), "colony-reputation-mining-clny-token-invalid-address"); + const chainId = await getChainId(); + await checkErrorRevert( + metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0), + "colony-reputation-mining-clny-token-invalid-address", + ); + }); + + it("should allow initialisation of mining on another chain if the clny token is 0", async () => { + const metaColonyUnderRecovery = await getColonyEditable(metaColony, colonyNetwork); + await metaColonyUnderRecovery.setStorageSlot(7, ethers.constants.AddressZero); + let chainId = await getChainId(); + chainId += 1; + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); }); it("should not allow another mining cycle to start if the process isn't initialised", async () => { @@ -172,7 +199,8 @@ contract("Colony Network", (accounts) => { }); it("should not allow another mining cycle to start if the clny token is 0", async () => { - await colonyNetwork.initialiseReputationMining(); + const chainId = await getChainId(); + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); const metaColonyUnderRecovery = await getColonyEditable(metaColony, colonyNetwork); await metaColonyUnderRecovery.setStorageSlot(7, ethers.constants.AddressZero); @@ -180,6 +208,9 @@ contract("Colony Network", (accounts) => { }); it('should not allow "punishStakers" to be called from an account that is not the mining cycle', async () => { + const chainId = await getChainId(); + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); + await checkErrorRevert( colonyNetwork.punishStakers([accounts[0], accounts[1]], MIN_STAKE), "colony-reputation-mining-sender-not-active-reputation-cycle", @@ -315,7 +346,11 @@ contract("Colony Network", (accounts) => { expect(colonyCount).to.eq.BN(8); }); - it("when meta colony is created, should have the root domain and local skills initialised, plus the local mining skill", async () => { + it(`when meta colony is created, after initialising mining, + should have the root domain and local skills initialised, plus the local mining skill`, async () => { + const chainId = await getChainId(); + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); + const skillCount = await colonyNetwork.getSkillCount(); expect(skillCount).to.eq.BN(3); @@ -499,15 +534,24 @@ contract("Colony Network", (accounts) => { it("should NOT be able to add a local skill, by an address that is not a Colony", async () => { await checkErrorRevert(colonyNetwork.addSkill(1), "colony-caller-must-be-colony"); }); + + it("should not be able to initialise root local skill for an address that's not a colony", async () => { + await checkErrorRevert(colonyNetwork.initialiseRootLocalSkill(), "colony-caller-must-be-colony"); + }); }); describe("when managing ENS names", () => { const orbitDBAddress = "QmPFtHi3cmfZerxtH9ySLdzpg1yFhocYDZgEZywdUXHxFU/my-db-name"; let ensRegistry; + let suffix; + + before(async () => { + suffix = (await isXdai()) ? "colonyxdai" : "eth"; + }); beforeEach(async () => { ensRegistry = await ENSRegistry.new(); - await setupENSRegistrar(colonyNetwork, ensRegistry, accounts[0]); + await setupENSRegistrar(colonyNetwork, ensRegistry, accounts[0], suffix); }); it("should not be able to set the ENS reigstrar to null", async () => { @@ -525,27 +569,28 @@ contract("Colony Network", (accounts) => { const { colonyAddress } = logs.filter((x) => x.event === "ColonyAdded")[0].args; const name = await colonyNetwork.lookupRegisteredENSDomain(colonyAddress); - expect(name).to.equal("test.colony.joincolony.eth"); + expect(name).to.equal(`test.colony.joincolony.${suffix}`); }); it("should own the root domains", async () => { - const rootNode = namehash.hash("joincolony.eth"); + const rootNode = namehash.hash(`joincolony.${suffix}`); let owner; owner = await ensRegistry.owner(rootNode); expect(owner).to.equal(accounts[0]); - owner = await ensRegistry.owner(namehash.hash("user.joincolony.eth")); + owner = await ensRegistry.owner(namehash.hash(`user.joincolony.${suffix}`)); expect(owner).to.equal(colonyNetwork.address); - owner = await ensRegistry.owner(namehash.hash("colony.joincolony.eth")); + owner = await ensRegistry.owner(namehash.hash(`colony.joincolony.${suffix}`)); expect(owner).to.equal(colonyNetwork.address); }); it("should be able to register one unique label per user", async () => { const username = "test"; const username2 = "test2"; - const hash = namehash.hash("test.user.joincolony.eth"); + + const hash = namehash.hash(`test.user.joincolony.${suffix}`); // User cannot register blank label await checkErrorRevert(colonyNetwork.registerUserLabel("", orbitDBAddress, { from: accounts[1] }), "colony-user-label-invalid"); @@ -565,7 +610,7 @@ contract("Colony Network", (accounts) => { // Check reverse lookup const lookedUpENSDomain = await colonyNetwork.lookupRegisteredENSDomain(accounts[1]); - expect(lookedUpENSDomain).to.equal("test.user.joincolony.eth"); + expect(lookedUpENSDomain).to.equal(`test.user.joincolony.${suffix}`); // Get stored orbitdb address const retrievedOrbitDB = await colonyNetwork.getProfileDBAddress(hash); @@ -591,13 +636,13 @@ contract("Colony Network", (accounts) => { await checkErrorRevert(fakeColony.registerUserLabel("test", orbitDBAddress), "colony-caller-must-not-be-colony"); const lookedUpENSDomain = await colonyNetwork.lookupRegisteredENSDomain(colony.address); - expect(lookedUpENSDomain).to.not.equal("test.user.joincolony.eth"); + expect(lookedUpENSDomain).to.not.equal(`test.user.joincolony.${suffix}`); }); it("should be able to register one unique label per colony, with root permission", async () => { const colonyName = "test"; const colonyName2 = "test2"; - const hash = namehash.hash("test.colony.joincolony.eth"); + const hash = namehash.hash(`test.colony.joincolony.${suffix}`); const { colony } = await setupRandomColony(colonyNetwork); @@ -622,7 +667,7 @@ contract("Colony Network", (accounts) => { // Check reverse lookup const lookedUpENSDomain = await colonyNetwork.lookupRegisteredENSDomain(colony.address); - expect(lookedUpENSDomain).to.equal("test.colony.joincolony.eth"); + expect(lookedUpENSDomain).to.equal(`test.colony.joincolony.${suffix}`); // Get stored orbitdb address const retrievedOrbitDB = await colonyNetwork.getProfileDBAddress(hash); expect(retrievedOrbitDB).to.equal(orbitDBAddress); @@ -644,11 +689,11 @@ contract("Colony Network", (accounts) => { // Check reverse lookup for colony const lookedUpENSDomainColony = await colonyNetwork.lookupRegisteredENSDomain(colony.address); - expect(lookedUpENSDomainColony).to.equal("test.colony.joincolony.eth"); + expect(lookedUpENSDomainColony).to.equal(`test.colony.joincolony.${suffix}`); // Check reverse lookup const lookedUpENSDomainUser = await colonyNetwork.lookupRegisteredENSDomain(accounts[1]); - expect(lookedUpENSDomainUser).to.equal("test.user.joincolony.eth"); + expect(lookedUpENSDomainUser).to.equal(`test.user.joincolony.${suffix}`); }); it("should return a blank address if looking up an address with no Colony-based ENS name", async () => { @@ -665,7 +710,7 @@ contract("Colony Network", (accounts) => { it("owner should be able to set and get the ttl of their node", async () => { ensRegistry = await ENSRegistry.new(); - const hash = namehash.hash("jane.user.joincolony.eth"); + const hash = namehash.hash(`jane.user.joincolony.${suffix}`); await ensRegistry.setTTL(hash, 123); const ttl = await ensRegistry.ttl(hash); @@ -673,20 +718,20 @@ contract("Colony Network", (accounts) => { }); it("use should NOT be able to set and get the ttl of a node they don't own", async () => { - const hash = namehash.hash("jane.user.joincolony.eth"); + const hash = namehash.hash(`jane.user.joincolony.${suffix}`); await colonyNetwork.registerUserLabel("jane", orbitDBAddress); await checkErrorRevert(ensRegistry.setTTL(hash, 123), "colony-ens-non-owner-access"); }); it("setting owner on a subnode should fail for a non existent subnode", async () => { ensRegistry = await ENSRegistry.new(); - const hash = namehash.hash("jane.user.joincolony.eth"); + const hash = namehash.hash(`jane.user.joincolony.${suffix}`); await checkErrorRevert(ensRegistry.setSubnodeOwner(hash, hash, accounts[0]), "unowned-node"); }); it("should allow a user to update their orbitDBAddress", async () => { - const hash = namehash.hash("test.user.joincolony.eth"); + const hash = namehash.hash(`test.user.joincolony.${suffix}`); await colonyNetwork.registerUserLabel("test", orbitDBAddress, { from: accounts[1] }); await colonyNetwork.updateUserOrbitDB("anotherstring", { from: accounts[1] }); const retrievedOrbitDB = await colonyNetwork.getProfileDBAddress(hash); @@ -699,7 +744,7 @@ contract("Colony Network", (accounts) => { it("should allow a colony to change its orbitDBAddress with root permissions", async () => { const colonyName = "test"; - const hash = namehash.hash("test.colony.joincolony.eth"); + const hash = namehash.hash(`test.colony.joincolony.${suffix}`); const { colony } = await setupRandomColony(colonyNetwork); await colony.registerColonyLabel(colonyName, orbitDBAddress, { from: accounts[0] }); await colony.updateColonyOrbitDB("anotherstring", { from: accounts[0] }); diff --git a/test/contracts-network/colony-recovery.js b/test/contracts-network/colony-recovery.js index 58c58b3f45..b339f3e4cb 100644 --- a/test/contracts-network/colony-recovery.js +++ b/test/contracts-network/colony-recovery.js @@ -146,7 +146,6 @@ contract("Colony Recovery", (accounts) => { await checkErrorRevert(metaColony.editColonyByDelta(""), "colony-in-recovery-mode"); await checkErrorRevert(metaColony.bootstrapColony([], []), "colony-in-recovery-mode"); await checkErrorRevert(metaColony.mintTokensFor(ADDRESS_ZERO, 0), "colony-in-recovery-mode"); - await checkErrorRevert(metaColony.mintTokensForColonyNetwork(0), "colony-in-recovery-mode"); await checkErrorRevert(metaColony.setNetworkFeeInverse(0), "colony-in-recovery-mode"); await checkErrorRevert(metaColony.setPayoutWhitelist(ADDRESS_ZERO, true), "colony-in-recovery-mode"); await checkErrorRevert(metaColony.setReputationMiningCycleReward(0), "colony-in-recovery-mode"); diff --git a/test/contracts-network/colony.js b/test/contracts-network/colony.js index 063e7510d6..6591aa76f9 100755 --- a/test/contracts-network/colony.js +++ b/test/contracts-network/colony.js @@ -4,8 +4,16 @@ const chai = require("chai"); const bnChai = require("bn-chai"); const { ethers } = require("ethers"); -const { IPFS_HASH, UINT256_MAX, WAD, ADDRESS_ZERO, SPECIFICATION_HASH, HASHZERO } = require("../../helpers/constants"); -const { getTokenArgs, web3GetBalance, checkErrorRevert, expectNoEvent, expectAllEvents, expectEvent } = require("../../helpers/test-helper"); +const { IPFS_HASH, UINT256_MAX, WAD, ADDRESS_ZERO, SPECIFICATION_HASH, HASHZERO, CURR_VERSION } = require("../../helpers/constants"); +const { + getTokenArgs, + web3GetBalance, + checkErrorRevert, + expectNoEvent, + expectAllEvents, + expectEvent, + upgradeColonyTo, +} = require("../../helpers/test-helper"); const { setupRandomColony, getMetaTransactionParameters, @@ -466,8 +474,7 @@ contract("Colony", (accounts) => { it("should be able to query for a task", async () => { await oldColony.makeTask(1, UINT256_MAX, SPECIFICATION_HASH, 1, localSkillId, 0, { from: USER0 }); - await colony.upgrade(14); - await colony.upgrade(15); + await upgradeColonyTo(oldColony, CURR_VERSION); const taskId = await colony.getTaskCount(); const task = await colony.getTask(taskId); @@ -488,8 +495,7 @@ contract("Colony", (accounts) => { it("should be able to query for a payment", async () => { await oldColony.addPayment(1, UINT256_MAX, USER1, token.address, WAD, 1, localSkillId, { from: USER0 }); - await colony.upgrade(14); - await colony.upgrade(15); + await upgradeColonyTo(oldColony, CURR_VERSION); const paymentId = await colony.getPaymentCount(); const payment = await colony.getPayment(paymentId); @@ -509,8 +515,7 @@ contract("Colony", (accounts) => { // Move funds into task funding pot await colony.moveFundsBetweenPots(1, UINT256_MAX, 1, UINT256_MAX, UINT256_MAX, 1, fundingPotId, WAD, token.address); - await colony.upgrade(14); - await colony.upgrade(15); + await upgradeColonyTo(oldColony, CURR_VERSION); // Move funds back await colony.moveFundsBetweenPots(1, UINT256_MAX, 1, UINT256_MAX, UINT256_MAX, fundingPotId, 1, WAD, token.address); }); @@ -526,8 +531,7 @@ contract("Colony", (accounts) => { // Move funds into task funding pot await colony.moveFundsBetweenPots(1, UINT256_MAX, 1, UINT256_MAX, UINT256_MAX, 1, fundingPotId, WAD, token.address); - await colony.upgrade(14); - await colony.upgrade(15); + await upgradeColonyTo(oldColony, CURR_VERSION); // Move funds back await colony.moveFundsBetweenPots(1, UINT256_MAX, 1, UINT256_MAX, UINT256_MAX, fundingPotId, 1, WAD, token.address); }); diff --git a/test/contracts-network/meta-colony.js b/test/contracts-network/meta-colony.js index bdebf6d869..46e57379c5 100644 --- a/test/contracts-network/meta-colony.js +++ b/test/contracts-network/meta-colony.js @@ -2,10 +2,18 @@ const chai = require("chai"); const bnChai = require("bn-chai"); +const ethers = require("ethers"); const { soliditySha3 } = require("web3-utils"); -const { UINT256_MAX, WAD, ADDRESS_ZERO, HASHZERO } = require("../../helpers/constants"); -const { checkErrorRevert, removeSubdomainLimit, restoreSubdomainLimit, bn2bytes32 } = require("../../helpers/test-helper"); +const { UINT256_MAX, WAD, ADDRESS_ZERO, HASHZERO, CURR_VERSION } = require("../../helpers/constants"); +const { + checkErrorRevert, + removeSubdomainLimit, + restoreSubdomainLimit, + bn2bytes32, + upgradeColonyTo, + getChainId, +} = require("../../helpers/test-helper"); const { setupColonyNetwork, setupMetaColonyWithLockedCLNYToken, setupRandomColony } = require("../../helpers/test-data-generator"); const { downgradeColony, @@ -33,6 +41,9 @@ contract("Meta Colony", (accounts) => { colonyNetwork = await setupColonyNetwork(); ({ metaColony, clnyToken } = await setupMetaColonyWithLockedCLNYToken(colonyNetwork)); + const chainId = await getChainId(); + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); + await metaColony.addLocalSkill(); // Skills: @@ -43,9 +54,6 @@ contract("Meta Colony", (accounts) => { const skillCount = await colonyNetwork.getSkillCount(); expect(skillCount).to.eq.BN(4); - - await colonyNetwork.initialiseReputationMining(); - await colonyNetwork.startNextCycle(); }); describe("when working with ERC20 properties of Meta Colony token", () => { @@ -440,8 +448,7 @@ contract("Meta Colony", (accounts) => { // Upgrade to current version await colonyNetworkAsEtherRouter.setResolver(latestResolver); - await metaColony.upgrade(14); - await metaColony.upgrade(15); + await upgradeColonyTo(metaColony, CURR_VERSION); }); describe("when getting a skill", () => { @@ -500,12 +507,6 @@ contract("Meta Colony", (accounts) => { }); }); - describe("when minting tokens for the Network", () => { - it("should NOT allow anyone but the Network to call mintTokensForColonyNetwork", async () => { - await checkErrorRevert(metaColony.mintTokensForColonyNetwork(100), "colony-access-denied-only-network-allowed"); - }); - }); - describe("when setting the per-cycle miner reward", () => { it("should allow the reward to be set", async () => { const rewardBefore = await colonyNetwork.getReputationMiningCycleReward(); diff --git a/test/cross-chain/cross-chain.js b/test/cross-chain/cross-chain.js index 9f763e329c..3b29dd8148 100644 --- a/test/cross-chain/cross-chain.js +++ b/test/cross-chain/cross-chain.js @@ -2,7 +2,8 @@ const chai = require("chai"); const bnChai = require("bn-chai"); -const { ethers } = require("ethers"); +const { ethers, BigNumber } = require("ethers"); +const path = require("path"); const baseExec = require("child_process").exec; const exec = function (command) { @@ -22,25 +23,60 @@ const { expect } = chai; chai.use(bnChai(web3.utils.BN)); const IColonyNetwork = artifacts.require("IColonyNetwork"); +const IMetaColony = artifacts.require("IMetaColony"); const EtherRouter = artifacts.require("EtherRouter"); const Token = artifacts.require("Token"); const IColony = artifacts.require("IColony"); +const IReputationMiningCycle = artifacts.require("IReputationMiningCycle"); +const WormholeBridgeForColony = artifacts.require("WormholeBridgeForColony"); +const { setupBridging, deployBridge } = require("../../scripts/setup-bridging-contracts"); -const setupBridging = require("../../scripts/setup-bridging-contracts"); +const { MINING_CYCLE_DURATION, CHALLENGE_RESPONSE_WINDOW_DURATION, ROOT_ROLE } = require("../../helpers/constants"); +const { forwardTime, checkErrorRevertEthers, snapshot, revert } = require("../../helpers/test-helper"); +const ReputationMinerTestWrapper = require("../../packages/reputation-miner/test/ReputationMinerTestWrapper"); +const { TruffleLoader } = require("../../packages/package-utils"); -contract("Cross-chain", () => { - let colony; +const UINT256_MAX_ETHERS = ethers.BigNumber.from(2).pow(256).sub(1); + +const contractLoader = new TruffleLoader({ + contractRoot: path.resolve(__dirname, "..", "..", "artifacts", "contracts"), +}); + +contract("Cross-chain", (accounts) => { + let homeColony; + let foreignColony; + let homeColonyNetwork; + let foreignColonyNetwork; let homeBridge; let foreignBridge; + let homeColonyBridge; + let foreignColonyBridge; let gnosisSafe; let zodiacBridge; let bridgeMonitor; + let homeChainId; + let foreignChainId; + let wormholeHomeChainId; + let wormholeForeignChainId; + + let homeMetacolony; + let foreignMetacolony; + + let web3HomeProvider; + let web3ForeignProvider; + + let client; + + let homeSnapshotId; + let foreignSnapshotId; const ADDRESS_ZERO = ethers.constants.AddressZero; const RPC_PORT_1 = hre.__SOLIDITY_COVERAGE_RUNNING ? 8555 : 8545; const RPC_PORT_2 = 8546; + const MINER_ADDRESS = accounts[5]; + const HOME_PORT = process.env.HARDHAT_FOREIGN === "true" ? RPC_PORT_2 : RPC_PORT_1; const FOREIGN_PORT = process.env.HARDHAT_FOREIGN === "true" ? RPC_PORT_1 : RPC_PORT_2; @@ -49,33 +85,154 @@ contract("Cross-chain", () => { const ethersForeignSigner = new ethers.providers.JsonRpcProvider(foreignRpcUrl).getSigner(); const ethersHomeSigner = new ethers.providers.JsonRpcProvider(homeRpcUrl).getSigner(); - let etherRouterAddress; + const ethersForeignSigner2 = new ethers.providers.JsonRpcProvider(foreignRpcUrl).getSigner(1); + const ethersHomeSigner2 = new ethers.providers.JsonRpcProvider(homeRpcUrl).getSigner(1); + + async function setForeignBridgeData(foreignColonyBridgeForColony) { + const bridge = new ethers.Contract(foreignColonyBridge.address, WormholeBridgeForColony.abi, ethersForeignSigner); + + let tx = await bridge.setColonyBridgeAddress(foreignChainId, foreignColonyBridge.address); + await tx.wait(); + tx = await bridge.setColonyBridgeAddress(homeChainId, homeColonyBridge.address); + await tx.wait(); + + tx = await bridge.setColonyNetworkAddress(foreignColonyNetwork.address); + await tx.wait(); + + tx = await foreignMetacolony.setColonyBridgeAddress(foreignColonyBridgeForColony); + await tx.wait(); + } + + async function setHomeBridgeData(homeColonyBridgeAddressForColony) { + const bridge = new ethers.Contract(homeColonyBridge.address, WormholeBridgeForColony.abi, ethersHomeSigner); + + let tx = await bridge.setColonyBridgeAddress(foreignChainId, foreignColonyBridge.address); + await tx.wait(); + tx = await bridge.setColonyBridgeAddress(homeChainId, homeColonyBridge.address); + await tx.wait(); + + tx = await bridge.setColonyNetworkAddress(homeColonyNetwork.address); + await tx.wait(); + + tx = await homeMetacolony.setColonyBridgeAddress(homeColonyBridgeAddressForColony); + await tx.wait(); + } + let newEtherRouterAddress; before(async () => { await exec(`PORT=${FOREIGN_PORT} bash ./scripts/setup-foreign-chain.sh`); - ({ bridgeMonitor, gnosisSafe, zodiacBridge, homeBridge, foreignBridge } = await setupBridging(homeRpcUrl, foreignRpcUrl)); + ({ bridgeMonitor, gnosisSafe, zodiacBridge, homeBridge, foreignBridge, foreignColonyBridge, homeColonyBridge } = await setupBridging( + homeRpcUrl, + foreignRpcUrl, + )); + + // Add bridge to the foreign colony network + // const homeNetworkId = await ethersHomeSigner.provider.send("net_version", []); + homeChainId = await ethersHomeSigner.provider.send("eth_chainId", []); + wormholeHomeChainId = BigNumber.from(homeChainId).mod(265669).mul(2); + + // const foreignNetworkId = await ethersForeignSigner.provider.send("net_version", []); + foreignChainId = await ethersForeignSigner.provider.send("eth_chainId", []); + wormholeForeignChainId = BigNumber.from(foreignChainId).mod(265669).mul(2); + + // Deploy colonyNetwork to whichever chain truffle hasn't already deployed to. + try { + const nonHardhatChainId = process.env.HARDHAT_FOREIGN === "true" ? homeChainId : foreignChainId; + + const output = await exec(`CHAIN_ID=${parseInt(nonHardhatChainId, 16)} npx hardhat deploy --network development2`); + [, , , , , , , newEtherRouterAddress] = output + .split("\n") + .filter((x) => x.includes("Colony Network deployed at"))[0] + .split(" "); + } catch (err) { + console.log(err); + process.exit(1); + } - // If Truffle is not on the home chain, then deploy colonyNetwork to the home chain + // 0x539 is the chain id used by truffle by default (regardless of networkid), and if + // we see it in our tests that's the coverage chain, which builds the contract artifacts + // in to a different location. If we see another chain id, we assume it's non-coverage + // truffle and look for the build artifacts in the normal place. + + let homeEtherRouterAddress; + let foreignEtherRouterAddress; if (process.env.HARDHAT_FOREIGN === "true") { - try { - const output = await exec(`npx hardhat deploy --network development2`); - [, , , , , , , etherRouterAddress] = output - .split("\n") - .filter((x) => x.includes("Colony Network deployed at"))[0] - .split(" "); - } catch (err) { - console.log(err); - process.exit(1); - } + homeEtherRouterAddress = newEtherRouterAddress; + foreignEtherRouterAddress = (await EtherRouter.deployed()).address; } else { - etherRouterAddress = (await EtherRouter.deployed()).address; + homeEtherRouterAddress = (await EtherRouter.deployed()).address; + foreignEtherRouterAddress = newEtherRouterAddress; } + + console.log("foreign colony network", foreignEtherRouterAddress); + console.log("home colony network", homeEtherRouterAddress); + + homeColonyNetwork = await new ethers.Contract(homeEtherRouterAddress, IColonyNetwork.abi, ethersHomeSigner); + foreignColonyNetwork = await new ethers.Contract(foreignEtherRouterAddress, IColonyNetwork.abi, ethersForeignSigner); }); beforeEach(async () => { + web3HomeProvider = new web3.eth.providers.HttpProvider(ethersHomeSigner.provider.connection.url); + web3ForeignProvider = new web3.eth.providers.HttpProvider(ethersForeignSigner.provider.connection.url); + + homeSnapshotId = await snapshot(web3HomeProvider); + foreignSnapshotId = await snapshot(web3ForeignProvider); + bridgeMonitor.reset(); + + let tx = await foreignBridge.setBridgeEnabled(true); + await tx.wait(); + tx = await homeBridge.setBridgeEnabled(true); + await tx.wait(); + + const foreignMCAddress = await foreignColonyNetwork.getMetaColony(); + foreignMetacolony = await new ethers.Contract(foreignMCAddress, IMetaColony.abi, ethersForeignSigner); + const homeMCAddress = await homeColonyNetwork.getMetaColony(); + homeMetacolony = await new ethers.Contract(homeMCAddress, IMetaColony.abi, ethersHomeSigner); + + await setForeignBridgeData(foreignColonyBridge.address); + await setHomeBridgeData(homeColonyBridge.address); + + // Bridge over skills that have been created on the foreign chain + + const latestSkillId = await foreignColonyNetwork.getSkillCount(); + const skillId = ethers.BigNumber.from(foreignChainId).mul(ethers.BigNumber.from(2).pow(128)).add(1); + for (let i = skillId; i <= latestSkillId; i = i.add(1)) { + const p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + tx = await foreignColonyNetwork.bridgeSkillIfNotMiningChain(i); + await tx.wait(); + await p; + // process.exit(1); + } + + // Set up mining client + client = new ReputationMinerTestWrapper({ + loader: contractLoader, + minerAddress: MINER_ADDRESS, + realProviderPort: HOME_PORT, + useJsTree: true, + }); + + await client.initialise(homeColonyNetwork.address); + + await forwardTime(MINING_CYCLE_DURATION + CHALLENGE_RESPONSE_WINDOW_DURATION, undefined, web3HomeProvider); + await client.addLogContentsToReputationTree(); + await client.submitRootHash(); + await client.confirmNewHash(); + + await forwardTime(MINING_CYCLE_DURATION + CHALLENGE_RESPONSE_WINDOW_DURATION, undefined, web3HomeProvider); + await client.addLogContentsToReputationTree(); + await client.submitRootHash(); + await client.confirmNewHash(); + // Set up a colony on the home chain. That may or may not be the truffle chain... - const colonyNetworkEthers = await new ethers.Contract(etherRouterAddress, IColonyNetwork.abi, ethersHomeSigner); + homeColony = await setupColony(homeColonyNetwork); + const p = bridgeMonitor.getPromiseForNextBridgedTransaction(2); + foreignColony = await setupColony(foreignColonyNetwork); + await p; + }); + + async function setupColony(colonyNetworkEthers) { let tx = await colonyNetworkEthers.deployTokenViaNetwork("Test", "TST", 18); let res = await tx.wait(); @@ -87,14 +244,108 @@ contract("Cross-chain", () => { const { colonyAddress } = res.events.filter((x) => x.event === "ColonyAdded")[0].args; - colony = await new ethers.Contract(colonyAddress, IColony.abi, ethersHomeSigner); + const colony = await new ethers.Contract(colonyAddress, IColony.abi, colonyNetworkEthers.signer); + return colony; + } + + afterEach(async () => { + await revert(web3HomeProvider, homeSnapshotId); + await revert(web3ForeignProvider, foreignSnapshotId); }); after(async () => { await bridgeMonitor.close(); }); - describe("when controlling a gnosis wallet on another chain", async () => { + describe("administrating cross-network bridges", async () => { + it("bridge data can be queried", async () => { + const bridgeAddress = await homeColonyNetwork.getColonyBridgeAddress(); + expect(bridgeAddress).to.equal(homeColonyBridge.address); + + const networkAddress = await homeColonyBridge.colonyNetwork(); + expect(networkAddress).to.equal(homeColonyNetwork.address); + + const foreignColonyBridgeAddress = await homeColonyBridge.getColonyBridgeAddress(foreignChainId); + expect(foreignColonyBridgeAddress).to.equal(foreignColonyBridge.address); + }); + + it("setColonyBridgeAddress on Network can only be called by the metacolony", async () => { + const tx = await foreignColonyNetwork.setColonyBridgeAddress(foreignColonyBridge.address, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-caller-must-be-meta-colony"); + }); + + it("setColonyBridgeAddress on Metacolony can't be called by an address without root permissions", async () => { + const foreignMetacolony2 = new ethers.Contract(foreignMetacolony.address, IColonyNetwork.abi, ethersForeignSigner2); + + let tx = await foreignMetacolony2.setColonyBridgeAddress(ADDRESS_ZERO, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "ds-auth-unauthorized"); + + // Add root permissions + tx = await foreignMetacolony.setUserRoles( + 1, + UINT256_MAX_ETHERS, + accounts[1], + 1, + ethers.utils.hexZeroPad(ethers.BigNumber.from(ethers.BigNumber.from(2).pow(ROOT_ROLE)).toHexString(), 32), + ); + await tx.wait(); + + // Can now call + tx = await foreignMetacolony2.setColonyBridgeAddress(ADDRESS_ZERO, { + gasLimit: 1000000, + }); + await tx.wait(); + + // Reset permissions + tx = await foreignMetacolony.setUserRoles(1, UINT256_MAX_ETHERS, accounts[1], 1, ethers.utils.hexZeroPad("0x00", 32)); + await tx.wait(); + }); + + it("setColonyNetworkAddress can only set information for bridges where assumptions we've made about chainid are not broken", async () => { + const tx = await homeColonyBridge.setColonyBridgeAddress(UINT256_MAX_ETHERS, ADDRESS_ZERO, { + gasLimit: 1000000, + }); + await checkErrorRevertEthers(tx.wait(), "colony-bridge-chainid-too-large"); + }); + + it("updating the bridge for a chain does not reset the bridged skill count", async () => { + const countBefore = await homeColonyNetwork.getBridgedSkillCounts(foreignChainId); + const tx = await homeMetacolony.setColonyBridgeAddress(ADDRESS_ZERO); + await tx.wait(); + + const countAfter = await homeColonyNetwork.getBridgedSkillCounts(foreignChainId); + expect(countAfter).to.not.equal(0); + expect(countAfter.sub(countBefore).toNumber()).to.equal(0); + }); + + it("the bridged skill count has a sensible default", async () => { + const unsetChainId = ethers.BigNumber.from(123456789); + const count = await homeColonyNetwork.getBridgedSkillCounts(unsetChainId); + expect(count.toString()).to.equal(unsetChainId.shl(128).toString()); + }); + + it("only owners can set properties on the ColonyBridge", async () => { + let tx = await homeColonyBridge.connect(ethersHomeSigner2).setColonyNetworkAddress(ADDRESS_ZERO, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "ds-auth-unauthorized"); + + tx = await homeColonyBridge.connect(ethersHomeSigner2).setColonyBridgeAddress(1, ADDRESS_ZERO, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "ds-auth-unauthorized"); + + tx = await homeColonyBridge.connect(ethersHomeSigner2).setWormholeAddress(ADDRESS_ZERO, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "ds-auth-unauthorized"); + + tx = await homeColonyBridge.connect(ethersHomeSigner2).setChainIdMapping([1], [2], { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "ds-auth-unauthorized"); + }); + + it("setChainIdMapping can only be called with sane arguments", async () => { + const tx = await homeColonyBridge.setChainIdMapping([1, 3], [2], { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-bridge-chainid-mapping-length-mismatch"); + }); + }); + + describe.skip("when controlling a gnosis wallet on another chain", async () => { + // No longer required it("can send tokens out of the gnosis safe", async () => { // Create token contract on foreign chain @@ -103,7 +354,6 @@ contract("Cross-chain", () => { await fToken.deployTransaction.wait(); await fToken.unlock(); // Send some to safe - // console.log(fToken); await fToken["mint(address,uint256)"](gnosisSafe.address, 100); // We want the safe to execute this transaction... @@ -118,7 +368,7 @@ contract("Cross-chain", () => { // Which we trigger by sending a transaction to the module... // So what's the tx data for what we want the colony to call on the amb? - const txDataToBeSentToAMB = homeBridge.interface.encodeFunctionData("requireToPassMessage", [ + const txDataToBeSentToAMB = homeColonyBridge.interface.encodeFunctionData("sendMessage", [ zodiacBridge.address, txDataToBeSentToZodiacModule, 1000000, @@ -126,18 +376,12 @@ contract("Cross-chain", () => { // Which we trigger by sending a transaction to the module... // Set up promise that will see it bridged across - const p = new Promise((resolve) => { - foreignBridge.on("RelayedMessage", async (_sender, msgSender, _messageId, success) => { - console.log("bridged with ", _sender, msgSender, _messageId, success); - resolve(); - }); - }); + const p = bridgeMonitor.getPromiseForNextBridgedTransaction(); // So 'just' call that on the colony... console.log("tx to home bridge address:", homeBridge.address); - - const tx = await colony.makeArbitraryTransaction(homeBridge.address, txDataToBeSentToAMB); + const tx = await homeColony.makeArbitraryTransaction(homeBridge.address, txDataToBeSentToAMB); await tx.wait(); await p; // Check balances @@ -147,4 +391,959 @@ contract("Cross-chain", () => { expect(b2.toNumber()).to.equal(10); }); }); + + describe("when adding skills on another chain", async () => { + it("can create a skill on another chain and it's reflected on the home chain", async () => { + // See skills on home chain + const beforeCount = await homeColonyNetwork.getBridgedSkillCounts("0x0fd5c9ed"); + + const p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + + // Create a skill on foreign chain + // await foreignColony.addDomain(1); + const foreignBeforeCount = await foreignColonyNetwork.getSkillCount(); + const tx = await foreignColony["addDomain(uint256,uint256,uint256)"](1, UINT256_MAX_ETHERS, 1); + await tx.wait(); + + const foreignAfterCount = await foreignColonyNetwork.getSkillCount(); + expect(foreignBeforeCount.add(1).toHexString()).to.equal(foreignAfterCount.toHexString()); + await p; + + // Check reflected on home chain + const afterCount = await homeColonyNetwork.getBridgedSkillCounts("0x0fd5c9ed"); + expect(beforeCount.add(1).toHexString()).to.equal(afterCount.toHexString()); + }); + + it("addSkillFromBridge cannot be called by a non-bridge address", async () => { + const tx = await homeColonyNetwork.addSkillFromBridge(0, 0, { + gasLimit: 1000000, + }); + await checkErrorRevertEthers(tx.wait(), "colony-network-caller-must-be-colony-bridge"); + }); + + it("addPendingSkill doesn't create skills that haven't been bridged", async () => { + const homeSkillCount = await homeColonyNetwork.getBridgedSkillCounts(foreignChainId); + const tx = await homeColonyNetwork.addPendingSkill(homeSkillCount.add(1), { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-network-no-such-bridged-skill"); + }); + + it("if a skill is bridged out-of-order, it's added to the pending mapping", async () => { + bridgeMonitor.skipCount = 1; + // Create a skill on the foreign chain + let tx = await foreignColony["addDomain(uint256,uint256,uint256)"](1, UINT256_MAX_ETHERS, 1); + await tx.wait(); + + await bridgeMonitor.waitUntilSkipped(); + + const foreignDomain = await foreignColony.getDomain(1); + + let p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + + // Create another skill on the foreign chain + // Bridge the latter without bridging the former + tx = await foreignColony["addDomain(uint256,uint256,uint256)"](1, UINT256_MAX_ETHERS, 1); + await tx.wait(); + const foreignSkillCount = await foreignColonyNetwork.getSkillCount(); + + await p; + + // Check it's pending + const pendingAddition = await homeColonyNetwork.getPendingSkillAddition(foreignChainId, foreignSkillCount); + + expect(pendingAddition.toHexString()).to.equal(foreignDomain.skillId.toHexString()); + + // Need to clean up + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + tx = await foreignColonyNetwork.bridgeSkillIfNotMiningChain(foreignSkillCount.sub(1)); + await tx.wait(); + await p; + tx = await homeColonyNetwork.addPendingSkill(foreignSkillCount, { gasLimit: 1000000 }); + await tx.wait(); + }); + + it("if a skill is bridged out-of-order, it can be added once the earlier skills are bridged ", async () => { + bridgeMonitor.skipCount = 1; + // Create a skill on the foreign chain + let tx = await foreignColony["addDomain(uint256,uint256,uint256)"](1, UINT256_MAX_ETHERS, 1); + await tx.wait(); + await bridgeMonitor.waitUntilSkipped(); + + let p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + // Create another skill on the foreign chain + // Bridge the latter without bridging the former + tx = await foreignColony["addDomain(uint256,uint256,uint256)"](1, UINT256_MAX_ETHERS, 1); + await tx.wait(); + const foreignSkillCount = await foreignColonyNetwork.getSkillCount(); + await p; + + // Try to add + tx = await homeColonyNetwork.addPendingSkill(foreignSkillCount, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-network-not-next-bridged-skill"); + + // Bridge the next skill + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + tx = await foreignColonyNetwork.bridgeSkillIfNotMiningChain(foreignSkillCount.sub(1)); + await tx.wait(); + await p; + + // Add the pending skill + tx = await homeColonyNetwork.addPendingSkill(foreignSkillCount, { gasLimit: 1000000 }); + await tx.wait(); + + // Check it was added + const homeSkillCount = await homeColonyNetwork.getBridgedSkillCounts(foreignChainId); + expect(homeSkillCount.toHexString()).to.equal(foreignSkillCount.toHexString()); + + // And removed from pending + const pendingAddition = await homeColonyNetwork.getPendingSkillAddition(foreignChainId, foreignSkillCount); + expect(pendingAddition.toHexString()).to.equal("0x00"); + }); + + it("if a skill that was pending is repeatedly bridged, the resuling transaction fails after the first time", async () => { + bridgeMonitor.skipCount = 1; + // Create a skill on the foreign chain + let tx = await foreignColony["addDomain(uint256,uint256,uint256)"](1, UINT256_MAX_ETHERS, 1); + await tx.wait(); + await bridgeMonitor.waitUntilSkipped(); + + let p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + // Create another skill on the foreign chain + // Bridge the latter without bridging the former + tx = await foreignColony["addDomain(uint256,uint256,uint256)"](1, UINT256_MAX_ETHERS, 1); + await tx.wait(); + const foreignSkillCount = await foreignColonyNetwork.getSkillCount(); + await p; + + // Try to add + tx = await homeColonyNetwork.addPendingSkill(foreignSkillCount, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-network-not-next-bridged-skill"); + + // Bridge the next skill + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + tx = await foreignColonyNetwork.bridgeSkillIfNotMiningChain(foreignSkillCount.sub(1)); + await tx.wait(); + await p; + + // Add the pending skill + tx = await homeColonyNetwork.addPendingSkill(foreignSkillCount, { gasLimit: 1000000 }); + await tx.wait(); + + // Adding again doesn't work + tx = await homeColonyNetwork.addPendingSkill(foreignSkillCount, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-network-not-next-bridged-skill"); + + // And bridging again doesn't work + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + tx = await foreignColonyNetwork.bridgeSkillIfNotMiningChain(foreignSkillCount); + await tx.wait(); + await p; + + const pendingAddition = await homeColonyNetwork.getPendingSkillAddition(foreignChainId, foreignSkillCount); + expect(pendingAddition.toHexString()).to.equal("0x00"); + + const homeSkillCount = await homeColonyNetwork.getBridgedSkillCounts(foreignChainId); + expect(homeSkillCount.toHexString()).to.equal(foreignSkillCount.toHexString()); + }); + + it("can't bridge a skill that doesn't exist", async () => { + const skillCount = await foreignColonyNetwork.getSkillCount(); + const nonExistentSkillId = skillCount.add(10000000); + const tx = await foreignColonyNetwork.bridgeSkillIfNotMiningChain(nonExistentSkillId, { + gasLimit: 1000000, + }); + await checkErrorRevertEthers(tx.wait(), "colony-invalid-skill-id"); + }); + + it("if bridge is broken, bridging skill transaction doesn't revert (allowing e.g. domains to be created)", async () => { + let tx = await foreignBridge.setBridgeEnabled(false); + await tx.wait(); + const skillCount = await foreignColonyNetwork.getSkillCount(); + + tx = await foreignColonyNetwork.bridgeSkillIfNotMiningChain(skillCount, { + gasLimit: 1000000, + }); + let receipt = await tx.wait(); + expect(receipt.status).to.equal(1); + + tx = await foreignColony["addDomain(uint256,uint256,uint256)"](1, UINT256_MAX_ETHERS, 1); + receipt = await tx.wait(); + + let events = receipt.logs.map(function (log) { + try { + return foreignColonyNetwork.interface.parseLog(log); + } catch (e) { + // Return nothing + } + return null; + }); + events = events.filter((x) => x != null && x.eventFragment.name === "SkillCreationStored"); + expect(events.length).to.equal(1); + const event = events[0]; + expect(event.args[0].toString()).to.equal(skillCount.add(1).toString()); + }); + + it("colony root local skill structures end up the same on both chains", async () => { + const homeColonyRootLocalSkillId = await homeColony.getRootLocalSkill(); + let homeColonyRootLocalSkill = await homeColonyNetwork.getSkill(homeColonyRootLocalSkillId); + + const foreignColonyRootLocalSkillId = await foreignColony.getRootLocalSkill(); + let foreignColonyRootLocalSkill = await foreignColonyNetwork.getSkill(foreignColonyRootLocalSkillId); + + expect(homeColonyRootLocalSkill.nParents.toString()).to.equal(foreignColonyRootLocalSkill.nParents.toString()); + expect(homeColonyRootLocalSkill.nChildren.toString()).to.equal(foreignColonyRootLocalSkill.nChildren.toString()); + + let tx = await homeColony.addLocalSkill(); + await tx.wait(); + + const p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + tx = await foreignColony.addLocalSkill(); + await tx.wait(); + await p; + homeColonyRootLocalSkill = await homeColonyNetwork.getSkill(homeColonyRootLocalSkillId); + foreignColonyRootLocalSkill = await foreignColonyNetwork.getSkill(foreignColonyRootLocalSkillId); + + expect(homeColonyRootLocalSkill.nParents.toString()).to.equal(foreignColonyRootLocalSkill.nParents.toString()); + expect(homeColonyRootLocalSkill.nChildren.toString()).to.equal(foreignColonyRootLocalSkill.nChildren.toString()); + + let zeroSkill = await foreignColonyNetwork.getSkill(ethers.BigNumber.from(foreignChainId).mul(ethers.BigNumber.from(2).pow(128))); + expect(zeroSkill.nChildren.toNumber()).to.equal(0); + + zeroSkill = await homeColonyNetwork.getSkill(ethers.BigNumber.from(foreignChainId).mul(ethers.BigNumber.from(2).pow(128))); + expect(zeroSkill.nChildren.toNumber()).to.equal(0); + + zeroSkill = await homeColonyNetwork.getSkill(0); + expect(zeroSkill.nChildren.toNumber()).to.equal(0); + }); + }); + + describe("while earning reputation on another chain", async () => { + it("reputation awards are ultimately reflected", async () => { + let p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + // Emit reputation + await foreignColony.emitDomainReputationReward(1, accounts[0], "0x1337"); + // See that it's bridged to the inactive log + await p; + const logAddress = await homeColonyNetwork.getReputationMiningCycle(false); + const reputationMiningCycleInactive = new ethers.Contract(logAddress, IReputationMiningCycle.abi, ethersHomeSigner); + + const len = await reputationMiningCycleInactive.getReputationUpdateLogLength(); + + const entry = await reputationMiningCycleInactive.getReputationUpdateLogEntry(len.sub(1)); + + expect(entry.amount.toHexString()).to.equal("0x1337"); + expect(entry.user).to.equal(accounts[0]); + expect(entry.colony).to.equal(foreignColony.address); + + const domain = await foreignColony.getDomain(1); + + expect(entry.skillId.toHexString()).to.equal(domain.skillId.toHexString()); + + // Advance mining cycle twice + await forwardTime(MINING_CYCLE_DURATION + CHALLENGE_RESPONSE_WINDOW_DURATION, undefined, web3HomeProvider); + await client.addLogContentsToReputationTree(); + await client.submitRootHash(); + await client.confirmNewHash(); + + await forwardTime(MINING_CYCLE_DURATION + CHALLENGE_RESPONSE_WINDOW_DURATION, undefined, web3HomeProvider); + await client.addLogContentsToReputationTree(); + await client.submitRootHash(); + await client.confirmNewHash(); + + // Check in state + const key = await ReputationMinerTestWrapper.getKey(foreignColony.address, entry.skillId, accounts[0]); + expect(client.reputations[key]).to.not.equal(undefined); + expect(ethers.BigNumber.from(client.reputations[key].slice(0, 66)).toHexString()).to.equal("0x1337"); + + // Bridge it + + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + const tx = await homeColonyNetwork.bridgeCurrentRootHash(foreignChainId); + await tx.wait(); + await p; + + // Check state bridged to host chain + const foreignChainRootHash = await foreignColonyNetwork.getReputationRootHash(); + const foreignNLeaves = await foreignColonyNetwork.getReputationRootHashNNodes(); + const homeChainRootHash = await homeColonyNetwork.getReputationRootHash(); + const homeNLeaves = await homeColonyNetwork.getReputationRootHashNNodes(); + + expect(foreignChainRootHash).to.equal(homeChainRootHash); + expect(homeNLeaves.toHexString()).to.equal(foreignNLeaves.toHexString()); + }); + + it("if bridge disabled, reputation emissions are stored to be reemitted later", async () => { + let tx = await foreignBridge.setBridgeEnabled(false); + await tx.wait(); + const bridgedReputationUpdateCountBefore = await foreignColonyNetwork.getBridgedReputationUpdateCount(foreignChainId, foreignColony.address); + tx = await foreignColony.emitDomainReputationReward(1, accounts[0], "0x1337"); + await tx.wait(); + + // See it was stored for later + const bridgedReputationUpdateCountAfter = await foreignColonyNetwork.getBridgedReputationUpdateCount(foreignChainId, foreignColony.address); + expect(bridgedReputationUpdateCountAfter.sub(bridgedReputationUpdateCountBefore).toNumber()).to.equal(1); + }); + + it("if bridge disabled, cannot bridge current state", async () => { + let tx = await homeBridge.setBridgeEnabled(false); + await tx.wait(); + tx = await homeColonyNetwork.bridgeCurrentRootHash(foreignChainId, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-mining-bridge-call-failed"); + }); + + it("if bridge not set, cannot bridge current state", async () => { + let tx = await homeMetacolony.setColonyBridgeAddress(ADDRESS_ZERO); + await tx.wait(); + tx = await homeColonyNetwork.bridgeCurrentRootHash(foreignChainId, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-network-bridge-not-set"); + }); + + it("if bridge unknown, cannot bridge current state", async () => { + const tx = await homeColonyNetwork.bridgeCurrentRootHash(ADDRESS_ZERO, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-bridge-not-known-chain"); + }); + + it("stored reputation emissions can be emitted later", async () => { + let tx = await foreignBridge.setBridgeEnabled(false); + await tx.wait(); + tx = await foreignColony.emitDomainReputationReward(1, accounts[0], "0x1338"); + await tx.wait(); + + const bridgedReputationUpdateCount = await foreignColonyNetwork.getBridgedReputationUpdateCount(foreignChainId, foreignColony.address); + + tx = await foreignBridge.setBridgeEnabled(true); + await tx.wait(); + const p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + + tx = await foreignColonyNetwork.bridgePendingReputationUpdate(foreignColony.address, bridgedReputationUpdateCount); + await tx.wait(); + + await p; + + // See that it's bridged to the inactive log + const logAddress = await homeColonyNetwork.getReputationMiningCycle(false); + const reputationMiningCycleInactive = await new ethers.Contract(logAddress, IReputationMiningCycle.abi, ethersHomeSigner); + + const len = await reputationMiningCycleInactive.getReputationUpdateLogLength(); + + const entry = await reputationMiningCycleInactive.getReputationUpdateLogEntry(len.sub(1)); + + expect(entry.amount.toHexString()).to.equal("0x1338"); + expect(entry.user).to.equal(accounts[0]); + expect(entry.colony).to.equal(foreignColony.address); + + const domain = await foreignColony.getDomain(1); + + expect(entry.skillId.toHexString()).to.equal(domain.skillId.toHexString()); + }); + + it("stored reputation emissions on the foreign chain can be bridged later, and are decayed if required", async () => { + let tx = await foreignBridge.setBridgeEnabled(false); + await tx.wait(); + tx = await foreignColony.emitDomainReputationReward(1, accounts[0], "0x1338"); + await tx.wait(); + + const bridgedReputationUpdateCount = await foreignColonyNetwork.getBridgedReputationUpdateCount(foreignChainId, foreignColony.address); + + tx = await foreignBridge.setBridgeEnabled(true); + await tx.wait(); + + await forwardTime(MINING_CYCLE_DURATION * 10, undefined, web3HomeProvider); + await forwardTime(MINING_CYCLE_DURATION * 10, undefined, web3ForeignProvider); + + const p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + + tx = await foreignColonyNetwork.bridgePendingReputationUpdate(foreignColony.address, bridgedReputationUpdateCount); + await tx.wait(); + + await p; + + // See that it's bridged to the inactive log + const logAddress = await homeColonyNetwork.getReputationMiningCycle(false); + const reputationMiningCycleInactive = await new ethers.Contract(logAddress, IReputationMiningCycle.abi, ethersHomeSigner); + + const len = await reputationMiningCycleInactive.getReputationUpdateLogLength(); + + const entry = await reputationMiningCycleInactive.getReputationUpdateLogEntry(len.sub(1)); + + expect(entry.amount.toHexString()).to.equal("0x1327"); // Decayed + expect(entry.user).to.equal(accounts[0]); + expect(entry.colony).to.equal(foreignColony.address); + + const domain = await foreignColony.getDomain(1); + + expect(entry.skillId.toHexString()).to.equal(domain.skillId.toHexString()); + }); + + it("stored reputation emissions have to be emitted in order, but only per-colony", async () => { + let p = bridgeMonitor.getPromiseForNextBridgedTransaction(2); + const foreignColony2 = await setupColony(foreignColonyNetwork); + await p; + + let tx = await foreignBridge.setBridgeEnabled(false); + await tx.wait(); + tx = await foreignColony.emitDomainReputationReward(1, accounts[0], "0x1338"); + await tx.wait(); + tx = await foreignColony.emitDomainReputationReward(1, accounts[0], "0x1339"); + await tx.wait(); + tx = await foreignColony2.emitDomainReputationReward(1, accounts[0], "0x1340"); + await tx.wait(); + + tx = await foreignBridge.setBridgeEnabled(true); + await tx.wait(); + const bridgedReputationUpdateCountColony1 = await foreignColonyNetwork.getBridgedReputationUpdateCount(foreignChainId, foreignColony.address); + + const logAddress = await homeColonyNetwork.getReputationMiningCycle(false); + const reputationMiningCycleInactive = await new ethers.Contract(logAddress, IReputationMiningCycle.abi, ethersHomeSigner); + const logLengthBefore = await reputationMiningCycleInactive.getReputationUpdateLogLength(); + + // We cannot emit the second bridged + tx = await foreignColonyNetwork.bridgePendingReputationUpdate(foreignColony.address, bridgedReputationUpdateCountColony1, { + gasLimit: 1000000, + }); + await checkErrorRevertEthers(tx.wait(), "colony-network-not-next-pending-update"); + + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + // We can emit the third (which was another colony) + const bridgedReputationUpdateCountColony2 = await foreignColonyNetwork.getBridgedReputationUpdateCount(foreignChainId, foreignColony2.address); + tx = await foreignColonyNetwork.bridgePendingReputationUpdate(foreignColony2.address, bridgedReputationUpdateCountColony2); + await tx.wait(); + await p; + + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + // We can emit the first + tx = await foreignColonyNetwork.bridgePendingReputationUpdate(foreignColony.address, bridgedReputationUpdateCountColony1.sub(1)); + await tx.wait(); + await p; + + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + // And now we can emit the second + tx = await foreignColonyNetwork.bridgePendingReputationUpdate(foreignColony.address, bridgedReputationUpdateCountColony1); + await tx.wait(); + await p; + + const logLengthAfter = await reputationMiningCycleInactive.getReputationUpdateLogLength(); + expect(logLengthAfter.sub(logLengthBefore).toNumber()).to.equal(3); + }); + + it("if a bridged reputation emission isn't the next one, it's stored on the mining chain to be added to the log later", async () => { + const logAddress = await homeColonyNetwork.getReputationMiningCycle(false); + const reputationMiningCycleInactive = await new ethers.Contract(logAddress, IReputationMiningCycle.abi, ethersHomeSigner); + const logLengthBefore = await reputationMiningCycleInactive.getReputationUpdateLogLength(); + + let p = bridgeMonitor.getPromiseForNextBridgedTransaction(2); + const foreignColony2 = await setupColony(foreignColonyNetwork); + await p; + + bridgeMonitor.skipCount = 1; + + // Bridge skills + + // This one is skipped + let tx = await foreignColony.emitDomainReputationReward(1, accounts[0], "0x1338"); + await tx.wait(); + await bridgeMonitor.waitUntilSkipped(); + + // These are bridged and added to the pending log + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + tx = await foreignColony.emitDomainReputationReward(1, accounts[0], "0x1339"); + await tx.wait(); + await p; + + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + tx = await foreignColony.emitDomainReputationReward(1, accounts[0], "0x1340"); + await tx.wait(); + await p; + + // This gets added to the log after being bridged, as it is another colony + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + tx = await foreignColony2.emitDomainReputationReward(1, accounts[0], "0x1341"); + await tx.wait(); + await p; + + // The log entry for foreignColony2 has been added to the reputation mining cycle contract + const logLengthAfterBridging = await reputationMiningCycleInactive.getReputationUpdateLogLength(); + expect(logLengthAfterBridging.sub(logLengthBefore).toNumber()).to.equal(1); + + // The two log entries have been added to the pending log + let count = await homeColonyNetwork.getBridgedReputationUpdateCount(foreignChainId, foreignColony.address); + let pending1 = await homeColonyNetwork.getPendingReputationUpdate(foreignChainId, foreignColony.address, count.add(2)); + expect(pending1.amount.toHexString()).to.equal("0x1339"); + expect(pending1.user).to.equal(accounts[0]); + expect(pending1.colony).to.equal(foreignColony.address); + + let pending2 = await homeColonyNetwork.getPendingReputationUpdate(foreignChainId, foreignColony.address, count.add(3)); + expect(pending2.amount.toHexString()).to.equal("0x1340"); + expect(pending2.user).to.equal(accounts[0]); + expect(pending2.colony).to.equal(foreignColony.address); + + // We can't emit those yet because we still haven't bridged the one that was skipped + tx = await homeColonyNetwork.addPendingReputationUpdate(foreignChainId, foreignColony.address, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-network-next-update-does-not-exist"); + + // If we bridge over the original one that was skipped, then we can emit the two pending ones + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + await bridgeMonitor.bridgeSkipped(); + await p; + count = await homeColonyNetwork.getBridgedReputationUpdateCount(foreignChainId, foreignColony.address); + + tx = await homeColonyNetwork.addPendingReputationUpdate(foreignChainId, foreignColony.address); + await tx.wait(); + tx = await homeColonyNetwork.addPendingReputationUpdate(foreignChainId, foreignColony.address); + await tx.wait(); + + // And now they're on the pending log + const logLengthAfterAdditionalBridging = await reputationMiningCycleInactive.getReputationUpdateLogLength(); + expect(logLengthAfterAdditionalBridging.sub(logLengthAfterBridging).toNumber()).to.equal(3); + + // And removed from the colony network + + pending1 = await homeColonyNetwork.getPendingReputationUpdate(foreignChainId, foreignColony.address, count.add(2)); + expect(pending1.amount.toHexString()).to.equal("0x00"); + expect(pending1.user).to.equal(ADDRESS_ZERO); + expect(pending1.colony).to.equal(ADDRESS_ZERO); + + pending2 = await homeColonyNetwork.getPendingReputationUpdate(foreignChainId, foreignColony.address, count.add(3)); + expect(pending2.amount.toHexString()).to.equal("0x00"); + expect(pending2.user).to.equal(ADDRESS_ZERO); + expect(pending2.colony).to.equal(ADDRESS_ZERO); + }); + + it(`if a bridged reputation emission isn't the next one, it's stored on the mining chain to be added to the log later + and decayed if required`, async () => { + let tx = await foreignBridge.setBridgeEnabled(false); + await tx.wait(); + tx = await foreignColony.emitDomainReputationReward(1, accounts[0], "0x1338"); + await tx.wait(); + + const bridgedReputationUpdateCount = await foreignColonyNetwork.getBridgedReputationUpdateCount(foreignChainId, foreignColony.address); + + tx = await foreignBridge.setBridgeEnabled(true); + await tx.wait(); + + let p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + await foreignColony.emitDomainReputationReward(1, accounts[0], "0x1339"); + await p; + + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + tx = await foreignColonyNetwork.bridgePendingReputationUpdate(foreignColony.address, bridgedReputationUpdateCount); + await tx.wait(); + await p; + + const pending1 = await homeColonyNetwork.getPendingReputationUpdate(foreignChainId, foreignColony.address, bridgedReputationUpdateCount.add(1)); + expect(pending1.amount.toHexString()).to.equal("0x1339"); + expect(pending1.user).to.equal(accounts[0]); + expect(pending1.colony).to.equal(foreignColony.address); + + await forwardTime(MINING_CYCLE_DURATION * 10, undefined, web3HomeProvider); + await forwardTime(MINING_CYCLE_DURATION * 10, undefined, web3ForeignProvider); + tx = await homeColonyNetwork.addPendingReputationUpdate(foreignChainId, foreignColony.address); + await tx; + + // See that it's bridged to the pending log, but decayed + + const logAddress = await homeColonyNetwork.getReputationMiningCycle(false); + const reputationMiningCycleInactive = await new ethers.Contract(logAddress, IReputationMiningCycle.abi, ethersHomeSigner); + + const len = await reputationMiningCycleInactive.getReputationUpdateLogLength(); + + const entry = await reputationMiningCycleInactive.getReputationUpdateLogEntry(len.sub(1)); + + expect(entry.amount.toHexString()).to.equal("0x1328"); + expect(entry.user).to.equal(accounts[0]); + expect(entry.colony).to.equal(foreignColony.address); + + const domain = await foreignColony.getDomain(1); + + expect(entry.skillId.toHexString()).to.equal(domain.skillId.toHexString()); + }); + + it(`if a bridged reputation emission is for a skill that hasn't been bridged, + it's stored on the mining chain to be added to the log later`, async () => { + const logAddress = await homeColonyNetwork.getReputationMiningCycle(false); + const reputationMiningCycleInactive = await new ethers.Contract(logAddress, IReputationMiningCycle.abi, ethersHomeSigner); + + bridgeMonitor.skipCount = 2; + const foreignColony2 = await setupColony(foreignColonyNetwork); + await bridgeMonitor.waitUntilSkipped(); + + // Bridge skills + let p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + let tx = await foreignColony2.emitDomainReputationReward(1, accounts[0], "0x1338"); + await tx.wait(); + await p; + + // A log entries have been added to the pending log + const count = await homeColonyNetwork.getBridgedReputationUpdateCount(foreignChainId, foreignColony2.address); + let pending = await homeColonyNetwork.getPendingReputationUpdate(foreignChainId, foreignColony2.address, count.add(1)); + expect(pending.amount.toHexString()).to.equal("0x1338"); + expect(pending.user).to.equal(accounts[0]); + expect(pending.colony).to.equal(foreignColony2.address); + + // We can't emit it yet, because the skill still hasn't been bridged + tx = await homeColonyNetwork.addPendingReputationUpdate(foreignChainId, foreignColony2.address, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-network-invalid-skill-id"); + + const logLength1 = await reputationMiningCycleInactive.getReputationUpdateLogLength(); + + // Bridge over the skill creation + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + await bridgeMonitor.bridgeSkipped(); + await p; + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + await bridgeMonitor.bridgeSkipped(); + await p; + + // Now try to emit the pending reputation emission + tx = await homeColonyNetwork.addPendingReputationUpdate(foreignChainId, foreignColony2.address); + await tx.wait(); + + // And now it's on the mining cycle contract + const logLength2 = await reputationMiningCycleInactive.getReputationUpdateLogLength(); + expect(logLength2.sub(logLength1).toNumber()).to.equal(1); + + // And removed from the colony network + + pending = await homeColonyNetwork.getPendingReputationUpdate(foreignChainId, foreignColony2.address, count.add(1)); + expect(pending.amount.toHexString()).to.equal("0x00"); + expect(pending.user).to.equal(ADDRESS_ZERO); + expect(pending.colony).to.equal(ADDRESS_ZERO); + }); + + it("addReputationUpdateLogFromBridge cannot be called by a non-bridge address", async () => { + const tx = await homeColonyNetwork.addReputationUpdateLogFromBridge(ADDRESS_ZERO, ADDRESS_ZERO, 0, 0, 0, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-network-caller-must-be-colony-bridge"); + }); + + it("bridgePendingReputationUpdate can only be called if the bridge is set", async () => { + // Set bridge to an address that's not a contract, causing the reputation update we subsequently emit to be stored + await setForeignBridgeData(accounts[0]); + let tx = await foreignColony.emitDomainReputationReward(1, accounts[0], "0x1338"); + + const bridgedReputationUpdateCount = await foreignColonyNetwork.getBridgedReputationUpdateCount(foreignChainId, foreignColony.address); + + await setForeignBridgeData(ADDRESS_ZERO); + + tx = await foreignColonyNetwork.bridgePendingReputationUpdate(foreignColony.address, bridgedReputationUpdateCount, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-network-foreign-bridge-not-set"); + await setForeignBridgeData(foreignColonyBridge.address); + }); + + it("bridgePendingReputationUpdate can only bridge an update that exists", async () => { + const tx = await foreignColonyNetwork.bridgePendingReputationUpdate(foreignColony.address, 1000, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-network-update-does-not-exist"); + }); + + it("bridgePendingReputationUpdate can be called again if the bridging transaction fails, or the bridge isn't a contract", async () => { + // Set bridge to an address that's not a contract, causing the reputation update we subsequently emit to be stored + await setForeignBridgeData(accounts[0]); + let tx = await foreignColony.emitDomainReputationReward(1, accounts[0], "0x1338"); + await tx.wait(); + + const bridgedReputationUpdateCount = await foreignColonyNetwork.getBridgedReputationUpdateCount(foreignChainId, foreignColony.address); + // Bridge isn't a contract + tx = await foreignColonyNetwork.bridgePendingReputationUpdate(foreignColony.address, bridgedReputationUpdateCount, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-network-bridging-tx-unsuccessful"); + await setForeignBridgeData(foreignColonyBridge.address); + + // Bridge is now right address, but disable it. + tx = await foreignBridge.setBridgeEnabled(false); + await tx.wait(); + + tx = await foreignColonyNetwork.bridgePendingReputationUpdate(foreignColony.address, bridgedReputationUpdateCount, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-network-bridging-tx-unsuccessful"); + + tx = await foreignBridge.setBridgeEnabled(true); + await tx.wait(); + + tx = await foreignColonyNetwork.bridgePendingReputationUpdate(foreignColony.address, bridgedReputationUpdateCount, { gasLimit: 1000000 }); + await tx.wait(); + }); + }); + + describe("bridge functions are secure", async () => { + it("only the configured colonyNetwork can call `sendMessage`", async () => { + const tx = await foreignColonyBridge.sendMessage(1, "0x00000000", { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "wormhole-bridge-only-colony-network"); + }); + + it("setReputationRootHashFromBridge can only be called by the colonyBridge contract", async () => { + const [, unknownColonyBridge] = await deployBridge(ethersForeignSigner); + await unknownColonyBridge.setColonyNetworkAddress(foreignColonyNetwork.address); + await unknownColonyBridge.setColonyBridgeAddress(homeChainId, homeColonyBridge.address); + const vaa = await bridgeMonitor.encodeMockVAA( + homeColonyBridge.address, + 0, + 0, + foreignColonyNetwork.interface.encodeFunctionData("setReputationRootHashFromBridge", [ethers.utils.hexZeroPad("0xdeadbeef", 32), 0, 1]), + 100, + wormholeHomeChainId, + ); + const tx = await unknownColonyBridge.receiveMessage(vaa, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-network-caller-must-be-colony-bridge"); + }); + + it("setReputationRootHashFromBridge reverts if bridged transaction did not originate from colonyBridge", async () => { + const vaa = await bridgeMonitor.encodeMockVAA( + ADDRESS_ZERO, + 0, + 0, + foreignColonyNetwork.interface.encodeFunctionData("setReputationRootHashFromBridge", [ethers.utils.hexZeroPad("0xdeadbeef", 32), 0, 1]), + 100, + wormholeForeignChainId, + ); + + const tx = await foreignColonyBridge.receiveMessage(vaa, { gasLimit: 1000000 }); + + await checkErrorRevertEthers(tx.wait(), "colony-bridge-bridged-tx-only-from-colony-bridge"); + + const hash = await foreignColonyNetwork.getReputationRootHash(); + expect(hash).to.not.equal(ethers.utils.hexZeroPad("0xdeadbeef", 32)); + }); + + it("setReputationRootHashFromBridge does not allow transactions to be replayed (if not enforced by bridge)", async () => { + await homeColony.emitDomainReputationReward(1, accounts[0], "0x1337"); + + // Advance mining cycle twice + await forwardTime(MINING_CYCLE_DURATION + CHALLENGE_RESPONSE_WINDOW_DURATION, undefined, web3HomeProvider); + await client.addLogContentsToReputationTree(); + await client.submitRootHash(); + await client.confirmNewHash(); + + await forwardTime(MINING_CYCLE_DURATION + CHALLENGE_RESPONSE_WINDOW_DURATION, undefined, web3HomeProvider); + await client.addLogContentsToReputationTree(); + await client.submitRootHash(); + await client.confirmNewHash(); + + const homeRootHash1 = await homeColonyNetwork.getReputationRootHash(); + + bridgeMonitor.skipCount = 1; + // Bridge root hash + let tx = await homeColonyNetwork.bridgeCurrentRootHash(foreignChainId); + await tx.wait(); + await bridgeMonitor.waitUntilSkipped(); + + const skippedTx = bridgeMonitor.skipped[0]; + + let p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + await bridgeMonitor.bridgeSkipped(); + await p; + + const foreignRootHash1 = await foreignColonyNetwork.getReputationRootHash(); + + expect(homeRootHash1).to.equal(foreignRootHash1); + + // Advance mining cycle twice + await forwardTime(MINING_CYCLE_DURATION + CHALLENGE_RESPONSE_WINDOW_DURATION, undefined, web3HomeProvider); + await client.addLogContentsToReputationTree(); + await client.submitRootHash(); + await client.confirmNewHash(); + + await forwardTime(MINING_CYCLE_DURATION + CHALLENGE_RESPONSE_WINDOW_DURATION, undefined, web3HomeProvider); + await client.addLogContentsToReputationTree(); + await client.submitRootHash(); + await client.confirmNewHash(); + + const homeRootHash2 = await homeColonyNetwork.getReputationRootHash(); + + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + tx = await homeColonyNetwork.bridgeCurrentRootHash(foreignChainId); + await tx.wait(); + await p; + + const foreignRootHash2 = await foreignColonyNetwork.getReputationRootHash(); + expect(foreignRootHash2).to.equal(homeRootHash2); + + // Try and replay + bridgeMonitor.skipped = [skippedTx]; + + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + await bridgeMonitor.bridgeSkipped(); + const bridgingTx = await p; + await checkErrorRevertEthers(bridgingTx.wait(), "colony-mining-bridge-invalid-nonce"); + + // Had no effect + + const foreignRootHash3 = await foreignColonyNetwork.getReputationRootHash(); + + expect(foreignRootHash3).to.equal(foreignRootHash2); + expect(foreignRootHash3).to.not.equal(foreignRootHash1); + }); + + it("addSkillFromBridge can only be called by the colonyBridge contract", async () => { + const skillCountBefore = await homeColonyNetwork.getSkillCount(); + + const [, unknownColonyBridge] = await deployBridge(ethersHomeSigner); + await unknownColonyBridge.setColonyBridgeAddress(foreignChainId, foreignColonyBridge.address); + await unknownColonyBridge.setColonyNetworkAddress(homeColonyNetwork.address); + const vaa = await bridgeMonitor.encodeMockVAA( + foreignColonyBridge.address, + 0, + 0, + homeColonyNetwork.interface.encodeFunctionData("addSkillFromBridge", [1, 2]), + 100, + wormholeForeignChainId, + ); + const tx = await unknownColonyBridge.receiveMessage(vaa, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-network-caller-must-be-colony-bridge"); + + const skillCountAfter = await homeColonyNetwork.getSkillCount(); + expect(skillCountAfter.toHexString()).to.be.equal(skillCountBefore.toHexString()); + }); + + it("addSkillFromBridge reverts if bridged transaction did not originate from colonyNetwork", async () => { + const skillCountBefore = await homeColonyNetwork.getSkillCount(); + + const vaa = await bridgeMonitor.encodeMockVAA( + ADDRESS_ZERO, + 0, + 0, + foreignColonyNetwork.interface.encodeFunctionData("setReputationRootHashFromBridge", [ethers.utils.hexZeroPad("0xdeadbeef", 32), 0, 1]), + 100, + wormholeForeignChainId, + ); + + const tx = await foreignColonyBridge.receiveMessage(vaa, { gasLimit: 1000000 }); + + await checkErrorRevertEthers(tx.wait(), "colony-bridge-bridged-tx-only-from-colony-bridge"); + + const skillCountAfter = await homeColonyNetwork.getSkillCount(); + expect(skillCountAfter.toHexString()).to.be.equal(skillCountBefore.toHexString()); + }); + + it("addSkillFromBridge does not allow transactions to be replayed (if not enforced by bridge)", async () => { + bridgeMonitor.skipCount = 2; + + // Create a skill on foreign chain + let tx = await foreignColony["addDomain(uint256,uint256,uint256)"](1, UINT256_MAX_ETHERS, 1); + await tx.wait(); + + // Create another + tx = await foreignColony["addDomain(uint256,uint256,uint256)"](1, UINT256_MAX_ETHERS, 1); + await tx.wait(); + await bridgeMonitor.waitUntilSkipped(); + const skippedTx1 = bridgeMonitor.skipped[0]; + const skippedTx2 = bridgeMonitor.skipped[1]; + + // Bridge out of order + bridgeMonitor.skipped = [skippedTx2]; + let p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + await bridgeMonitor.bridgeSkipped(); + let bridgingTx = await p; + await bridgingTx.wait(); + + // Replay + bridgeMonitor.skipped = [skippedTx2]; + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + await bridgeMonitor.bridgeSkipped(); + bridgingTx = await p; + await checkErrorRevertEthers(bridgingTx.wait(), "colony-network-skill-already-pending"); + + // Bridge first tx + bridgeMonitor.skipped = [skippedTx1]; + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + await bridgeMonitor.bridgeSkipped(); + bridgingTx = await p; + await bridgingTx.wait(); + + // Replay first tx + bridgeMonitor.skipped = [skippedTx1]; + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + await bridgeMonitor.bridgeSkipped(); + bridgingTx = await p; + await checkErrorRevertEthers(bridgingTx.wait(), "colony-network-skill-already-added"); + }); + + // addReputationUpdateLogFromBridge + it("addReputationUpdateLogFromBridge can only be called by the colonyBridge contract", async () => { + const [, unknownColonyBridge] = await deployBridge(ethersHomeSigner); + await unknownColonyBridge.setColonyNetworkAddress(homeColonyNetwork.address); + await unknownColonyBridge.setColonyBridgeAddress(foreignChainId, foreignColonyBridge.address); + const vaa = await bridgeMonitor.encodeMockVAA( + foreignColonyBridge.address, + 0, + 0, + homeColonyNetwork.interface.encodeFunctionData("addReputationUpdateLogFromBridge", [ADDRESS_ZERO, ADDRESS_ZERO, 0, 0, 0]), + 100, + wormholeForeignChainId, + ); + const tx = await unknownColonyBridge.receiveMessage(vaa, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "colony-network-caller-must-be-colony-bridge"); + + // const tx = await homeColonyNetwork.addReputationUpdateLogFromBridge(ADDRESS_ZERO, ADDRESS_ZERO, 0, 0, 0, { gasLimit: 1000000 }); + // await checkErrorRevertEthers(tx.wait(), "colony-network-not-known-bridge"); + }); + + it("addReputationUpdateLogFromBridge reverts if bridged transaction did not originate from colonyNetwork", async () => { + const vaa = await bridgeMonitor.encodeMockVAA( + ADDRESS_ZERO, + 0, + 0, + foreignColonyNetwork.interface.encodeFunctionData("addReputationUpdateLogFromBridge", [ADDRESS_ZERO, ADDRESS_ZERO, 0, 0, 0]), + 100, + wormholeForeignChainId, + ); + + const tx = await foreignColonyBridge.receiveMessage(vaa, { gasLimit: 1000000 }); + + await checkErrorRevertEthers(tx.wait(), "colony-bridge-bridged-tx-only-from-colony-bridge"); + }); + + it("addReputationUpdateLogFromBridge does not allow transactions to be replayed (if not enforced by bridge)", async () => { + bridgeMonitor.skipCount = 2; + + // Emit reputation on foreign chain + let tx = await foreignColony.emitDomainReputationReward(1, accounts[0], "0x1337"); + await tx.wait(); + + // Emit more reputation + tx = await foreignColony.emitDomainReputationReward(1, accounts[0], "0x1337"); + await tx.wait(); + await bridgeMonitor.waitUntilSkipped(); + const skippedTx1 = bridgeMonitor.skipped[0]; + const skippedTx2 = bridgeMonitor.skipped[1]; + + // Bridge out of order + bridgeMonitor.skipped = [skippedTx2]; + let p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + await bridgeMonitor.bridgeSkipped(); + let bridgingTx = await p; + await bridgingTx.wait(); + + // Replay + bridgeMonitor.skipped = [skippedTx2]; + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + await bridgeMonitor.bridgeSkipped(); + bridgingTx = await p; + await checkErrorRevertEthers(bridgingTx.wait(), "colony-network-update-already-pending"); + + // Bridge first tx + bridgeMonitor.skipped = [skippedTx1]; + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + await bridgeMonitor.bridgeSkipped(); + bridgingTx = await p; + await bridgingTx.wait(); + + // Replay first tx + bridgeMonitor.skipped = [skippedTx1]; + p = bridgeMonitor.getPromiseForNextBridgedTransaction(); + await bridgeMonitor.bridgeSkipped(); + bridgingTx = await p; + await checkErrorRevertEthers(bridgingTx.wait(), "colony-network-update-already-added"); + }); + + it("an invalid VM is respected", async () => { + await homeBridge.setVerifyVMResult(false, "some-good-reason"); + const vaa = await bridgeMonitor.encodeMockVAA( + homeColonyBridge.address, + 0, + 0, + foreignColonyNetwork.interface.encodeFunctionData("setReputationRootHashFromBridge", [ethers.utils.hexZeroPad("0xdeadbeef", 32), 0, 1]), + 100, + wormholeHomeChainId, + ); + const tx = await homeColonyBridge.receiveMessage(vaa, { gasLimit: 1000000 }); + await checkErrorRevertEthers(tx.wait(), "some-good-reason"); + await homeBridge.setVerifyVMResult(true, ""); + }); + }); }); diff --git a/test/extensions/one-tx-payment.js b/test/extensions/one-tx-payment.js index 85c2490c57..805e8796b5 100644 --- a/test/extensions/one-tx-payment.js +++ b/test/extensions/one-tx-payment.js @@ -14,9 +14,10 @@ const { ADMINISTRATION_ROLE, ADDRESS_ZERO, SECONDS_PER_DAY, + CURR_VERSION, } = require("../../helpers/constants"); -const { checkErrorRevert, web3GetCode, rolesToBytes32, expectEvent } = require("../../helpers/test-helper"); +const { checkErrorRevert, web3GetCode, rolesToBytes32, expectEvent, upgradeColonyTo } = require("../../helpers/test-helper"); const { setupRandomColony, fundColonyWithTokens, getMetaTransactionParameters, setupColony } = require("../../helpers/test-data-generator"); const { expect } = chai; @@ -282,8 +283,7 @@ contract("One transaction payments", (accounts) => { // Upgrade to current version await colonyNetworkAsEtherRouter.setResolver(latestResolver); - await metaColony.upgrade(14); - await metaColony.upgrade(15); + await upgradeColonyTo(metaColony, CURR_VERSION); await checkErrorRevert( oneTxPayment.makePaymentFundedFromDomain(1, UINT256_MAX, 1, UINT256_MAX, [USER1], [token.address], [10], 1, globalSkillId), @@ -559,6 +559,8 @@ contract("One transaction payments", (accounts) => { before(async () => { // V5 is `glwss4`, await deployOldExtensionVersion("OneTxPayment", "OneTxPayment", ["OneTxPayment"], "glwss4", colonyNetwork); + // V6 is `hmwss`, + await deployOldExtensionVersion("OneTxPayment", "OneTxPayment", ["OneTxPayment"], "hmwss", colonyNetwork); await deployColonyNetworkVersionGLWSS4(); await deployColonyVersionGLWSS4(colonyNetwork); await deployColonyVersionHMWSS(colonyNetwork); diff --git a/test/extensions/voting-rep.js b/test/extensions/voting-rep.js index 89319de161..da53e22e3f 100644 --- a/test/extensions/voting-rep.js +++ b/test/extensions/voting-rep.js @@ -2789,7 +2789,7 @@ contract("Voting Reputation", (accounts) => { before(async () => { // See if already deployed - const resolverAddress = await colonyNetwork.getExtensionResolver(VOTING_REPUTATION, 9); + let resolverAddress = await colonyNetwork.getExtensionResolver(VOTING_REPUTATION, 9); if (resolverAddress === ADDRESS_ZERO) { // V9 is `glwss4`, @@ -2805,12 +2805,26 @@ contract("Voting Reputation", (accounts) => { // though we can skip any tags where the contract did not change // I don't think there's an elegant way to automate this, as the deployOldExtensionVersion // call might change between versions + + resolverAddress = await colonyNetwork.getExtensionResolver(VOTING_REPUTATION, 10); + + if (resolverAddress === ADDRESS_ZERO) { + // V10 is `hmwss`, + await deployOldExtensionVersion( + "VotingReputation", + "IVotingReputation", + ["VotingReputation,VotingReputationMisalignedRecovery,VotingReputationStaking"], + "hmwss", + colonyNetwork, + ); + } }); // This function as written would also need updating every version, but is infinitely more // upgradeable async function upgradeFromV9ToLatest(colonyInTest) { await colonyInTest.upgradeExtension(VOTING_REPUTATION, 10); + await colonyInTest.upgradeExtension(VOTING_REPUTATION, 11); } beforeEach(async () => { @@ -2854,7 +2868,7 @@ contract("Voting Reputation", (accounts) => { expect(expenditure.globalClaimDelay).to.eq.BN(UINT256_MAX.divn(3)); // V9 behavior await upgradeFromV9ToLatest(colony); - expect(await voting.version()).to.eq.BN(10); + expect(await voting.version()).to.eq.BN(version); await forwardTime(STAKE_PERIOD, this); await voting.finalizeMotion(motionId); @@ -2896,7 +2910,7 @@ contract("Voting Reputation", (accounts) => { expect(slot.claimDelay).to.eq.BN(UINT256_MAX.divn(3)); // V9 behavior await upgradeFromV9ToLatest(colony); - expect(await voting.version()).to.eq.BN(10); + expect(await voting.version()).to.eq.BN(version); await forwardTime(STAKE_PERIOD, this); await voting.finalizeMotion(motionId); @@ -3158,7 +3172,7 @@ contract("Voting Reputation", (accounts) => { const motionId = await voting.getMotionCount(); await upgradeFromV9ToLatest(colony); - expect(await voting.version()).to.eq.BN(10); + expect(await voting.version()).to.eq.BN(version); const motion = await voting.getMotion(motionId); expect(motion.action).to.equal(action); @@ -3173,7 +3187,7 @@ contract("Voting Reputation", (accounts) => { const motionId = await voting.getMotionCount(); await upgradeFromV9ToLatest(colony); - expect(await voting.version()).to.eq.BN(10); + expect(await voting.version()).to.eq.BN(version); const motion = await voting.getMotion(motionId); expect(motion.altTarget).to.equal(oneTxPayment.address); diff --git a/test/reputation-system/client-calculations.js b/test/reputation-system/client-calculations.js index d3243315d2..b5278ef012 100644 --- a/test/reputation-system/client-calculations.js +++ b/test/reputation-system/client-calculations.js @@ -8,7 +8,13 @@ const { ethers } = require("ethers"); const { TruffleLoader } = require("../../packages/package-utils"); const { UINT256_MAX, DEFAULT_STAKE, INITIAL_FUNDING } = require("../../helpers/constants"); -const { advanceMiningCycleNoContest, getActiveRepCycle, finishReputationMiningCycle, removeSubdomainLimit } = require("../../helpers/test-helper"); +const { + advanceMiningCycleNoContest, + getActiveRepCycle, + finishReputationMiningCycle, + removeSubdomainLimit, + getChainId, +} = require("../../helpers/test-helper"); const ReputationMinerTestWrapper = require("../../packages/reputation-miner/test/ReputationMinerTestWrapper"); const { @@ -31,12 +37,20 @@ let colonyNetwork; let metaColony; let clnyToken; let goodClient; +const domainSkills = {}; const realProviderPort = hre.__SOLIDITY_COVERAGE_RUNNING ? 8555 : 8545; const setupNewNetworkInstance = async (MINER1, MINER2) => { colonyNetwork = await setupColonyNetwork(); ({ metaColony, clnyToken } = await setupMetaColonyWithLockedCLNYToken(colonyNetwork)); + await giveUserCLNYTokensAndStake(colonyNetwork, MINER1, DEFAULT_STAKE); + await giveUserCLNYTokensAndStake(colonyNetwork, MINER2, DEFAULT_STAKE); + + const chainId = await getChainId(); + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); + // await colonyNetwork.startNextCycle(); + await removeSubdomainLimit(colonyNetwork); // Temporary for tests until we allow subdomain depth > 1 // Initialise local skill: 3. Set up local skills tree 1 -> 4 -> 5 @@ -47,11 +61,10 @@ const setupNewNetworkInstance = async (MINER1, MINER2) => { // 1 -> M // -> 2 -> 3 - await giveUserCLNYTokensAndStake(colonyNetwork, MINER1, DEFAULT_STAKE); - await giveUserCLNYTokensAndStake(colonyNetwork, MINER2, DEFAULT_STAKE); - await colonyNetwork.initialiseReputationMining(); - await colonyNetwork.startNextCycle(); - + for (let i = 1; i <= 3; i += 1) { + const d = await metaColony.getDomain(i); + domainSkills[i] = d.skillId; + } goodClient = new ReputationMinerTestWrapper({ loader, realProviderPort, useJsTree, minerAddress: MINER1 }); }; @@ -116,22 +129,52 @@ hre.__SOLIDITY_COVERAGE_RUNNING await advanceMiningCycleNoContest({ colonyNetwork, test: this, client: goodClient }); await advanceMiningCycleNoContest({ colonyNetwork, test: this, client: goodClient }); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 6, WORKER)].slice(2, 66), 16)).to.eq.BN(100); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 5, WORKER)].slice(2, 66), 16)).to.eq.BN(100); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 1, WORKER)].slice(2, 66), 16)).to.eq.BN(100); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[3], WORKER)].slice(2, 66), 16), + ).to.eq.BN(100); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[2], WORKER)].slice(2, 66), 16), + ).to.eq.BN(100); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[2], WORKER)].slice(2, 66), 16), + ).to.eq.BN(100); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 6, OTHER)].slice(2, 66), 16)).to.eq.BN(0); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 5, OTHER)].slice(2, 66), 16)).to.eq.BN(900); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 1, OTHER)].slice(2, 66), 16)).to.eq.BN(1900); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[3], OTHER)].slice(2, 66), 16), + ).to.eq.BN(0); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[2], OTHER)].slice(2, 66), 16), + ).to.eq.BN(900); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[1], OTHER)].slice(2, 66), 16), + ).to.eq.BN(1900); expect( - new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 6, ethers.constants.AddressZero)].slice(2, 66), 16), + new BN( + goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[3], ethers.constants.AddressZero)].slice( + 2, + 66, + ), + 16, + ), ).to.eq.BN(100); expect( - new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 5, ethers.constants.AddressZero)].slice(2, 66), 16), + new BN( + goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[2], ethers.constants.AddressZero)].slice( + 2, + 66, + ), + 16, + ), ).to.eq.BN(1000); expect( - new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 1, ethers.constants.AddressZero)].slice(2, 66), 16), + new BN( + goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[1], ethers.constants.AddressZero)].slice( + 2, + 66, + ), + 16, + ), ).to.eq.BN(2000); }); @@ -156,22 +199,52 @@ hre.__SOLIDITY_COVERAGE_RUNNING await advanceMiningCycleNoContest({ colonyNetwork, test: this, client: goodClient }); await advanceMiningCycleNoContest({ colonyNetwork, test: this, client: goodClient }); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 6, WORKER)].slice(2, 66), 16)).to.eq.BN(100); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 5, WORKER)].slice(2, 66), 16)).to.eq.BN(100); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 1, WORKER)].slice(2, 66), 16)).to.eq.BN(100); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[3], WORKER)].slice(2, 66), 16), + ).to.eq.BN(100); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[2], WORKER)].slice(2, 66), 16), + ).to.eq.BN(100); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[1], WORKER)].slice(2, 66), 16), + ).to.eq.BN(100); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 6, OTHER)].slice(2, 66), 16)).to.eq.BN(80); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 5, OTHER)].slice(2, 66), 16)).to.eq.BN(800); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 1, OTHER)].slice(2, 66), 16)).to.eq.BN(800); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[3], OTHER)].slice(2, 66), 16), + ).to.eq.BN(80); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[2], OTHER)].slice(2, 66), 16), + ).to.eq.BN(800); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[1], OTHER)].slice(2, 66), 16), + ).to.eq.BN(800); expect( - new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 6, ethers.constants.AddressZero)].slice(2, 66), 16), + new BN( + goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[3], ethers.constants.AddressZero)].slice( + 2, + 66, + ), + 16, + ), ).to.eq.BN(180); expect( - new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 5, ethers.constants.AddressZero)].slice(2, 66), 16), + new BN( + goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[2], ethers.constants.AddressZero)].slice( + 2, + 66, + ), + 16, + ), ).to.eq.BN(900); expect( - new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 1, ethers.constants.AddressZero)].slice(2, 66), 16), + new BN( + goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[1], ethers.constants.AddressZero)].slice( + 2, + 66, + ), + 16, + ), ).to.eq.BN(900); }); @@ -200,22 +273,52 @@ hre.__SOLIDITY_COVERAGE_RUNNING await advanceMiningCycleNoContest({ colonyNetwork, test: this, client: goodClient }); await advanceMiningCycleNoContest({ colonyNetwork, test: this, client: goodClient }); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 6, WORKER)].slice(2, 66), 16)).to.eq.BN(100); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 5, WORKER)].slice(2, 66), 16)).to.eq.BN(100); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 1, WORKER)].slice(2, 66), 16)).to.eq.BN(100); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[3], WORKER)].slice(2, 66), 16), + ).to.eq.BN(100); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[2], WORKER)].slice(2, 66), 16), + ).to.eq.BN(100); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[1], WORKER)].slice(2, 66), 16), + ).to.eq.BN(100); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 6, OTHER)].slice(2, 66), 16)).to.eq.BN(0); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 5, OTHER)].slice(2, 66), 16)).to.eq.BN(0); - expect(new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 1, OTHER)].slice(2, 66), 16)).to.eq.BN(500); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[3], OTHER)].slice(2, 66), 16), + ).to.eq.BN(0); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[2], OTHER)].slice(2, 66), 16), + ).to.eq.BN(0); + expect( + new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[1], OTHER)].slice(2, 66), 16), + ).to.eq.BN(500); expect( - new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 6, ethers.constants.AddressZero)].slice(2, 66), 16), + new BN( + goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[3], ethers.constants.AddressZero)].slice( + 2, + 66, + ), + 16, + ), ).to.eq.BN(100); expect( - new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 5, ethers.constants.AddressZero)].slice(2, 66), 16), + new BN( + goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[2], ethers.constants.AddressZero)].slice( + 2, + 66, + ), + 16, + ), ).to.eq.BN(100); expect( - new BN(goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, 1, ethers.constants.AddressZero)].slice(2, 66), 16), + new BN( + goodClient.reputations[ReputationMinerTestWrapper.getKey(metaColony.address, domainSkills[1], ethers.constants.AddressZero)].slice( + 2, + 66, + ), + 16, + ), ).to.eq.BN(600); }); }); diff --git a/test/reputation-system/client-core-functionality.js b/test/reputation-system/client-core-functionality.js index 1d74d53923..22f0030a87 100644 --- a/test/reputation-system/client-core-functionality.js +++ b/test/reputation-system/client-core-functionality.js @@ -4,10 +4,11 @@ const path = require("path"); const request = require("async-request"); const chai = require("chai"); const bnChai = require("bn-chai"); +const ethers = require("ethers"); const { TruffleLoader } = require("../../packages/package-utils"); const { DEFAULT_STAKE, INITIAL_FUNDING } = require("../../helpers/constants"); -const { makeReputationKey, advanceMiningCycleNoContest, getActiveRepCycle, TestAdapter } = require("../../helpers/test-helper"); +const { makeReputationKey, advanceMiningCycleNoContest, getActiveRepCycle, TestAdapter, getChainId } = require("../../helpers/test-helper"); const { fundColonyWithTokens, setupColonyNetwork, @@ -33,7 +34,8 @@ hre.__SOLIDITY_COVERAGE_RUNNING ? contract.skip : contract("Reputation mining - client core functionality", (accounts) => { const MINER1 = accounts[5]; - const MINING_SKILL_ID = 3; + + let miningSkillId; let colonyNetwork; let metaColony; @@ -49,13 +51,15 @@ hre.__SOLIDITY_COVERAGE_RUNNING ({ metaColony, clnyToken } = await setupMetaColonyWithLockedCLNYToken(colonyNetwork)); await giveUserCLNYTokensAndStake(colonyNetwork, MINER1, DEFAULT_STAKE); - await colonyNetwork.initialiseReputationMining(); - await colonyNetwork.startNextCycle(); + const chainId = await getChainId(); + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); const lock = await tokenLocking.getUserLock(clnyToken.address, MINER1); expect(lock.balance).to.eq.BN(DEFAULT_STAKE); reputationMiner = new ReputationMinerTestWrapper({ loader, minerAddress: MINER1, realProviderPort, useJsTree: true }); + + miningSkillId = await colonyNetwork.getReputationMiningSkillId(); }); beforeEach(async () => { @@ -86,12 +90,12 @@ hre.__SOLIDITY_COVERAGE_RUNNING it("should correctly respond to a request for a reputation state in the current state", async () => { await client.initialise(colonyNetwork.address, 1); const rootHash = await reputationMiner.getRootHash(); - const url = `http://127.0.0.1:3000/${rootHash}/${metaColony.address}/${MINING_SKILL_ID}/${MINER1}`; + const url = `http://127.0.0.1:3000/${rootHash}/${metaColony.address}/${miningSkillId}/${MINER1}`; const res = await request(url); expect(res.statusCode).to.equal(200); const oracleProofObject = JSON.parse(res.body); - const key = makeReputationKey(metaColony.address, MINING_SKILL_ID, MINER1); + const key = makeReputationKey(metaColony.address, miningSkillId, MINER1); const [branchMask, siblings] = await reputationMiner.getProof(key); const value = reputationMiner.reputations[key]; @@ -114,7 +118,7 @@ hre.__SOLIDITY_COVERAGE_RUNNING await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner, test: this }); const rootHash = await reputationMiner.getRootHash(); - const key = makeReputationKey(metaColony.address, MINING_SKILL_ID, MINER1); + const key = makeReputationKey(metaColony.address, miningSkillId, MINER1); const [branchMask, siblings] = await reputationMiner.getProof(key); const value = reputationMiner.reputations[key]; @@ -130,7 +134,7 @@ hre.__SOLIDITY_COVERAGE_RUNNING await client.initialise(colonyNetwork.address, 1); - let url = `http://127.0.0.1:3000/${rootHash}/${metaColony.address}/${MINING_SKILL_ID}/${MINER1}`; + let url = `http://127.0.0.1:3000/${rootHash}/${metaColony.address}/${miningSkillId}/${MINER1}`; let res = await request(url); expect(res.statusCode).to.equal(200); @@ -149,7 +153,7 @@ hre.__SOLIDITY_COVERAGE_RUNNING await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner, test: this }); // Different URL so we don't hit the cache. - url = `http://127.0.0.1:3000/${rootHash2}/${metaColony.address}/${MINING_SKILL_ID}/${MINER1}`; + url = `http://127.0.0.1:3000/${rootHash2}/${metaColony.address}/${miningSkillId}/${MINER1}`; res = await request(url); expect(res.statusCode).to.equal(200); @@ -165,7 +169,7 @@ hre.__SOLIDITY_COVERAGE_RUNNING expect(value2).to.equal(oracleProofObject.value); // Different URL so we don't hit the cache. - url = `http://127.0.0.1:3000/${rootHash3}/${metaColony.address}/${MINING_SKILL_ID}/${MINER1}`; + url = `http://127.0.0.1:3000/${rootHash3}/${metaColony.address}/${miningSkillId}/${MINER1}`; res = await request(url); expect(res.statusCode).to.equal(200); @@ -188,7 +192,7 @@ hre.__SOLIDITY_COVERAGE_RUNNING await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner, test: this }); const rootHash = await reputationMiner.getRootHash(); - const key = makeReputationKey(metaColony.address, MINING_SKILL_ID, MINER1); + const key = makeReputationKey(metaColony.address, miningSkillId, MINER1); const value = reputationMiner.reputations[key]; await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner, test: this }); @@ -201,7 +205,7 @@ hre.__SOLIDITY_COVERAGE_RUNNING await client.initialise(colonyNetwork.address, 1); - let url = `http://127.0.0.1:3000/${rootHash}/${metaColony.address}/${MINING_SKILL_ID}/${MINER1}/noProof`; + let url = `http://127.0.0.1:3000/${rootHash}/${metaColony.address}/${miningSkillId}/${MINER1}/noProof`; let res = await request(url); expect(res.statusCode).to.equal(200); @@ -213,7 +217,7 @@ hre.__SOLIDITY_COVERAGE_RUNNING expect(value).to.equal(oracleProofObject.value); // Different URL so we don't hit the cache. - url = `http://127.0.0.1:3000/${rootHash2}/${metaColony.address}/${MINING_SKILL_ID}/${MINER1}/noProof`; + url = `http://127.0.0.1:3000/${rootHash2}/${metaColony.address}/${miningSkillId}/${MINER1}/noProof`; res = await request(url); expect(res.statusCode).to.equal(200); @@ -224,7 +228,7 @@ hre.__SOLIDITY_COVERAGE_RUNNING expect(key).to.equal(oracleProofObject.key); expect(value2).to.equal(oracleProofObject.value); - url = `http://127.0.0.1:3000/${rootHash3}/${metaColony.address}/${MINING_SKILL_ID}/${MINER1}/noProof`; + url = `http://127.0.0.1:3000/${rootHash3}/${metaColony.address}/${miningSkillId}/${MINER1}/noProof`; res = await request(url); expect(res.statusCode).to.equal(200); @@ -239,7 +243,7 @@ hre.__SOLIDITY_COVERAGE_RUNNING it("should correctly respond to a request for a valid key in a reputation state that never existed", async () => { await client.initialise(colonyNetwork.address, 1); const rootHash = await reputationMiner.getRootHash(); - const url = `http://127.0.0.1:3000/0x${rootHash.slice(8)}000000/${metaColony.address}/${MINING_SKILL_ID}/${MINER1}`; + const url = `http://127.0.0.1:3000/0x${rootHash.slice(8)}000000/${metaColony.address}/${miningSkillId}/${MINER1}`; const res = await request(url); expect(res.statusCode).to.equal(400); expect(JSON.parse(res.body).message).to.equal("No such reputation state"); @@ -280,9 +284,11 @@ hre.__SOLIDITY_COVERAGE_RUNNING await reputationMiner.saveCurrentState(); await client.initialise(colonyNetwork.address, 1); + const domain1 = await metaColony.getDomain(1); + // Note that we're testing here with one URL with a trailing slash and one without. // Both should work - let url = `http://127.0.0.1:3000/${rootHash}/${metaColony.address}/1`; + let url = `http://127.0.0.1:3000/${rootHash}/${metaColony.address}/${domain1.skillId}`; let res = await request(url); expect(res.statusCode).to.equal(200); let { addresses, reputations } = JSON.parse(res.body); @@ -299,7 +305,7 @@ hre.__SOLIDITY_COVERAGE_RUNNING await advanceMiningCycleNoContest({ colonyNetwork, client: reputationMiner, test: this }); rootHash = await reputationMiner.reputationTree.getRootHash(); await reputationMiner.saveCurrentState(); - url = `http://127.0.0.1:3000/${rootHash}/${metaColony.address}/1/`; + url = `http://127.0.0.1:3000/${rootHash}/${metaColony.address}/${domain1.skillId}/`; res = await request(url); expect(res.statusCode).to.equal(200); diff --git a/test/reputation-system/dispute-resolution-misbehaviour.js b/test/reputation-system/dispute-resolution-misbehaviour.js index 2c8ae2d174..4f4dd5eb4c 100644 --- a/test/reputation-system/dispute-resolution-misbehaviour.js +++ b/test/reputation-system/dispute-resolution-misbehaviour.js @@ -21,6 +21,7 @@ const { finishReputationMiningCycle, removeSubdomainLimit, makeTxAtTimestamp, + getChainId, } = require("../../helpers/test-helper"); const { @@ -69,6 +70,11 @@ const setupNewNetworkInstance = async (MINER1, MINER2) => { colonyNetwork = await setupColonyNetwork(); ({ metaColony, clnyToken } = await setupMetaColonyWithLockedCLNYToken(colonyNetwork)); + await giveUserCLNYTokensAndStake(colonyNetwork, MINER1, DEFAULT_STAKE); + await giveUserCLNYTokensAndStake(colonyNetwork, MINER2, DEFAULT_STAKE); + const chainId = await getChainId(); + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); + await metaColony.addLocalSkill(); localSkillId = await colonyNetwork.getSkillCount(); @@ -79,11 +85,6 @@ const setupNewNetworkInstance = async (MINER1, MINER2) => { await metaColony.addDomain(1, UINT256_MAX, 1); await metaColony.addDomain(1, 1, 2); - await giveUserCLNYTokensAndStake(colonyNetwork, MINER1, DEFAULT_STAKE); - await giveUserCLNYTokensAndStake(colonyNetwork, MINER2, DEFAULT_STAKE); - await colonyNetwork.initialiseReputationMining(); - await colonyNetwork.startNextCycle(); - goodClient = new ReputationMinerTestWrapper({ loader, realProviderPort, useJsTree, minerAddress: MINER1 }); }; diff --git a/test/reputation-system/disputes-over-child-reputation.js b/test/reputation-system/disputes-over-child-reputation.js index d32d7d7214..802f40afbb 100644 --- a/test/reputation-system/disputes-over-child-reputation.js +++ b/test/reputation-system/disputes-over-child-reputation.js @@ -3,6 +3,7 @@ const path = require("path"); const chai = require("chai"); const bnChai = require("bn-chai"); +const ethers = require("ethers"); const { TruffleLoader } = require("../../packages/package-utils"); const { @@ -15,6 +16,7 @@ const { accommodateChallengeAndInvalidateHash, finishReputationMiningCycle, removeSubdomainLimit, + getChainId, } = require("../../helpers/test-helper"); const { @@ -56,6 +58,11 @@ const setupNewNetworkInstance = async (MINER1, MINER2) => { colonyNetwork = await setupColonyNetwork(); ({ metaColony, clnyToken } = await setupMetaColonyWithLockedCLNYToken(colonyNetwork)); + await giveUserCLNYTokensAndStake(colonyNetwork, MINER1, DEFAULT_STAKE); + await giveUserCLNYTokensAndStake(colonyNetwork, MINER2, DEFAULT_STAKE); + const chainId = await getChainId(); + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); + await metaColony.addLocalSkill(); localSkillId = await colonyNetwork.getSkillCount(); @@ -66,11 +73,6 @@ const setupNewNetworkInstance = async (MINER1, MINER2) => { await metaColony.addDomain(1, UINT256_MAX, 1); await metaColony.addDomain(1, 1, 2); - await giveUserCLNYTokensAndStake(colonyNetwork, MINER1, DEFAULT_STAKE); - await giveUserCLNYTokensAndStake(colonyNetwork, MINER2, DEFAULT_STAKE); - await colonyNetwork.initialiseReputationMining(); - await colonyNetwork.startNextCycle(); - goodClient = new ReputationMinerTestWrapper({ loader, realProviderPort, useJsTree, minerAddress: MINER1 }); }; diff --git a/test/reputation-system/happy-paths.js b/test/reputation-system/happy-paths.js index f873c667e5..9b2d9eec12 100644 --- a/test/reputation-system/happy-paths.js +++ b/test/reputation-system/happy-paths.js @@ -19,6 +19,8 @@ const { makeReputationValue, removeSubdomainLimit, checkErrorRevert, + getChainId, + getMultichainSkillId, } = require("../../helpers/test-helper"); const { @@ -42,6 +44,8 @@ const { WORKER_PAYOUT, CHALLENGE_RESPONSE_WINDOW_DURATION, ADDRESS_ZERO, + XDAI_CHAINID, + FORKED_XDAI_CHAINID, } = require("../../helpers/constants"); const ReputationMinerTestWrapper = require("../../packages/reputation-miner/test/ReputationMinerTestWrapper"); @@ -62,6 +66,9 @@ let colonyNetwork; let metaColony; let clnyToken; let localSkillId; +let miningSkillId; +let metaRootSkillId; +let chainId; let goodClient; const realProviderPort = hre.__SOLIDITY_COVERAGE_RUNNING ? 8555 : 8545; @@ -69,9 +76,23 @@ const setupNewNetworkInstance = async (MINER1, MINER2) => { colonyNetwork = await setupColonyNetwork(); ({ metaColony, clnyToken } = await setupMetaColonyWithLockedCLNYToken(colonyNetwork)); + await giveUserCLNYTokensAndStake(colonyNetwork, MINER1, DEFAULT_STAKE); + await giveUserCLNYTokensAndStake(colonyNetwork, MINER2, DEFAULT_STAKE); + chainId = await getChainId(); + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); + await metaColony.addLocalSkill(); localSkillId = await colonyNetwork.getSkillCount(); - expect(localSkillId).to.eq.BN(4); + const miningChainId = parseInt(process.env.MINING_CHAIN_ID, 10) || chainId; + + if (chainId !== miningChainId) { + throw Error("Test suite doesn't support this configuration yet"); + } + if (chainId === XDAI_CHAINID || chainId === FORKED_XDAI_CHAINID) { + expect(localSkillId).to.eq.BN(4); + } else { + expect(localSkillId.shln(128).mod(UINT256_MAX).shrn(128)).to.eq.BN(4); + } // Temporary for tests until we allow subdomain depth > 1 await removeSubdomainLimit(colonyNetwork); @@ -87,12 +108,11 @@ const setupNewNetworkInstance = async (MINER1, MINER2) => { const domainCount = await metaColony.getDomainCount(); const latestDomain = await metaColony.getDomain(domainCount); - expect(latestDomain.skillId).to.eq.BN(11); + expect(latestDomain.skillId).to.eq.BN(getMultichainSkillId(chainId, 11).toString()); - await giveUserCLNYTokensAndStake(colonyNetwork, MINER1, DEFAULT_STAKE); - await giveUserCLNYTokensAndStake(colonyNetwork, MINER2, DEFAULT_STAKE); - await colonyNetwork.initialiseReputationMining(); - await colonyNetwork.startNextCycle(); + miningSkillId = await colonyNetwork.getReputationMiningSkillId(); + const metaDomain1 = await metaColony.getDomain(1); + metaRootSkillId = metaDomain1.skillId; goodClient = new ReputationMinerTestWrapper({ loader, realProviderPort, useJsTree, minerAddress: MINER1 }); }; @@ -105,9 +125,6 @@ contract("Reputation Mining - happy paths", (accounts) => { const MINER1 = accounts[5]; const MINER2 = accounts[6]; - const META_ROOT_SKILL = new BN(1); - const MINING_SKILL = new BN(3); - before(async () => { // Setup a new network instance as we'll be modifying the global skills tree await setupNewNetworkInstance(MINER1, MINER2); @@ -489,44 +506,44 @@ contract("Reputation Mining - happy paths", (accounts) => { const reputationProps = [ // Reputation for the miner - { id: 1, skill: META_ROOT_SKILL, account: undefined, value: META_ROOT_SKILL_TOTAL }, - { id: 2, skill: MINING_SKILL, account: undefined, value: REWARD }, - { id: 3, skill: META_ROOT_SKILL, account: MINER1, value: REWARD }, - { id: 4, skill: MINING_SKILL, account: MINER1, value: REWARD }, + { id: 1, skill: metaRootSkillId, account: undefined, value: META_ROOT_SKILL_TOTAL }, + { id: 2, skill: miningSkillId, account: undefined, value: REWARD }, + { id: 3, skill: metaRootSkillId, account: MINER1, value: REWARD }, + { id: 4, skill: miningSkillId, account: MINER1, value: REWARD }, // Completing 3 claimed expenditures - { id: 5, skill: META_ROOT_SKILL, account: MANAGER, value: MANAGER_PAYOUT.add(EVALUATOR_PAYOUT).muln(3) }, - { id: 6, skill: META_ROOT_SKILL, account: WORKER, value: WORKER_PAYOUT.muln(3) }, + { id: 5, skill: metaRootSkillId, account: MANAGER, value: MANAGER_PAYOUT.add(EVALUATOR_PAYOUT).muln(3) }, + { id: 6, skill: metaRootSkillId, account: WORKER, value: WORKER_PAYOUT.muln(3) }, { id: 7, skill: rootLocalSkillId, account: undefined, value: WORKER_PAYOUT.muln(3) }, { id: 8, skill: localSkillId, account: undefined, value: WORKER_PAYOUT.muln(3) }, { id: 9, skill: rootLocalSkillId, account: WORKER, value: WORKER_PAYOUT.muln(3) }, { id: 10, skill: localSkillId, account: WORKER, value: WORKER_PAYOUT.muln(3) }, // Manual reputation updates, rewards flow up and penalties flow down - { id: 11, skill: new BN(6), account: undefined, value: new BN(0) }, - { id: 12, skill: new BN(7), account: undefined, value: new BN(0) }, - { id: 13, skill: new BN(8), account: undefined, value: new BN(0) }, - { id: 14, skill: new BN(9), account: undefined, value: new BN(0) }, - { id: 15, skill: new BN(10), account: undefined, value: new BN(0) }, - { id: 16, skill: new BN(11), account: undefined, value: new BN(0) }, - { id: 17, skill: new BN(5), account: undefined, value: new BN(1000000000) }, - { id: 18, skill: new BN(6), account: MANAGER, value: new BN(0) }, - { id: 19, skill: new BN(7), account: MANAGER, value: new BN(0) }, - { id: 20, skill: new BN(8), account: MANAGER, value: new BN(0) }, - { id: 21, skill: new BN(9), account: MANAGER, value: new BN(0) }, - { id: 22, skill: new BN(10), account: MANAGER, value: new BN(0) }, - { id: 23, skill: new BN(11), account: MANAGER, value: new BN(0) }, - { id: 24, skill: new BN(5), account: MANAGER, value: new BN(0) }, - { id: 25, skill: META_ROOT_SKILL, account: EVALUATOR, value: new BN(1000000000) }, - { id: 26, skill: new BN(5), account: EVALUATOR, value: new BN(1000000000) }, - { id: 27, skill: new BN(6), account: accounts[3], value: new BN(0) }, - { id: 28, skill: new BN(7), account: accounts[3], value: new BN(0) }, - { id: 29, skill: new BN(8), account: accounts[3], value: new BN(0) }, - { id: 30, skill: new BN(9), account: accounts[3], value: new BN(0) }, - { id: 31, skill: new BN(10), account: accounts[3], value: new BN(0) }, - { id: 32, skill: new BN(11), account: accounts[3], value: new BN(0) }, - { id: 33, skill: META_ROOT_SKILL, account: accounts[3], value: new BN(0) }, - { id: 34, skill: new BN(5), account: accounts[3], value: new BN(0) }, + { id: 11, skill: getMultichainSkillId(chainId, 6), account: undefined, value: new BN(0) }, + { id: 12, skill: getMultichainSkillId(chainId, 7), account: undefined, value: new BN(0) }, + { id: 13, skill: getMultichainSkillId(chainId, 8), account: undefined, value: new BN(0) }, + { id: 14, skill: getMultichainSkillId(chainId, 9), account: undefined, value: new BN(0) }, + { id: 15, skill: getMultichainSkillId(chainId, 10), account: undefined, value: new BN(0) }, + { id: 16, skill: getMultichainSkillId(chainId, 11), account: undefined, value: new BN(0) }, + { id: 17, skill: getMultichainSkillId(chainId, 5), account: undefined, value: new BN(1000000000) }, + { id: 18, skill: getMultichainSkillId(chainId, 6), account: MANAGER, value: new BN(0) }, + { id: 19, skill: getMultichainSkillId(chainId, 7), account: MANAGER, value: new BN(0) }, + { id: 20, skill: getMultichainSkillId(chainId, 8), account: MANAGER, value: new BN(0) }, + { id: 21, skill: getMultichainSkillId(chainId, 9), account: MANAGER, value: new BN(0) }, + { id: 22, skill: getMultichainSkillId(chainId, 10), account: MANAGER, value: new BN(0) }, + { id: 23, skill: getMultichainSkillId(chainId, 11), account: MANAGER, value: new BN(0) }, + { id: 24, skill: getMultichainSkillId(chainId, 5), account: MANAGER, value: new BN(0) }, + { id: 25, skill: metaRootSkillId, account: EVALUATOR, value: new BN(1000000000) }, + { id: 26, skill: getMultichainSkillId(chainId, 5), account: EVALUATOR, value: new BN(1000000000) }, + { id: 27, skill: getMultichainSkillId(chainId, 6), account: accounts[3], value: new BN(0) }, + { id: 28, skill: getMultichainSkillId(chainId, 7), account: accounts[3], value: new BN(0) }, + { id: 29, skill: getMultichainSkillId(chainId, 8), account: accounts[3], value: new BN(0) }, + { id: 30, skill: getMultichainSkillId(chainId, 9), account: accounts[3], value: new BN(0) }, + { id: 31, skill: getMultichainSkillId(chainId, 10), account: accounts[3], value: new BN(0) }, + { id: 32, skill: getMultichainSkillId(chainId, 11), account: accounts[3], value: new BN(0) }, + { id: 33, skill: metaRootSkillId, account: accounts[3], value: new BN(0) }, + { id: 34, skill: getMultichainSkillId(chainId, 5), account: accounts[3], value: new BN(0) }, { id: 35, skill: rootLocalSkillId, account: accounts[3], value: new BN(0) }, { id: 36, skill: localSkillId, account: accounts[3], value: new BN(0) }, ]; @@ -588,26 +605,26 @@ contract("Reputation Mining - happy paths", (accounts) => { const reputationProps = [ // Reputation for the miner - { id: 1, skill: META_ROOT_SKILL, account: undefined, value: META_ROOT_SKILL_TOTAL }, - { id: 2, skill: MINING_SKILL, account: undefined, value: REWARD }, - { id: 3, skill: META_ROOT_SKILL, account: MINER1, value: REWARD }, - { id: 4, skill: MINING_SKILL, account: MINER1, value: REWARD }, + { id: 1, skill: metaRootSkillId, account: undefined, value: META_ROOT_SKILL_TOTAL }, + { id: 2, skill: miningSkillId, account: undefined, value: REWARD }, + { id: 3, skill: metaRootSkillId, account: MINER1, value: REWARD }, + { id: 4, skill: miningSkillId, account: MINER1, value: REWARD }, - { id: 5, skill: META_ROOT_SKILL, account: MANAGER, value: MANAGER_PAYOUT.add(EVALUATOR_PAYOUT).add(new BN(2500000000000)) }, - { id: 6, skill: META_ROOT_SKILL, account: WORKER, value: WORKER_PAYOUT.add(new BN(3300000000000)) }, + { id: 5, skill: metaRootSkillId, account: MANAGER, value: MANAGER_PAYOUT.add(EVALUATOR_PAYOUT).add(new BN(2500000000000)) }, + { id: 6, skill: metaRootSkillId, account: WORKER, value: WORKER_PAYOUT.add(new BN(3300000000000)) }, { id: 7, skill: rootLocalSkillId, account: undefined, value: WORKER_PAYOUT.add(new BN(3300000000000)) }, { id: 8, skill: localSkillId, account: undefined, value: WORKER_PAYOUT.add(new BN(3300000000000)) }, { id: 9, skill: rootLocalSkillId, account: WORKER, value: WORKER_PAYOUT.add(new BN(3300000000000)) }, { id: 10, skill: localSkillId, account: WORKER, value: WORKER_PAYOUT.add(new BN(3300000000000)) }, { id: 11, - skill: new BN(10), + skill: getMultichainSkillId(chainId, 10), account: undefined, value: new BN(1500000000000).add(new BN(7500000000000)).add(new BN(1000000000)).sub(new BN(4200000000000)), }, { id: 12, - skill: new BN(9), + skill: getMultichainSkillId(chainId, 9), account: undefined, value: new BN(1500000000000) .add(new BN(7500000000000)) @@ -618,7 +635,7 @@ contract("Reputation Mining - happy paths", (accounts) => { }, { id: 13, - skill: new BN(8), + skill: getMultichainSkillId(chainId, 8), account: undefined, value: new BN(1500000000000) .add(new BN(7500000000000)) @@ -629,7 +646,7 @@ contract("Reputation Mining - happy paths", (accounts) => { }, { id: 14, - skill: new BN(7), + skill: getMultichainSkillId(chainId, 7), account: undefined, value: new BN(1500000000000) .add(new BN(7500000000000)) @@ -640,7 +657,7 @@ contract("Reputation Mining - happy paths", (accounts) => { }, { id: 15, - skill: new BN(6), + skill: getMultichainSkillId(chainId, 6), account: undefined, value: new BN(1500000000000) .add(new BN(7500000000000)) @@ -651,7 +668,7 @@ contract("Reputation Mining - happy paths", (accounts) => { }, { id: 16, - skill: new BN(5), + skill: getMultichainSkillId(chainId, 5), account: undefined, value: new BN(1500000000000) .add(new BN(7500000000000)) @@ -662,32 +679,32 @@ contract("Reputation Mining - happy paths", (accounts) => { }, { id: 17, - skill: new BN(11), + skill: getMultichainSkillId(chainId, 11), account: undefined, value: new BN(1500000000000).add(new BN(7500000000000)).add(new BN(1000000000)).sub(new BN(4200000000000)), }, - { id: 18, skill: new BN(10), account: MANAGER, value: new BN(1500000000000) }, - { id: 19, skill: new BN(9), account: MANAGER, value: new BN(2500000000000) }, - { id: 20, skill: new BN(8), account: MANAGER, value: new BN(2500000000000) }, - { id: 21, skill: new BN(7), account: MANAGER, value: new BN(2500000000000) }, - { id: 22, skill: new BN(6), account: MANAGER, value: new BN(2500000000000) }, - { id: 23, skill: new BN(5), account: MANAGER, value: new BN(2500000000000) }, - { id: 24, skill: new BN(11), account: MANAGER, value: new BN(1500000000000) }, - { id: 25, skill: new BN(10), account: EVALUATOR, value: new BN(1000000000) }, - { id: 26, skill: new BN(9), account: EVALUATOR, value: new BN(2000000000) }, - { id: 27, skill: new BN(8), account: EVALUATOR, value: new BN(2000000000) }, - { id: 28, skill: new BN(7), account: EVALUATOR, value: new BN(2000000000) }, - { id: 29, skill: new BN(6), account: EVALUATOR, value: new BN(2000000000) }, - { id: 30, skill: new BN(5), account: EVALUATOR, value: new BN(2000000000) }, - { id: 31, skill: META_ROOT_SKILL, account: EVALUATOR, value: new BN(2000000000) }, - { id: 32, skill: new BN(11), account: EVALUATOR, value: new BN(1000000000) }, - { id: 33, skill: new BN(10), account: WORKER, value: new BN(3300000000000) }, - { id: 34, skill: new BN(9), account: WORKER, value: new BN(3300000000000) }, - { id: 35, skill: new BN(8), account: WORKER, value: new BN(3300000000000) }, - { id: 36, skill: new BN(7), account: WORKER, value: new BN(3300000000000) }, - { id: 37, skill: new BN(6), account: WORKER, value: new BN(3300000000000) }, - { id: 38, skill: new BN(5), account: WORKER, value: new BN(3300000000000) }, - { id: 39, skill: new BN(11), account: WORKER, value: new BN(3300000000000) }, + { id: 18, skill: getMultichainSkillId(chainId, 10), account: MANAGER, value: new BN(1500000000000) }, + { id: 19, skill: getMultichainSkillId(chainId, 9), account: MANAGER, value: new BN(2500000000000) }, + { id: 20, skill: getMultichainSkillId(chainId, 8), account: MANAGER, value: new BN(2500000000000) }, + { id: 21, skill: getMultichainSkillId(chainId, 7), account: MANAGER, value: new BN(2500000000000) }, + { id: 22, skill: getMultichainSkillId(chainId, 6), account: MANAGER, value: new BN(2500000000000) }, + { id: 23, skill: getMultichainSkillId(chainId, 5), account: MANAGER, value: new BN(2500000000000) }, + { id: 24, skill: getMultichainSkillId(chainId, 11), account: MANAGER, value: new BN(1500000000000) }, + { id: 25, skill: getMultichainSkillId(chainId, 10), account: EVALUATOR, value: new BN(1000000000) }, + { id: 26, skill: getMultichainSkillId(chainId, 9), account: EVALUATOR, value: new BN(2000000000) }, + { id: 27, skill: getMultichainSkillId(chainId, 8), account: EVALUATOR, value: new BN(2000000000) }, + { id: 28, skill: getMultichainSkillId(chainId, 7), account: EVALUATOR, value: new BN(2000000000) }, + { id: 29, skill: getMultichainSkillId(chainId, 6), account: EVALUATOR, value: new BN(2000000000) }, + { id: 30, skill: getMultichainSkillId(chainId, 5), account: EVALUATOR, value: new BN(2000000000) }, + { id: 31, skill: metaRootSkillId, account: EVALUATOR, value: new BN(2000000000) }, + { id: 32, skill: getMultichainSkillId(chainId, 11), account: EVALUATOR, value: new BN(1000000000) }, + { id: 33, skill: getMultichainSkillId(chainId, 10), account: WORKER, value: new BN(3300000000000) }, + { id: 34, skill: getMultichainSkillId(chainId, 9), account: WORKER, value: new BN(3300000000000) }, + { id: 35, skill: getMultichainSkillId(chainId, 8), account: WORKER, value: new BN(3300000000000) }, + { id: 36, skill: getMultichainSkillId(chainId, 7), account: WORKER, value: new BN(3300000000000) }, + { id: 37, skill: getMultichainSkillId(chainId, 6), account: WORKER, value: new BN(3300000000000) }, + { id: 38, skill: getMultichainSkillId(chainId, 5), account: WORKER, value: new BN(3300000000000) }, + { id: 39, skill: getMultichainSkillId(chainId, 11), account: WORKER, value: new BN(3300000000000) }, ]; expect(Object.keys(goodClient.reputations).length).to.equal(reputationProps.length); @@ -730,41 +747,41 @@ contract("Reputation Mining - happy paths", (accounts) => { const rootLocalSkillId = await metaColony.getRootLocalSkill(); const reputationProps = [ - { id: 1, skillId: META_ROOT_SKILL, account: undefined, value: REWARD.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT).add(WORKER_PAYOUT) }, // eslint-disable-line prettier/prettier - { id: 2, skillId: MINING_SKILL, account: undefined, value: REWARD }, - { id: 3, skillId: META_ROOT_SKILL, account: MINER1, value: REWARD }, - { id: 4, skillId: MINING_SKILL, account: MINER1, value: REWARD }, - { id: 5, skillId: 10, account: undefined, value: WORKER_PAYOUT.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT) }, - { id: 6, skillId: 9, account: undefined, value: WORKER_PAYOUT.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT) }, - { id: 7, skillId: 8, account: undefined, value: WORKER_PAYOUT.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT) }, - { id: 8, skillId: 7, account: undefined, value: WORKER_PAYOUT.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT) }, - { id: 9, skillId: 6, account: undefined, value: WORKER_PAYOUT.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT) }, - { id: 10, skillId: 5, account: undefined, value: WORKER_PAYOUT.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT) }, - { id: 11, skillId: 11, account: undefined, value: WORKER_PAYOUT.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT) }, - { id: 12, skillId: 10, account: MANAGER, value: MANAGER_PAYOUT }, - { id: 13, skillId: 9, account: MANAGER, value: MANAGER_PAYOUT }, - { id: 14, skillId: 8, account: MANAGER, value: MANAGER_PAYOUT }, - { id: 15, skillId: 7, account: MANAGER, value: MANAGER_PAYOUT }, - { id: 16, skillId: 6, account: MANAGER, value: MANAGER_PAYOUT }, - { id: 17, skillId: 5, account: MANAGER, value: MANAGER_PAYOUT }, - { id: 18, skillId: META_ROOT_SKILL, account: MANAGER, value: MANAGER_PAYOUT }, - { id: 19, skillId: 11, account: MANAGER, value: MANAGER_PAYOUT }, - { id: 20, skillId: 10, account: EVALUATOR, value: EVALUATOR_PAYOUT }, - { id: 21, skillId: 9, account: EVALUATOR, value: EVALUATOR_PAYOUT }, - { id: 22, skillId: 8, account: EVALUATOR, value: EVALUATOR_PAYOUT }, - { id: 23, skillId: 7, account: EVALUATOR, value: EVALUATOR_PAYOUT }, - { id: 24, skillId: 6, account: EVALUATOR, value: EVALUATOR_PAYOUT }, - { id: 25, skillId: 5, account: EVALUATOR, value: EVALUATOR_PAYOUT }, - { id: 26, skillId: META_ROOT_SKILL, account: EVALUATOR, value: EVALUATOR_PAYOUT }, - { id: 27, skillId: 11, account: EVALUATOR, value: EVALUATOR_PAYOUT }, - { id: 28, skillId: 10, account: WORKER, value: WORKER_PAYOUT }, - { id: 29, skillId: 9, account: WORKER, value: WORKER_PAYOUT }, - { id: 30, skillId: 8, account: WORKER, value: WORKER_PAYOUT }, - { id: 31, skillId: 7, account: WORKER, value: WORKER_PAYOUT }, - { id: 32, skillId: 6, account: WORKER, value: WORKER_PAYOUT }, - { id: 33, skillId: 5, account: WORKER, value: WORKER_PAYOUT }, - { id: 34, skillId: META_ROOT_SKILL, account: WORKER, value: WORKER_PAYOUT }, - { id: 35, skillId: 11, account: WORKER, value: WORKER_PAYOUT }, + { id: 1, skillId: metaRootSkillId, account: undefined, value: REWARD.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT).add(WORKER_PAYOUT) }, // eslint-disable-line prettier/prettier + { id: 2, skillId: miningSkillId, account: undefined, value: REWARD }, + { id: 3, skillId: metaRootSkillId, account: MINER1, value: REWARD }, + { id: 4, skillId: miningSkillId, account: MINER1, value: REWARD }, + { id: 5, skillId: getMultichainSkillId(chainId, 10), account: undefined, value: WORKER_PAYOUT.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT) }, + { id: 6, skillId: getMultichainSkillId(chainId, 9), account: undefined, value: WORKER_PAYOUT.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT) }, + { id: 7, skillId: getMultichainSkillId(chainId, 8), account: undefined, value: WORKER_PAYOUT.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT) }, + { id: 8, skillId: getMultichainSkillId(chainId, 7), account: undefined, value: WORKER_PAYOUT.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT) }, + { id: 9, skillId: getMultichainSkillId(chainId, 6), account: undefined, value: WORKER_PAYOUT.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT) }, + { id: 10, skillId: getMultichainSkillId(chainId, 5), account: undefined, value: WORKER_PAYOUT.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT) }, + { id: 11, skillId: getMultichainSkillId(chainId, 11), account: undefined, value: WORKER_PAYOUT.add(MANAGER_PAYOUT).add(EVALUATOR_PAYOUT) }, + { id: 12, skillId: getMultichainSkillId(chainId, 10), account: MANAGER, value: MANAGER_PAYOUT }, + { id: 13, skillId: getMultichainSkillId(chainId, 9), account: MANAGER, value: MANAGER_PAYOUT }, + { id: 14, skillId: getMultichainSkillId(chainId, 8), account: MANAGER, value: MANAGER_PAYOUT }, + { id: 15, skillId: getMultichainSkillId(chainId, 7), account: MANAGER, value: MANAGER_PAYOUT }, + { id: 16, skillId: getMultichainSkillId(chainId, 6), account: MANAGER, value: MANAGER_PAYOUT }, + { id: 17, skillId: getMultichainSkillId(chainId, 5), account: MANAGER, value: MANAGER_PAYOUT }, + { id: 18, skillId: metaRootSkillId, account: MANAGER, value: MANAGER_PAYOUT }, + { id: 19, skillId: getMultichainSkillId(chainId, 11), account: MANAGER, value: MANAGER_PAYOUT }, + { id: 20, skillId: getMultichainSkillId(chainId, 10), account: EVALUATOR, value: EVALUATOR_PAYOUT }, + { id: 21, skillId: getMultichainSkillId(chainId, 9), account: EVALUATOR, value: EVALUATOR_PAYOUT }, + { id: 22, skillId: getMultichainSkillId(chainId, 8), account: EVALUATOR, value: EVALUATOR_PAYOUT }, + { id: 23, skillId: getMultichainSkillId(chainId, 7), account: EVALUATOR, value: EVALUATOR_PAYOUT }, + { id: 24, skillId: getMultichainSkillId(chainId, 6), account: EVALUATOR, value: EVALUATOR_PAYOUT }, + { id: 25, skillId: getMultichainSkillId(chainId, 5), account: EVALUATOR, value: EVALUATOR_PAYOUT }, + { id: 26, skillId: metaRootSkillId, account: EVALUATOR, value: EVALUATOR_PAYOUT }, + { id: 27, skillId: getMultichainSkillId(chainId, 11), account: EVALUATOR, value: EVALUATOR_PAYOUT }, + { id: 28, skillId: getMultichainSkillId(chainId, 10), account: WORKER, value: WORKER_PAYOUT }, + { id: 29, skillId: getMultichainSkillId(chainId, 9), account: WORKER, value: WORKER_PAYOUT }, + { id: 30, skillId: getMultichainSkillId(chainId, 8), account: WORKER, value: WORKER_PAYOUT }, + { id: 31, skillId: getMultichainSkillId(chainId, 7), account: WORKER, value: WORKER_PAYOUT }, + { id: 32, skillId: getMultichainSkillId(chainId, 6), account: WORKER, value: WORKER_PAYOUT }, + { id: 33, skillId: getMultichainSkillId(chainId, 5), account: WORKER, value: WORKER_PAYOUT }, + { id: 34, skillId: metaRootSkillId, account: WORKER, value: WORKER_PAYOUT }, + { id: 35, skillId: getMultichainSkillId(chainId, 11), account: WORKER, value: WORKER_PAYOUT }, { id: 36, skillId: rootLocalSkillId, account: undefined, value: WORKER_PAYOUT }, { id: 37, skillId: localSkillId, account: undefined, value: WORKER_PAYOUT }, { id: 38, skillId: rootLocalSkillId, account: WORKER, value: WORKER_PAYOUT }, @@ -774,7 +791,7 @@ contract("Reputation Mining - happy paths", (accounts) => { expect(Object.keys(goodClient.reputations).length).to.equal(reputationProps.length); reputationProps.forEach((reputationProp) => { - const key = makeReputationKey(metaColony.address, new BN(reputationProp.skillId), reputationProp.account); + const key = makeReputationKey(metaColony.address, new BN(reputationProp.skillId.toString()), reputationProp.account); const value = makeReputationValue(reputationProp.value, reputationProp.id); const decimalValue = new BN(goodClient.reputations[key].slice(2, 66), 16); expect(goodClient.reputations[key], `${reputationProp.id} failed. Actual value is ${decimalValue}`).to.eq.BN(value); @@ -820,7 +837,7 @@ contract("Reputation Mining - happy paths", (accounts) => { await repCycle.submitRootHash(newRootHash, 10, "0x00", 10, { from: MINER1 }); await repCycle.confirmNewHash(0, { from: MINER1 }); - const key = makeReputationKey(metaColony.address, MINING_SKILL, MINER1); + const key = makeReputationKey(metaColony.address, miningSkillId, MINER1); const value = goodClient.reputations[key]; const [branchMask, siblings] = await goodClient.getProof(key); // Checking all good parameters confirms a good proof @@ -828,7 +845,7 @@ contract("Reputation Mining - happy paths", (accounts) => { expect(isValid).to.be.true; // Check using a bad key confirms an invalid proof - const badKey = makeReputationKey("0xdeadbeef", MINING_SKILL, MINER1); + const badKey = makeReputationKey("0xdeadbeef", miningSkillId, MINER1); isValid = await metaColony.verifyReputationProof(badKey, value, branchMask, siblings, { from: MINER1 }); expect(isValid).to.be.false; diff --git a/test/reputation-system/reputation-mining-client/client-auto-functionality.js b/test/reputation-system/reputation-mining-client/client-auto-functionality.js index 43f7a2c823..0b16df2fba 100644 --- a/test/reputation-system/reputation-mining-client/client-auto-functionality.js +++ b/test/reputation-system/reputation-mining-client/client-auto-functionality.js @@ -3,6 +3,7 @@ const path = require("path"); const chai = require("chai"); const bnChai = require("bn-chai"); +const ethers = require("ethers"); const { TruffleLoader } = require("../../../packages/package-utils"); @@ -26,6 +27,7 @@ const { sleep, stopMining, startMining, + getChainId, } = require("../../../helpers/test-helper"); const { setupColonyNetwork, @@ -70,8 +72,8 @@ hre.__SOLIDITY_COVERAGE_RUNNING await giveUserCLNYTokensAndStake(colonyNetwork, _MINER1, DEFAULT_STAKE); await giveUserCLNYTokensAndStake(colonyNetwork, _MINER2, DEFAULT_STAKE); await giveUserCLNYTokensAndStake(colonyNetwork, _MINER3, DEFAULT_STAKE); - await colonyNetwork.initialiseReputationMining(); - await colonyNetwork.startNextCycle(); + const chainId = await getChainId(); + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); await advanceMiningCycleNoContest({ colonyNetwork, test: this }); await setupClaimedExpenditure({ colonyNetwork, colony: metaColony }); @@ -858,7 +860,7 @@ hre.__SOLIDITY_COVERAGE_RUNNING await forwardTimeTo(parseInt(goodEntry.lastResponseTimestamp, 10)); await noEventSeen(repCycleEthers, "JustificationRootHashConfirmed"); - await forwardTime(CHALLENGE_RESPONSE_WINDOW_DURATION + 1, this); + await forwardTimeTo(parseInt(goodEntry.lastResponseTimestamp, 10) + CHALLENGE_RESPONSE_WINDOW_DURATION + 1, this); const reputationMinerClient2 = new ReputationMinerClient({ loader, @@ -873,7 +875,10 @@ hre.__SOLIDITY_COVERAGE_RUNNING await goodClientConfirmedJRH; // Now cleanup - + // I think there was an issue here on CI where sometimes, we would happen to be eligible to + // invalidate the bad hash immediately after confirming the JRH due to things being slow and + // using 'forwardTime'. That would cause this event to fire before the promise was set up, and + // the test to fail. I've replace the forwardTimes with forwardTimeTo to try and fix this. const goodClientInvalidateOpponent = new Promise(function (resolve, reject) { repCycleEthers.on("HashInvalidated", async (_hash, _nLeaves, _jrh, event) => { if (_hash === badRootHash && _nLeaves.eq(badNLeaves) && _jrh === badJrh) { @@ -888,7 +893,7 @@ hre.__SOLIDITY_COVERAGE_RUNNING }, 30000); }); - await forwardTime(CHALLENGE_RESPONSE_WINDOW_DURATION + 1, this); + await forwardTimeTo(parseInt(goodEntry.lastResponseTimestamp, 10) + CHALLENGE_RESPONSE_WINDOW_DURATION * 2 + 1, this); // Good client should now realise it can timeout bad submission await goodClientInvalidateOpponent; diff --git a/test/reputation-system/root-hash-submissions.js b/test/reputation-system/root-hash-submissions.js index 3a4f007f27..27744ba668 100644 --- a/test/reputation-system/root-hash-submissions.js +++ b/test/reputation-system/root-hash-submissions.js @@ -7,7 +7,12 @@ const chai = require("chai"); const bnChai = require("bn-chai"); const { TruffleLoader } = require("../../packages/package-utils"); -const { setupColonyNetwork, setupMetaColonyWithLockedCLNYToken, giveUserCLNYTokensAndStake } = require("../../helpers/test-data-generator"); +const { + setupColonyNetwork, + setupMetaColonyWithLockedCLNYToken, + giveUserCLNYTokensAndStake, + giveUserCLNYTokens, +} = require("../../helpers/test-data-generator"); const { MINING_CYCLE_DURATION, @@ -34,6 +39,7 @@ const { currentBlock, currentBlockTime, makeReputationKey, + getChainId, } = require("../../helpers/test-helper"); const ReputationMinerTestWrapper = require("../../packages/reputation-miner/test/ReputationMinerTestWrapper"); @@ -71,8 +77,8 @@ const setupNewNetworkInstance = async (MINER1, MINER2, MINER3, MINER4) => { await giveUserCLNYTokensAndStake(colonyNetwork, MINER2, DEFAULT_STAKE); await giveUserCLNYTokensAndStake(colonyNetwork, MINER3, DEFAULT_STAKE); await giveUserCLNYTokensAndStake(colonyNetwork, MINER4, DEFAULT_STAKE); - await colonyNetwork.initialiseReputationMining(); - await colonyNetwork.startNextCycle(); + const chainId = await getChainId(); + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); goodClient = new ReputationMinerTestWrapper({ loader, minerAddress: MINER1, realProviderPort, useJsTree }); // Mess up the second calculation. There will always be one if giveUserCLNYTokens has been called. @@ -214,9 +220,13 @@ contract("Reputation mining - root hash submissions", (accounts) => { }); it("should allow a user to back the same hash more than once in a same cycle with different entries, and be rewarded", async () => { - const miningSkillId = 3; + const miningSkillId = await colonyNetwork.getReputationMiningSkillId(); await metaColony.setReputationMiningCycleReward(WAD.muln(10)); + + // Need tokens to pay out rewards + await giveUserCLNYTokens(colonyNetwork, colonyNetwork.address, WAD.muln(100)); + const repCycle = await getActiveRepCycle(colonyNetwork); await forwardTime(MINING_CYCLE_DURATION / 2, this); @@ -693,7 +703,10 @@ contract("Reputation mining - root hash submissions", (accounts) => { }); it("should reward all stakers if they submitted the agreed new hash", async () => { - const miningSkillId = 3; + const miningSkillId = await colonyNetwork.getReputationMiningSkillId(); + + // Need tokens to pay out rewards + await giveUserCLNYTokens(colonyNetwork, colonyNetwork.address, WAD.muln(100)); await metaColony.setReputationMiningCycleReward(WAD.muln(10)); await advanceMiningCycleNoContest({ colonyNetwork, test: this }); @@ -769,6 +782,9 @@ contract("Reputation mining - root hash submissions", (accounts) => { }); it("should be able to complete a cycle and claim rewards even if CLNY has been locked", async () => { + // Need tokens to pay out rewards + await giveUserCLNYTokens(colonyNetwork, colonyNetwork.address, WAD.muln(100)); + await metaColony.setReputationMiningCycleReward(WAD.muln(10)); await metaColony.mintTokens(WAD); await metaColony.claimColonyFunds(clnyToken.address); @@ -794,7 +810,6 @@ contract("Reputation mining - root hash submissions", (accounts) => { const colonyWideReputationKey = makeReputationKey(metaColony.address, rootDomainSkill); const { key, value, branchMask, siblings } = await goodClient.getReputationProofObject(colonyWideReputationKey); const colonyWideReputationProof = [key, value, branchMask, siblings]; - await metaColony.startNextRewardPayout(clnyToken.address, ...colonyWideReputationProof); await goodClient.saveCurrentState(); diff --git a/test/reputation-system/types-of-disagreement.js b/test/reputation-system/types-of-disagreement.js index 8f3108776e..6c47d15a55 100644 --- a/test/reputation-system/types-of-disagreement.js +++ b/test/reputation-system/types-of-disagreement.js @@ -5,6 +5,7 @@ const BN = require("bn.js"); const { toBN } = require("web3-utils"); const chai = require("chai"); const bnChai = require("bn-chai"); +const ethers = require("ethers"); const { TruffleLoader } = require("../../packages/package-utils"); const { @@ -17,6 +18,7 @@ const { advanceMiningCycleNoContest, accommodateChallengeAndInvalidateHash, finishReputationMiningCycle, + getChainId, } = require("../../helpers/test-helper"); const { @@ -65,8 +67,8 @@ const setupNewNetworkInstance = async (MINER1, MINER2) => { await giveUserCLNYTokensAndStake(colonyNetwork, MINER1, DEFAULT_STAKE); await giveUserCLNYTokensAndStake(colonyNetwork, MINER2, DEFAULT_STAKE); - await colonyNetwork.initialiseReputationMining(); - await colonyNetwork.startNextCycle(); + const chainId = await getChainId(); + await metaColony.initialiseReputationMining(chainId, ethers.constants.HashZero, 0); goodClient = new ReputationMinerTestWrapper({ loader, realProviderPort, useJsTree, minerAddress: MINER1 }); }; diff --git a/test/truffle-fixture.js b/test/truffle-fixture.js index 5f4f734a72..c42464b14a 100644 --- a/test/truffle-fixture.js +++ b/test/truffle-fixture.js @@ -60,6 +60,7 @@ artifacts.require("ReputationMiningCycle"); const { writeFileSync } = require("fs"); const path = require("path"); const assert = require("assert"); +const ethers = require("ethers"); const { soliditySha3 } = require("web3-utils"); const { @@ -70,6 +71,8 @@ const { setupENSRegistrar, setupEtherRouter, } = require("../helpers/upgradable-contracts"); +const { FORKED_XDAI_CHAINID, XDAI_CHAINID, UINT256_MAX } = require("../helpers/constants"); +const { getChainId } = require("../helpers/test-helper"); module.exports = async () => { await deployContracts(); @@ -316,12 +319,22 @@ async function setupMetaColony() { const v4responder = await Version4.new(); await resolver4.register("version()", v4responder.address); await metaColony.addNetworkColonyVersion(4, resolver4.address); + const chainId = await getChainId(); + const miningChainId = parseInt(process.env.MINING_CHAIN_ID, 10) || chainId; - await colonyNetwork.initialiseReputationMining(); - await colonyNetwork.startNextCycle(); + await metaColony.initialiseReputationMining(miningChainId, ethers.constants.HashZero, 0); + // await colonyNetwork.startNextCycle(); const skillCount = await colonyNetwork.getSkillCount(); - assert.equal(skillCount.toNumber(), 3); // Root domain, root local skill, mining skill + if (chainId === miningChainId) { + if (chainId === XDAI_CHAINID || chainId === FORKED_XDAI_CHAINID) { + assert.equal(skillCount.toNumber(), 3); // Root domain, root local skill, mining skill + } else { + assert.equal(skillCount.shln(128).mod(UINT256_MAX).shrn(128).toNumber(), 3); + } + } else { + assert.equal(skillCount.shln(128).mod(UINT256_MAX).shrn(128).toNumber(), 2); + } } async function setupExtensions() {