pragma solidity 0.5.17; import "./Avatar.sol"; import "../globalConstraints/GlobalConstraintInterface.sol"; /** * @title Controller contract * @dev A controller controls the organizations tokens, reputation and avatar. * It is subject to a set of schemes and constraints that determine its behavior. * Each scheme has it own parameters and operation permissions. */ contract Controller { struct Scheme { bytes32 paramsHash; // a hash "configuration" of the scheme bytes4 permissions; // A bitwise flags of permissions, // All 0: Not registered, // 1st bit: Flag if the scheme is registered, // 2nd bit: Scheme can register other schemes // 3rd bit: Scheme can add/remove global constraints // 4th bit: Scheme can upgrade the controller // 5th bit: Scheme can call genericCall on behalf of // the organization avatar } struct GlobalConstraint { address gcAddress; bytes32 params; } struct GlobalConstraintRegister { bool isRegistered; //is registered uint256 index; //index at globalConstraints } mapping(address=>Scheme) public schemes; Avatar public avatar; DAOToken public nativeToken; Reputation public nativeReputation; // newController will point to the new controller after the present controller is upgraded address public newController; // globalConstraintsPre that determine pre conditions for all actions on the controller GlobalConstraint[] public globalConstraintsPre; // globalConstraintsPost that determine post conditions for all actions on the controller GlobalConstraint[] public globalConstraintsPost; // globalConstraintsRegisterPre indicate if a globalConstraints is registered as a pre global constraint mapping(address=>GlobalConstraintRegister) public globalConstraintsRegisterPre; // globalConstraintsRegisterPost indicate if a globalConstraints is registered as a post global constraint mapping(address=>GlobalConstraintRegister) public globalConstraintsRegisterPost; event MintReputation (address indexed _sender, address indexed _to, uint256 _amount); event BurnReputation (address indexed _sender, address indexed _from, uint256 _amount); event MintTokens (address indexed _sender, address indexed _beneficiary, uint256 _amount); event RegisterScheme (address indexed _sender, address indexed _scheme); event UnregisterScheme (address indexed _sender, address indexed _scheme); event UpgradeController(address indexed _oldController, address _newController); event AddGlobalConstraint( address indexed _globalConstraint, bytes32 _params, GlobalConstraintInterface.CallPhase _when); event RemoveGlobalConstraint(address indexed _globalConstraint, uint256 _index, bool _isPre); constructor( Avatar _avatar) public { avatar = _avatar; nativeToken = avatar.nativeToken(); nativeReputation = avatar.nativeReputation(); schemes[msg.sender] = Scheme({paramsHash: bytes32(0), permissions: bytes4(0x0000001F)}); emit RegisterScheme (msg.sender, msg.sender); } // Do not allow mistaken calls: // solhint-disable-next-line payable-fallback function() external { revert(); } // Modifiers: modifier onlyRegisteredScheme() { require(schemes[msg.sender].permissions&bytes4(0x00000001) == bytes4(0x00000001)); _; } modifier onlyRegisteringSchemes() { require(schemes[msg.sender].permissions&bytes4(0x00000002) == bytes4(0x00000002)); _; } modifier onlyGlobalConstraintsScheme() { require(schemes[msg.sender].permissions&bytes4(0x00000004) == bytes4(0x00000004)); _; } modifier onlyUpgradingScheme() { require(schemes[msg.sender].permissions&bytes4(0x00000008) == bytes4(0x00000008)); _; } modifier onlyGenericCallScheme() { require(schemes[msg.sender].permissions&bytes4(0x00000010) == bytes4(0x00000010)); _; } modifier onlyMetaDataScheme() { require(schemes[msg.sender].permissions&bytes4(0x00000010) == bytes4(0x00000010)); _; } modifier onlySubjectToConstraint(bytes32 func) { uint256 idx; for (idx = 0; idx < globalConstraintsPre.length; idx++) { require( (GlobalConstraintInterface(globalConstraintsPre[idx].gcAddress)) .pre(msg.sender, globalConstraintsPre[idx].params, func)); } _; for (idx = 0; idx < globalConstraintsPost.length; idx++) { require( (GlobalConstraintInterface(globalConstraintsPost[idx].gcAddress)) .post(msg.sender, globalConstraintsPost[idx].params, func)); } } modifier isAvatarValid(address _avatar) { require(_avatar == address(avatar)); _; } /** * @dev Mint `_amount` of reputation that are assigned to `_to` . * @param _amount amount of reputation to mint * @param _to beneficiary address * @return bool which represents a success */ function mintReputation(uint256 _amount, address _to, address _avatar) external onlyRegisteredScheme onlySubjectToConstraint("mintReputation") isAvatarValid(_avatar) returns(bool) { emit MintReputation(msg.sender, _to, _amount); return nativeReputation.mint(_to, _amount); } /** * @dev Burns `_amount` of reputation from `_from` * @param _amount amount of reputation to burn * @param _from The address that will lose the reputation * @return bool which represents a success */ function burnReputation(uint256 _amount, address _from, address _avatar) external onlyRegisteredScheme onlySubjectToConstraint("burnReputation") isAvatarValid(_avatar) returns(bool) { emit BurnReputation(msg.sender, _from, _amount); return nativeReputation.burn(_from, _amount); } /** * @dev mint tokens . * @param _amount amount of token to mint * @param _beneficiary beneficiary address * @return bool which represents a success */ function mintTokens(uint256 _amount, address _beneficiary, address _avatar) external onlyRegisteredScheme onlySubjectToConstraint("mintTokens") isAvatarValid(_avatar) returns(bool) { emit MintTokens(msg.sender, _beneficiary, _amount); return nativeToken.mint(_beneficiary, _amount); } /** * @dev register a scheme * @param _scheme the address of the scheme * @param _paramsHash a hashed configuration of the usage of the scheme * @param _permissions the permissions the new scheme will have * @return bool which represents a success */ function registerScheme(address _scheme, bytes32 _paramsHash, bytes4 _permissions, address _avatar) external onlyRegisteringSchemes onlySubjectToConstraint("registerScheme") isAvatarValid(_avatar) returns(bool) { Scheme memory scheme = schemes[_scheme]; // Check scheme has at least the permissions it is changing, and at least the current permissions: // Implementation is a bit messy. One must recall logic-circuits ^^ // produces non-zero if sender does not have all of the perms that are changing between old and new require(bytes4(0x0000001f)&(_permissions^scheme.permissions)&(~schemes[msg.sender].permissions) == bytes4(0)); // produces non-zero if sender does not have all of the perms in the old scheme require(bytes4(0x0000001f)&(scheme.permissions&(~schemes[msg.sender].permissions)) == bytes4(0)); // Add or change the scheme: schemes[_scheme].paramsHash = _paramsHash; schemes[_scheme].permissions = _permissions|bytes4(0x00000001); emit RegisterScheme(msg.sender, _scheme); return true; } /** * @dev unregister a scheme * @param _scheme the address of the scheme * @return bool which represents a success */ function unregisterScheme( address _scheme, address _avatar) external onlyRegisteringSchemes onlySubjectToConstraint("unregisterScheme") isAvatarValid(_avatar) returns(bool) { //check if the scheme is registered if (_isSchemeRegistered(_scheme) == false) { return false; } // Check the unregistering scheme has enough permissions: require(bytes4(0x0000001f)&(schemes[_scheme].permissions&(~schemes[msg.sender].permissions)) == bytes4(0)); // Unregister: emit UnregisterScheme(msg.sender, _scheme); delete schemes[_scheme]; return true; } /** * @dev unregister the caller's scheme * @return bool which represents a success */ function unregisterSelf(address _avatar) external isAvatarValid(_avatar) returns(bool) { if (_isSchemeRegistered(msg.sender) == false) { return false; } delete schemes[msg.sender]; emit UnregisterScheme(msg.sender, msg.sender); return true; } /** * @dev add or update Global Constraint * @param _globalConstraint the address of the global constraint to be added. * @param _params the constraint parameters hash. * @return bool which represents a success */ function addGlobalConstraint(address _globalConstraint, bytes32 _params, address _avatar) external onlyGlobalConstraintsScheme isAvatarValid(_avatar) returns(bool) { GlobalConstraintInterface.CallPhase when = GlobalConstraintInterface(_globalConstraint).when(); if ((when == GlobalConstraintInterface.CallPhase.Pre)|| (when == GlobalConstraintInterface.CallPhase.PreAndPost)) { if (!globalConstraintsRegisterPre[_globalConstraint].isRegistered) { globalConstraintsPre.push(GlobalConstraint(_globalConstraint, _params)); globalConstraintsRegisterPre[_globalConstraint] = GlobalConstraintRegister(true, globalConstraintsPre.length-1); }else { globalConstraintsPre[globalConstraintsRegisterPre[_globalConstraint].index].params = _params; } } if ((when == GlobalConstraintInterface.CallPhase.Post)|| (when == GlobalConstraintInterface.CallPhase.PreAndPost)) { if (!globalConstraintsRegisterPost[_globalConstraint].isRegistered) { globalConstraintsPost.push(GlobalConstraint(_globalConstraint, _params)); globalConstraintsRegisterPost[_globalConstraint] = GlobalConstraintRegister(true, globalConstraintsPost.length-1); }else { globalConstraintsPost[globalConstraintsRegisterPost[_globalConstraint].index].params = _params; } } emit AddGlobalConstraint(_globalConstraint, _params, when); return true; } /** * @dev remove Global Constraint * @param _globalConstraint the address of the global constraint to be remove. * @return bool which represents a success */ // solhint-disable-next-line code-complexity function removeGlobalConstraint (address _globalConstraint, address _avatar) external onlyGlobalConstraintsScheme isAvatarValid(_avatar) returns(bool) { GlobalConstraintRegister memory globalConstraintRegister; GlobalConstraint memory globalConstraint; GlobalConstraintInterface.CallPhase when = GlobalConstraintInterface(_globalConstraint).when(); bool retVal = false; if ((when == GlobalConstraintInterface.CallPhase.Pre)|| (when == GlobalConstraintInterface.CallPhase.PreAndPost)) { globalConstraintRegister = globalConstraintsRegisterPre[_globalConstraint]; if (globalConstraintRegister.isRegistered) { if (globalConstraintRegister.index < globalConstraintsPre.length-1) { globalConstraint = globalConstraintsPre[globalConstraintsPre.length-1]; globalConstraintsPre[globalConstraintRegister.index] = globalConstraint; globalConstraintsRegisterPre[globalConstraint.gcAddress].index = globalConstraintRegister.index; } globalConstraintsPre.length--; delete globalConstraintsRegisterPre[_globalConstraint]; retVal = true; } } if ((when == GlobalConstraintInterface.CallPhase.Post)|| (when == GlobalConstraintInterface.CallPhase.PreAndPost)) { globalConstraintRegister = globalConstraintsRegisterPost[_globalConstraint]; if (globalConstraintRegister.isRegistered) { if (globalConstraintRegister.index < globalConstraintsPost.length-1) { globalConstraint = globalConstraintsPost[globalConstraintsPost.length-1]; globalConstraintsPost[globalConstraintRegister.index] = globalConstraint; globalConstraintsRegisterPost[globalConstraint.gcAddress].index = globalConstraintRegister.index; } globalConstraintsPost.length--; delete globalConstraintsRegisterPost[_globalConstraint]; retVal = true; } } if (retVal) { emit RemoveGlobalConstraint( _globalConstraint, globalConstraintRegister.index, when == GlobalConstraintInterface.CallPhase.Pre ); } return retVal; } /** * @dev upgrade the Controller * The function will trigger an event 'UpgradeController'. * @param _newController the address of the new controller. * @return bool which represents a success */ function upgradeController(address _newController, Avatar _avatar) external onlyUpgradingScheme isAvatarValid(address(_avatar)) returns(bool) { require(newController == address(0)); // so the upgrade could be done once for a contract. require(_newController != address(0)); newController = _newController; avatar.transferOwnership(_newController); require(avatar.owner() == _newController); if (nativeToken.owner() == address(this)) { nativeToken.transferOwnership(_newController); require(nativeToken.owner() == _newController); } if (nativeReputation.owner() == address(this)) { nativeReputation.transferOwnership(_newController); require(nativeReputation.owner() == _newController); } emit UpgradeController(address(this), newController); return true; } /** * @dev perform a generic call to an arbitrary contract * @param _contract the contract's address to call * @param _data ABI-encoded contract call to call `_contract` address. * @param _avatar the controller's avatar address * @param _value value (ETH) to transfer with the transaction * @return bool -success * bytes - the return value of the called _contract's function. */ function genericCall(address _contract, bytes calldata _data, Avatar _avatar, uint256 _value) external onlyGenericCallScheme onlySubjectToConstraint("genericCall") isAvatarValid(address(_avatar)) returns (bool, bytes memory) { return avatar.genericCall(_contract, _data, _value); } /** * @dev send some ether * @param _amountInWei the amount of ether (in Wei) to send * @param _to address of the beneficiary * @return bool which represents a success */ function sendEther(uint256 _amountInWei, address payable _to, Avatar _avatar) external onlyRegisteredScheme onlySubjectToConstraint("sendEther") isAvatarValid(address(_avatar)) returns(bool) { return avatar.sendEther(_amountInWei, _to); } /** * @dev send some amount of arbitrary ERC20 Tokens * @param _externalToken the address of the Token Contract * @param _to address of the beneficiary * @param _value the amount of ether (in Wei) to send * @return bool which represents a success */ function externalTokenTransfer(IERC20 _externalToken, address _to, uint256 _value, Avatar _avatar) external onlyRegisteredScheme onlySubjectToConstraint("externalTokenTransfer") isAvatarValid(address(_avatar)) returns(bool) { return avatar.externalTokenTransfer(_externalToken, _to, _value); } /** * @dev transfer token "from" address "to" address * One must to approve the amount of tokens which can be spend from the * "from" account.This can be done using externalTokenApprove. * @param _externalToken the address of the Token Contract * @param _from address of the account to send from * @param _to address of the beneficiary * @param _value the amount of ether (in Wei) to send * @return bool which represents a success */ function externalTokenTransferFrom( IERC20 _externalToken, address _from, address _to, uint256 _value, Avatar _avatar) external onlyRegisteredScheme onlySubjectToConstraint("externalTokenTransferFrom") isAvatarValid(address(_avatar)) returns(bool) { return avatar.externalTokenTransferFrom(_externalToken, _from, _to, _value); } /** * @dev externalTokenApproval approve the spender address to spend a specified amount of tokens * on behalf of msg.sender. * @param _externalToken the address of the Token Contract * @param _spender address * @param _value the amount of ether (in Wei) which the approval is referring to. * @return bool which represents a success */ function externalTokenApproval(IERC20 _externalToken, address _spender, uint256 _value, Avatar _avatar) external onlyRegisteredScheme onlySubjectToConstraint("externalTokenIncreaseApproval") isAvatarValid(address(_avatar)) returns(bool) { return avatar.externalTokenApproval(_externalToken, _spender, _value); } /** * @dev metaData emits an event with a string, should contain the hash of some meta data. * @param _metaData a string representing a hash of the meta data * @param _avatar Avatar * @return bool which represents a success */ function metaData(string calldata _metaData, Avatar _avatar) external onlyMetaDataScheme isAvatarValid(address(_avatar)) returns(bool) { return avatar.metaData(_metaData); } /** * @dev getNativeReputation * @param _avatar the organization avatar. * @return organization native reputation */ function getNativeReputation(address _avatar) external isAvatarValid(_avatar) view returns(address) { return address(nativeReputation); } function isSchemeRegistered(address _scheme, address _avatar) external isAvatarValid(_avatar) view returns(bool) { return _isSchemeRegistered(_scheme); } function getSchemeParameters(address _scheme, address _avatar) external isAvatarValid(_avatar) view returns(bytes32) { return schemes[_scheme].paramsHash; } function getSchemePermissions(address _scheme, address _avatar) external isAvatarValid(_avatar) view returns(bytes4) { return schemes[_scheme].permissions; } function getGlobalConstraintParameters(address _globalConstraint, address) external view returns(bytes32) { GlobalConstraintRegister memory register = globalConstraintsRegisterPre[_globalConstraint]; if (register.isRegistered) { return globalConstraintsPre[register.index].params; } register = globalConstraintsRegisterPost[_globalConstraint]; if (register.isRegistered) { return globalConstraintsPost[register.index].params; } } /** * @dev globalConstraintsCount return the global constraint pre and post count * @return uint256 globalConstraintsPre count. * @return uint256 globalConstraintsPost count. */ function globalConstraintsCount(address _avatar) external isAvatarValid(_avatar) view returns(uint, uint) { return (globalConstraintsPre.length, globalConstraintsPost.length); } function isGlobalConstraintRegistered(address _globalConstraint, address _avatar) external isAvatarValid(_avatar) view returns(bool) { return (globalConstraintsRegisterPre[_globalConstraint].isRegistered || globalConstraintsRegisterPost[_globalConstraint].isRegistered); } function _isSchemeRegistered(address _scheme) private view returns(bool) { return (schemes[_scheme].permissions&bytes4(0x00000001) != bytes4(0)); } }