diff --git a/.soliumrc.json b/.soliumrc.json
index 450acbc769..712f0e541b 100644
--- a/.soliumrc.json
+++ b/.soliumrc.json
@@ -12,6 +12,7 @@
"error-reason": "error",
"max-len": "error",
"no-trailing-whitespace": "error",
- "blank-lines": "error"
+ "blank-lines": "error",
+ "custom-errors": "off"
}
}
diff --git a/contracts/bridging/ColonyShell.sol b/contracts/bridging/ColonyShell.sol
new file mode 100644
index 0000000000..fab7c8947c
--- /dev/null
+++ b/contracts/bridging/ColonyShell.sol
@@ -0,0 +1,65 @@
+// 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;
+
+import { BasicMetaTransaction } from "./../common/BasicMetaTransaction.sol";
+import { CallWithGuards } from "../common/CallWithGuards.sol";
+import { DSAuth } from "./../../lib/dappsys/auth.sol";
+import { ERC20Extended } from "./../common/ERC20Extended.sol";
+import { Multicall } from "./../common/Multicall.sol";
+import { IColonyNetwork } from "./../colonyNetwork/IColonyNetwork.sol";
+
+contract ColonyShell is DSAuth, BasicMetaTransaction, Multicall, CallWithGuards {
+ // Address of the Resolver contract used by EtherRouter for lookups and routing
+ address resolver; // Storage slot 2 (from DSAuth there is authority and owner at storage slots 0 and 1 respectively)
+
+ mapping(address => uint256) metatransactionNonces;
+
+ function getMetatransactionNonce(address _user) public view override returns (uint256 _nonce) {
+ return metatransactionNonces[_user];
+ }
+
+ function incrementMetatransactionNonce(address _user) internal override {
+ metatransactionNonces[_user] += 1;
+ }
+
+ // Events
+
+ event ColonyFundsClaimed(address token, uint256 balance);
+ event TransferMade(address token, address user, uint256 amount);
+
+ // Public functions
+
+ function claimColonyShellFunds(address _token) public {
+ uint256 balance = (_token == address(0x0))
+ ? address(this).balance
+ : ERC20Extended(_token).balanceOf(address(this));
+
+ IColonyNetwork(owner).sendClaimColonyShellFunds(_token, balance);
+
+ emit ColonyFundsClaimed(_token, balance);
+ }
+
+ function transfer(address _token, address _user, uint256 _amount) public auth {
+ require(ERC20Extended(_token).transfer(_user, _amount), "colony-shell-transfer-failed");
+
+ emit TransferMade(_token, _user, _amount);
+ }
+}
diff --git a/contracts/colony/ColonyFunding.sol b/contracts/colony/ColonyFunding.sol
index a11919fa56..348346f7c7 100755
--- a/contracts/colony/ColonyFunding.sol
+++ b/contracts/colony/ColonyFunding.sol
@@ -106,6 +106,18 @@ contract ColonyFunding is
emit ColonyFundsClaimed(msgSender(), _token, feeToPay, remainder);
}
+ function claimColonyShellFunds(address _token, uint256 _balance) public stoppable {
+ uint256 toClaim = (_balance - nonRewardPotsTotal[_token]) - fundingPots[0].balance[_token];
+ uint256 feeToPay = toClaim / getRewardInverse(); // ignore-swc-110
+ uint256 remainder = toClaim - feeToPay;
+
+ nonRewardPotsTotal[_token] += remainder;
+ fundingPots[1].balance[_token] += remainder;
+ fundingPots[0].balance[_token] += feeToPay;
+
+ emit ColonyFundsClaimed(msgSender(), _token, feeToPay, remainder);
+ }
+
function getNonRewardPotsTotal(address _token) public view returns (uint256) {
return nonRewardPotsTotal[_token];
}
diff --git a/contracts/colony/IColony.sol b/contracts/colony/IColony.sol
index 220006ad94..3a2a585856 100644
--- a/contracts/colony/IColony.sol
+++ b/contracts/colony/IColony.sol
@@ -830,6 +830,12 @@ interface IColony is ColonyDataTypes, IRecovery, IBasicMetaTransaction, IMultica
/// @param _token Address of the token, `0x0` value indicates Ether
function claimColonyFunds(address _token) external;
+ /// @notice Move any funds received by the shell colony in `_token` denomination to the top-level domain pot,
+ /// siphoning off a small amount to the reward pot. If called against a colony's own token, no fee is taken.
+ /// @param _token Address of the token, `0x0` value indicates Ether
+ /// @param _balance Balance of the token held by the shell colony
+ function claimColonyShellFunds(address _token, uint256 _balance) external;
+
/// @notice Get the total amount of tokens `_token` minus amount reserved to be paid to the reputation and token holders as rewards.
/// @param _token Address of the token, `0x0` value indicates Ether
/// @return amount Total amount of tokens in funding pots other than the rewards pot (id 0)
diff --git a/contracts/colonyNetwork/ColonyNetworkDeployer.sol b/contracts/colonyNetwork/ColonyNetworkDeployer.sol
index a88a68d449..7cf0e556c3 100644
--- a/contracts/colonyNetwork/ColonyNetworkDeployer.sol
+++ b/contracts/colonyNetwork/ColonyNetworkDeployer.sol
@@ -129,6 +129,15 @@ contract ColonyNetworkDeployer is ColonyNetworkStorage {
return (address(token), colonyAddress);
}
+ function createColonyShell(bytes32 _salt) public onlyColonyBridge {
+ ICreateX(CREATEX_ADDRESS).deployCreate3AndInit(
+ _salt,
+ type(EtherRouterCreate3).creationCode,
+ abi.encodeWithSignature("setOwner(address)", (address(this))),
+ ICreateX.Values(0, 0)
+ );
+ }
+
/**
* @dev Generates pseudo-randomly a salt value using a diverse selection of block and
* transaction properties.
diff --git a/contracts/colonyNetwork/ColonyNetworkShells.sol b/contracts/colonyNetwork/ColonyNetworkShells.sol
new file mode 100644
index 0000000000..af2b05c956
--- /dev/null
+++ b/contracts/colonyNetwork/ColonyNetworkShells.sol
@@ -0,0 +1,71 @@
+// 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;
+
+import { IColony } from "./../colony/IColony.sol";
+import { Multicall } from "./../common/Multicall.sol";
+import { ColonyNetworkStorage } from "./ColonyNetworkStorage.sol";
+import { ColonyShell } from "./../bridging/ColonyShell.sol";
+
+contract ColonyNetworkShells is ColonyNetworkStorage, Multicall {
+ // To shells
+
+ function sendDeployColonyShell(bytes32 _salt) public calledByColony{
+ bytes memory payload = abi.encodeWithSignature(
+ "deployColonyShell(bytes32)",
+ _salt
+ );
+
+ require(callThroughBridgeWithGuards(payload), "colony-network-shell-deploy-failed");
+ }
+
+ function colonyShellTransfer(address _colony, address _token, address _user, uint256 _amount) public onlyColonyBridge {
+ ColonyShell(_colony).transfer(_token, _user, _amount);
+ }
+
+ function sendColonyShellTransfer(address _token, address _user, uint256 _amount) public calledByColony{
+ bytes memory payload = abi.encodeWithSignature(
+ "colonyShellTransfer(address,address,address,uint256)",
+ msgSender(),
+ _token,
+ _user,
+ _amount
+ );
+
+ require(callThroughBridgeWithGuards(payload), "colony-network-shell-transfer-failed");
+ }
+
+ // From shells
+
+ function claimColonyShellFunds(address _colony, address _token, uint256 _balance) public onlyColonyBridge {
+ IColony(_colony).claimColonyShellFunds(_token, _balance);
+ }
+
+ function sendClaimColonyShellFunds(address _token, uint256 _balance) public calledByColony{
+ bytes memory payload = abi.encodeWithSignature(
+ "claimColonyShellFunds(address,address,uint256)",
+ msgSender(),
+ _token,
+ _balance
+ );
+
+ require(callThroughBridgeWithGuards(payload), "colony-network-shell-claim-failed");
+ }
+}
diff --git a/contracts/colonyNetwork/ColonyNetworkSkills.sol b/contracts/colonyNetwork/ColonyNetworkSkills.sol
index 52473b7990..62ebb41c43 100644
--- a/contracts/colonyNetwork/ColonyNetworkSkills.sol
+++ b/contracts/colonyNetwork/ColonyNetworkSkills.sol
@@ -24,7 +24,7 @@ import "./ColonyNetworkStorage.sol";
import { IColonyBridge } from "./../bridging/IColonyBridge.sol";
import { CallWithGuards } from "../common/CallWithGuards.sol";
-contract ColonyNetworkSkills is ColonyNetworkStorage, Multicall, CallWithGuards {
+contract ColonyNetworkSkills is ColonyNetworkStorage, Multicall {
// Skills
function addSkill(
@@ -502,21 +502,4 @@ contract ColonyNetworkSkills is ColonyNetworkStorage, Multicall, CallWithGuards
}
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 ac640b70e3..a85fbf12b0 100644
--- a/contracts/colonyNetwork/ColonyNetworkStorage.sol
+++ b/contracts/colonyNetwork/ColonyNetworkStorage.sol
@@ -24,11 +24,12 @@ import { CommonStorage } from "./../common/CommonStorage.sol";
import { MultiChain } from "./../common/MultiChain.sol";
import { ERC20Extended } from "./../common/ERC20Extended.sol";
import { ColonyNetworkDataTypes } from "./ColonyNetworkDataTypes.sol";
+import { CallWithGuards } from "../common/CallWithGuards.sol";
// ignore-file-swc-131
// ignore-file-swc-108
-contract ColonyNetworkStorage is ColonyNetworkDataTypes, DSMath, CommonStorage, MultiChain {
+contract ColonyNetworkStorage is ColonyNetworkDataTypes, DSMath, CommonStorage, MultiChain, CallWithGuards {
// 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
@@ -216,4 +217,21 @@ contract ColonyNetworkStorage is ColonyNetworkDataTypes, DSMath, CommonStorage,
}
return reputationMiningChainId;
}
+
+ 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/IColonyNetwork.sol b/contracts/colonyNetwork/IColonyNetwork.sol
index fbe52b8bfc..9b05773741 100644
--- a/contracts/colonyNetwork/IColonyNetwork.sol
+++ b/contracts/colonyNetwork/IColonyNetwork.sol
@@ -626,4 +626,9 @@ interface IColonyNetwork is ColonyNetworkDataTypes, IRecovery, IBasicMetaTransac
/// @param _chainId The chainId the update was bridged from
/// @param _colony The colony being queried
function addPendingReputationUpdate(uint256 _chainId, address _colony) external;
+
+ /// @notice Send the claimFunds transaction from the shell to the colony
+ /// @param _token The token being held by the shell
+ /// @param _balance The shell's current balance of the token
+ function sendClaimColonyShellFunds(address _token, uint256 _balance) external;
}
diff --git a/docs/interfaces/icolony.md b/docs/interfaces/icolony.md
index aee5722207..18b0a9ba12 100644
--- a/docs/interfaces/icolony.md
+++ b/docs/interfaces/icolony.md
@@ -154,6 +154,19 @@ Move any funds received by the colony in `_token` denomination to the top-level
|_token|address|Address of the token, `0x0` value indicates Ether
+### ▸ `claimColonyShellFunds(address _token, uint256 _balance)`
+
+Move any funds received by the shell colony in `_token` denomination to the top-level domain pot, siphoning off a small amount to the reward pot. If called against a colony's own token, no fee is taken.
+
+
+**Parameters**
+
+|Name|Type|Description|
+|---|---|---|
+|_token|address|Address of the token, `0x0` value indicates Ether
+|_balance|uint256|Balance of the token held by the shell colony
+
+
### ▸ `claimExpenditurePayout(uint256 _id, uint256 _slot, address _token)`
Claim the payout for an expenditure slot. Here the network receives a fee from each payout.
diff --git a/docs/interfaces/icolonynetwork.md b/docs/interfaces/icolonynetwork.md
index b803d3ee0c..8e710554c6 100644
--- a/docs/interfaces/icolonynetwork.md
+++ b/docs/interfaces/icolonynetwork.md
@@ -1077,6 +1077,19 @@ Used to track that a user is eligible to claim a reward
|_amount|uint256|The amount of CLNY to be awarded
+### ▸ `sendClaimColonyShellFunds(address _token, uint256 _balance)`
+
+Send the claimFunds transaction from the shell to the colony
+
+
+**Parameters**
+
+|Name|Type|Description|
+|---|---|---|
+|_token|address|The token being held by the shell
+|_balance|uint256|The shell's current balance of the token
+
+
### ▸ `setColonyBridgeAddress(address _bridgeAddress)`
Called to set the address of the colony bridge contract
diff --git a/helpers/upgradable-contracts.js b/helpers/upgradable-contracts.js
index d65c032ddb..e95bec6483 100644
--- a/helpers/upgradable-contracts.js
+++ b/helpers/upgradable-contracts.js
@@ -131,6 +131,7 @@ exports.setupUpgradableColonyNetwork = async function setupUpgradableColonyNetwo
colonyNetworkENS,
colonyNetworkExtensions,
colonyNetworkSkills,
+ colonyNetworkShells,
contractRecovery,
) {
const deployedImplementations = {};
@@ -139,9 +140,9 @@ exports.setupUpgradableColonyNetwork = async function setupUpgradableColonyNetwo
deployedImplementations.ColonyNetworkMining = colonyNetworkMining.address;
deployedImplementations.ColonyNetworkAuction = colonyNetworkAuction.address;
deployedImplementations.ColonyNetworkENS = colonyNetworkENS.address;
- deployedImplementations.ColonyNetworkSkills = colonyNetworkSkills.address;
deployedImplementations.ColonyNetworkExtensions = colonyNetworkExtensions.address;
deployedImplementations.ColonyNetworkSkills = colonyNetworkSkills.address;
+ deployedImplementations.ColonyNetworkShells = colonyNetworkShells.address;
deployedImplementations.ContractRecovery = contractRecovery.address;
await exports.setupEtherRouter("colonyNetwork", "IColonyNetwork", deployedImplementations, resolver);
diff --git a/test/truffle-fixture.js b/test/truffle-fixture.js
index b29f4ff6e1..e36de5f744 100644
--- a/test/truffle-fixture.js
+++ b/test/truffle-fixture.js
@@ -22,6 +22,7 @@ const ColonyNetworkAuction = artifacts.require("ColonyNetworkAuction");
const ColonyNetworkENS = artifacts.require("ColonyNetworkENS");
const ColonyNetworkExtensions = artifacts.require("ColonyNetworkExtensions");
const ColonyNetworkSkills = artifacts.require("ColonyNetworkSkills");
+const ColonyNetworkShells = artifacts.require("ColonyNetworkShells");
const IColonyNetwork = artifacts.require("IColonyNetwork");
const ENSRegistry = artifacts.require("ENSRegistry");
@@ -129,6 +130,9 @@ async function deployContracts() {
const colonyNetworkSkills = await ColonyNetworkSkills.new();
ColonyNetworkSkills.setAsDeployed(colonyNetworkSkills);
+ const colonyNetworkShells = await ColonyNetworkShells.new();
+ ColonyNetworkShells.setAsDeployed(colonyNetworkShells);
+
const reputationMiningCycle = await ReputationMiningCycle.new();
ReputationMiningCycle.setAsDeployed(reputationMiningCycle);
@@ -149,6 +153,7 @@ async function setupColonyNetwork() {
const colonyNetworkENS = await ColonyNetworkENS.deployed();
const colonyNetworkExtensions = await ColonyNetworkExtensions.deployed();
const colonyNetworkSkills = await ColonyNetworkSkills.deployed();
+ const colonyNetworkShells = await ColonyNetworkShells.deployed();
// const etherRouter = await EtherRouter.deployed();
const resolver = await Resolver.deployed();
const contractRecovery = await ContractRecovery.deployed();
@@ -184,6 +189,7 @@ async function setupColonyNetwork() {
colonyNetworkENS,
colonyNetworkExtensions,
colonyNetworkSkills,
+ colonyNetworkShells,
contractRecovery,
);