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, );