Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-chain reputation rebooted #1216

Closed
wants to merge 53 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
334b257
Make upgrade tests work with next version
area Feb 9, 2024
c8137a7
Make upgrade tests work with next version
area Feb 9, 2024
6720816
First commit, to be squashed
area Mar 24, 2023
abeda29
Bridge skills on creation to home chain
area Apr 9, 2023
753a47e
Add functionality to bridge reputation state
area Apr 10, 2023
480be93
Add some more bridging tests
area Apr 13, 2023
c14543a
Low hanging fruit from first (p)review
area Apr 20, 2023
ab443ab
Test reputation decay on troubled bridging
area May 2, 2023
2119eb2
Additional multichain tests
area May 5, 2023
d7828a4
Straighten out bridged skill trees so they match
area May 10, 2023
23de753
First changes following second review
area Jun 9, 2023
647e29a
First changes following second review
area Jun 9, 2023
37a301b
Update relevant Network struct definitions
kronosapiens Jun 12, 2023
d6067e9
Add helper functions, misc refactoring
kronosapiens Jun 12, 2023
eac2c8e
Introduce ColonyNetworkSkills
kronosapiens Jun 13, 2023
651fbac
Make bridging function names consistent
kronosapiens Jun 13, 2023
fb38608
Minor test edits
kronosapiens Jun 13, 2023
199a7ee
Fix chainIds, revert cross-chain setup
area Jun 18, 2023
9414d60
Some tweaks from review, add events
area Jun 21, 2023
e29cc30
Resurrect skipped tests as appropriate
area Jun 22, 2023
74a585b
Minor close to final tweaks
area Jun 22, 2023
6566443
Non-functional tweaks
area Jun 26, 2023
d7da304
Some contract tidying, extra tests for coverage
area Jun 26, 2023
f1e4f9c
Slither updates
area Jun 26, 2023
4b5bea3
Change how bridged transactions are tracked in tests
area Jun 30, 2023
0507a59
Add guards for unsupported large chainIds
area Aug 7, 2023
f755d5d
Add missing awaits to tests
area Aug 7, 2023
c86989d
Meaningless tweaks and correctly error-out in tests
area Aug 8, 2023
2092d7b
Continue making cross-chain tests more robust
area Aug 9, 2023
426f446
Fix flubbed rebase
area Aug 22, 2023
e9968a4
Add example tests that need to pass
area Sep 1, 2023
3a29e18
Add and get all bridging permission tests passing
area Sep 6, 2023
4748003
Pickup post-rebase
area Feb 8, 2024
c6864c4
WIP: Wormhole restructuring
area Feb 16, 2024
e73009f
Make chainIds configurable
area Feb 28, 2024
120ba2f
Low hanging fruit from review
area Feb 28, 2024
a59624a
More updates post-review
area Feb 28, 2024
4b28154
Remove mintTokensForColonyNetwork
area Mar 1, 2024
b92595f
Update version-specific extension tests
area Mar 2, 2024
512165c
Adjust multi-chain logic around mining skill id
area Mar 1, 2024
5e11e58
Add extra output to upgrade scripts
area Mar 6, 2024
90a4739
First pass after new review
area Mar 18, 2024
64e34fa
Finish easy bits from first review plus rebase
area Mar 19, 2024
c25658e
Try to factorise calls to bridge
area Mar 20, 2024
7f794ea
Further pickups from review
area Mar 29, 2024
a90c748
First attempt to add tests with mining on a non-xdai chain
area Mar 31, 2024
9c6f0f1
Move skillId to a string in miner db
area Mar 31, 2024
f528684
More fixes for mining on non-gnosis chain
area Apr 2, 2024
66f3558
MINING_CHAIN_ID needs to be set for cross-chaintests
area Apr 3, 2024
bcc4f78
Fix setup for test:reputation:2:anotherChain
area Apr 4, 2024
8caed67
Temporary workaround for upgrade tests
area Apr 4, 2024
fb0f170
New contracts need new solidity version
area Apr 4, 2024
1790a8e
Slither wants to use hardhat, so update imports
area Apr 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,37 @@ jobs:
- run:
name: "Running storage consistency smoke tests"
command: npm run test:contracts:smoke
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

lint-and-unit-test:
<<: *job_common
steps:
Expand Down Expand Up @@ -388,6 +419,8 @@ workflows:
context: dockerhub-credentials
- reputation-test:
context: dockerhub-credentials
- reputation-test-non-gnosis:
context: dockerhub-credentials
- test-contracts-coverage:
context: dockerhub-credentials
- test-contracts-extensions-coverage:
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 0 additions & 1 deletion .solcover.chainid.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
19 changes: 18 additions & 1 deletion .solcover.crosschain.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
const config = require("./.solcover.js")
const log = console.log;
const { execSync } = require("child_process");
const ethers = require("ethers");

const { FORKED_XDAI_CHAINID } = require("./helpers/constants");

const existingCompileComplete = config.onCompileComplete;

config.istanbulFolder = `./coverage-cross-chain-${process.env.TRUFFLE_HOME ? "home" : "foreign"}`
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.TRUFFLE_FOREIGN)){
chainId = FORKED_XDAI_CHAINID + 1;
} else {
chainId = FORKED_XDAI_CHAINID;
}

config.providerOptions.network_id = chainId;
config.providerOptions._chainId = chainId;
config.providerOptions._chainIdRpc = chainId;

config.istanbulFolder = `./coverage-cross-chain-${JSON.parse(process.env.TRUFFLE_FOREIGN) ? "foreign" : "home"}`

function provisionSafeContracts(){
let output;
Expand Down
9 changes: 6 additions & 3 deletions .solcover.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ const log = console.log;
// Copies pre-built token artifacts to .coverage_artifacts/contracts
function provisionTokenContracts(config){
let output;
const provisionColonyToken = `BUILD_DIR="build-coverage" bash ./scripts/provision-token-contracts.sh`;
const provisionColonyToken = `BUILD_DIR="build-coverage" npm run provision:token:contracts`;

log('Provisioning ColonyToken contracts...')
output = execSync(provisionColonyToken);
log(output.toString())

const provisionSafeContracts = `BUILD_DIR="build-coverage" bash ./scripts/provision-safe-contracts.sh`;
const provisionSafeContracts = `BUILD_DIR="build-coverage" npm run provision:safe:contracts`;

log('Provisioning Safe contracts...')
output = execSync(provisionSafeContracts);
Expand All @@ -33,6 +33,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"},
Expand All @@ -56,5 +59,5 @@ module.exports = {
},
onCompileComplete: provisionTokenContracts,
istanbulFolder: "./coverage-contracts",
modifierWhitelist: ["always"],
modifierWhitelist: ["always", "onlyMiningChain", "onlyNotMiningChain"],
}
52 changes: 52 additions & 0 deletions contracts/bridging/IColonyBridge.sol
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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 getColonyNetworkAddress() 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);
}
135 changes: 135 additions & 0 deletions contracts/bridging/WormholeBridgeForColony.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// 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 <http://www.gnu.org/licenses/>.
*/

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 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 {
kronosapiens marked this conversation as resolved.
Show resolved Hide resolved
wormhole = IWormhole(_wormhole);
}

function setColonyNetworkAddress(address _colonyNetwork) public auth {
colonyNetwork = _colonyNetwork;
}

function getColonyNetworkAddress() public view returns (address) {
return 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 {
kronosapiens marked this conversation as resolved.
Show resolved Hide resolved
// 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;
}
}
}
39 changes: 22 additions & 17 deletions contracts/colony/Colony.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions contracts/colony/ColonyAuthority.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading