diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c5e82d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin \ No newline at end of file diff --git a/CommonUtilities.sol b/CommonUtilities.sol new file mode 100644 index 0000000..111f1b7 --- /dev/null +++ b/CommonUtilities.sol @@ -0,0 +1,92 @@ +pragma solidity ^0.6.0; + +import "./ICommonUtilities.sol"; + +contract CommonUtilities is ICommonUtilities { + + function toString(address _addr) public override pure returns(string memory) { + bytes32 value = bytes32(uint256(_addr)); + bytes memory alphabet = "0123456789abcdef"; + + bytes memory str = new bytes(42); + str[0] = '0'; + str[1] = 'x'; + for (uint i = 0; i < 20; i++) { + str[2+i*2] = alphabet[uint(uint8(value[i + 12] >> 4))]; + str[3+i*2] = alphabet[uint(uint8(value[i + 12] & 0x0f))]; + } + return string(str); + } + + function toString(uint _i) public override pure returns(string memory) { + if (_i == 0) { + return "0"; + } + uint j = _i; + uint len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint k = len - 1; + while (_i != 0) { + bstr[k--] = byte(uint8(48 + _i % 10)); + _i /= 10; + } + return string(bstr); + } + + function toUint256(bytes memory bs) public override pure returns(uint256 x) { + if(bs.length >= 32) { + assembly { + x := mload(add(bs, add(0x20, 0))) + } + } + } + + function toAddress(bytes memory b) public override pure returns (address addr) { + if(b.length > 0) { + assembly { + addr := mload(add(b,20)) + } + } + } + + function compareStrings(string memory a, string memory b) public override pure returns(bool) { + return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); + } + + function getFirstJSONPart(address sourceLocation, uint256 sourceLocationId, address location) public override pure returns(bytes memory) { + return abi.encodePacked( + '"sourceLocation":"', + toString(sourceLocation), + '","sourceLocationId":', + toString(sourceLocationId), + ',"location":"', + toString(location) + ); + } + + function formatReturnAbiParametersArray(string memory m) public override pure returns(string memory) { + bytes memory b = bytes(m); + if(b.length < 2) { + return "[]"; + } + if(b[0] != bytes1("[")) { + return "[]"; + } + if(b[b.length - 1] != bytes1("]")) { + return "[]"; + } + return m; + } + + function toLowerCase(string memory str) public override pure returns(string memory) { + bytes memory bStr = bytes(str); + for (uint i = 0; i < bStr.length; i++) { + bStr[i] = bStr[i] >= 0x41 && bStr[i] <= 0x5A ? bytes1(uint8(bStr[i]) + 0x20) : bStr[i]; + } + return string(bStr); + } +} \ No newline at end of file diff --git a/Functionality.sol b/Functionality.sol new file mode 100644 index 0000000..450e525 --- /dev/null +++ b/Functionality.sol @@ -0,0 +1,16 @@ +pragma solidity ^0.6.0; + +struct Functionality { + string codeName; + address sourceLocation; + uint256 sourceLocationId; + address location; + bool submitable; + string methodSignature; + string returnAbiParametersArray; + bool isInternal; + bool needsSender; + address proposalAddress; + bool active; + uint256 position; +} \ No newline at end of file diff --git a/ICommonUtilities.sol b/ICommonUtilities.sol new file mode 100644 index 0000000..7c86a7a --- /dev/null +++ b/ICommonUtilities.sol @@ -0,0 +1,12 @@ +pragma solidity ^0.6.0; + +interface ICommonUtilities { + function toString(address _addr) external pure returns(string memory); + function toString(uint _i) external pure returns(string memory); + function toUint256(bytes calldata bs) external pure returns(uint256 x); + function toAddress(bytes calldata b) external pure returns (address addr); + function compareStrings(string calldata a, string calldata b) external pure returns(bool); + function getFirstJSONPart(address sourceLocation, uint256 sourceLocationId, address location) external pure returns(bytes memory); + function formatReturnAbiParametersArray(string calldata m) external pure returns(string memory); + function toLowerCase(string calldata str) external pure returns(string memory); +} \ No newline at end of file diff --git a/IERC20.sol b/IERC20.sol new file mode 100644 index 0000000..0ec4e7b --- /dev/null +++ b/IERC20.sol @@ -0,0 +1,13 @@ +pragma solidity ^0.6.0; + +interface IERC20 { + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); +} \ No newline at end of file diff --git a/IMVDFunctionalitiesManager.sol b/IMVDFunctionalitiesManager.sol new file mode 100644 index 0000000..04955e9 --- /dev/null +++ b/IMVDFunctionalitiesManager.sol @@ -0,0 +1,30 @@ +pragma solidity ^0.6.0; + +interface IMVDFunctionalitiesManager { + + function getProxy() external view returns (address); + function setProxy() external; + + function init(address sourceLocation, + uint256 getMinimumBlockNumberSourceLocationId, address getMinimumBlockNumberFunctionalityAddress, + uint256 getEmergencyMinimumBlockNumberSourceLocationId, address getEmergencyMinimumBlockNumberFunctionalityAddress, + uint256 getEmergencySurveyStakingSourceLocationId, address getEmergencySurveyStakingFunctionalityAddress, + uint256 checkVoteResultSourceLocationId, address checkVoteResultFunctionalityAddress) external; + + function addFunctionality(string calldata codeName, address sourceLocation, uint256 sourceLocationId, address location, bool submitable, string calldata methodSignature, string calldata returnAbiParametersArray, bool isInternal, bool needsSender) external; + function addFunctionality(string calldata codeName, address sourceLocation, uint256 sourceLocationId, address location, bool submitable, string calldata methodSignature, string calldata returnAbiParametersArray, bool isInternal, bool needsSender, uint256 position) external; + function removeFunctionality(string calldata codeName) external returns(bool removed, uint256 position); + function isValidFunctionality(address functionality) external view returns(bool); + function isAuthorizedFunctionality(address functionality) external view returns(bool); + function setCallingContext(address location) external returns(bool); + function clearCallingContext() external; + function getFunctionalityData(string calldata codeName) external view returns(address, uint256, string memory); + function hasFunctionality(string calldata codeName) external view returns(bool); + function getFunctionalitiesAmount() external view returns(uint256); + function functionalitiesToJSON() external view returns(string memory functionsJSONArray); + function functionalitiesToJSON(uint256 start, uint256 l) external view returns(string memory functionsJSONArray); + + function preConditionCheck(string calldata codeName, bytes calldata data, uint8 submitable, address sender, uint256 value) external view returns(address location, bytes memory payload); + + function setupFunctionality(address proposalAddress) external; +} \ No newline at end of file diff --git a/IMVDFunctionalityModelsManager.sol b/IMVDFunctionalityModelsManager.sol new file mode 100644 index 0000000..4e0c27d --- /dev/null +++ b/IMVDFunctionalityModelsManager.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.6.0; + +interface IMVDFunctionalityModelsManager { + function init() external; + function checkWellKnownFunctionalities(string calldata codeName, bool submitable, string calldata methodSignature, string calldata returnAbiParametersArray, bool isInternal, bool needsSender, string calldata replaces) external view; +} \ No newline at end of file diff --git a/IMVDFunctionalityProposal.sol b/IMVDFunctionalityProposal.sol new file mode 100644 index 0000000..4c0bc61 --- /dev/null +++ b/IMVDFunctionalityProposal.sol @@ -0,0 +1,48 @@ +pragma solidity ^0.6.0; + +interface IMVDFunctionalityProposal { + + function init(string calldata codeName, address location, string calldata methodSignature, string calldata returnAbiParametersArray, string calldata replaces, address proxy) external; + function setCollateralData(bool emergency, address sourceLocation, uint256 sourceLocationId, bool submitable, bool isInternal, bool needsSender, address proposer) external; + + function getProxy() external view returns(address); + function getCodeName() external view returns(string memory); + function isEmergency() external view returns(bool); + function getSourceLocation() external view returns(address); + function getSourceLocationId() external view returns(uint256); + function getLocation() external view returns(address); + function isSubmitable() external view returns(bool); + function getMethodSignature() external view returns(string memory); + function getReturnAbiParametersArray() external view returns(string memory); + function isInternal() external view returns(bool); + function needsSender() external view returns(bool); + function getReplaces() external view returns(string memory); + function getProposer() external view returns(address); + function getSurveyEndBlock() external view returns(uint256); + function getSurveyDuration() external view returns(uint256); + function toJSON() external view returns(string memory); + function getVote(address addr) external view returns(uint256 accept, uint256 refuse); + function getVotes() external view returns(uint256, uint256); + function start() external; + function disable() external; + function isDisabled() external view returns(bool); + function isTerminated() external view returns(bool); + function accept(uint256 amount) external; + function retireAccept(uint256 amount) external; + function moveToAccept(uint256 amount) external; + function refuse(uint256 amount) external; + function retireRefuse(uint256 amount) external; + function moveToRefuse(uint256 amount) external; + function retireAll() external; + function withdraw() external; + function terminate() external; + function set() external; + + event Accept(address indexed voter, uint256 amount); + event RetireAccept(address indexed voter, uint256 amount); + event MoveToAccept(address indexed voter, uint256 amount); + event Refuse(address indexed voter, uint256 amount); + event RetireRefuse(address indexed voter, uint256 amount); + event MoveToRefuse(address indexed voter, uint256 amount); + event RetireAll(address indexed voter, uint256 amount); +} \ No newline at end of file diff --git a/IMVDFunctionalityProposalManager.sol b/IMVDFunctionalityProposalManager.sol new file mode 100644 index 0000000..614548d --- /dev/null +++ b/IMVDFunctionalityProposalManager.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.6.0; + +interface IMVDFunctionalityProposalManager { + function newProposal(string calldata codeName, address location, string calldata methodSignature, string calldata returnAbiParametersArray, string calldata replaces) external returns(address); + function checkProposal(address proposalAddress) external; + function disableProposal(address proposalAddress) external; + function getProxy() external view returns (address); + function setProxy() external; + function isValidProposal(address proposal) external view returns (bool); + function getPendingProposal(string calldata codeName) external view returns(address proposalAddress, bool isReallyPending); +} \ No newline at end of file diff --git a/IMVDProxy.sol b/IMVDProxy.sol new file mode 100644 index 0000000..4a96804 --- /dev/null +++ b/IMVDProxy.sol @@ -0,0 +1,52 @@ +pragma solidity ^0.6.0; + +interface IMVDProxy { + + function init(address votingTokenAddress, address stateHolderAddress, address functionalityModelsManagerAddress, address functionalityProposalManagerAddress, address functionalitiesManagerAddress) external; + + function getToken() external view returns(address); + function setToken(address newAddress) external; + function getStateHolderAddress() external view returns(address); + function setStateHolderAddress(address newAddress) external; + function getMVDFunctionalityProposalManagerAddress() external view returns(address); + function setMVDFunctionalityProposalManagerAddress(address newAddress) external; + function getMVDFunctionalityModelsManagerAddress() external view returns(address); + function setMVDFunctionalityModelsManagerAddress(address newAddress) external; + function getMVDFunctionalitiesManagerAddress() external view returns(address); + function setMVDFunctionalitiesManagerAddress(address newAddress) external; + function changeProxy(address payable newAddress) external payable; + function getFunctionalitiesAmount() external view returns(uint256); + function isValidProposal(address proposal) external view returns (bool); + function isValidFunctionality(address functionality) external view returns(bool); + function isAuthorizedFunctionality(address functionality) external view returns(bool); + function getFunctionalityAddress(string calldata codeName) external view returns(address); + function hasFunctionality(string calldata codeName) external view returns(bool); + function functionalitiesToJSON() external view returns(string memory functionsJSONArray); + function functionalitiesToJSON(uint256 start, uint256 l) external view returns(string memory functionsJSONArray); + function getPendingProposal(string calldata codeName) external view returns(address proposalAddress, bool isPending); + function newProposal(string calldata codeName, bool emergency, address sourceLocation, uint256 sourceLocationId, address location, bool submitable, string calldata methodSignature, string calldata returnParametersJSONArray, bool isInternal, bool needsSender, string calldata replaces) external returns(address proposalAddress); + function startProposal(address proposalAddress) external; + function disableProposal(address proposalAddress) external; + function transfer(address receiver, uint256 value, address token) external; + function setProposal() external; + function read(string calldata codeName, bytes calldata data) external view returns(bytes memory returnData); + function submit(string calldata codeName, bytes calldata data) external payable returns(bytes memory returnData); + function callFromManager(address location, bytes calldata payload) external returns(bool, bytes memory); + function emitFromManager(string calldata codeName, uint256 position, address proposal, string calldata replaced, address location, bool submitable, string calldata methodSignature, bool isInternal, bool needsSender, address proposalAddress, uint256 replacedPosition) external; + + function emitEvent(string calldata eventSignature, bytes calldata firstIndex, bytes calldata secondIndex, bytes calldata data) external; + + event TokenChanged(address indexed oldAddress, address indexed newAddress); + event MVDFunctionalityProposalManagerChanged(address indexed oldAddress, address indexed newAddress); + event MVDFunctionalityModelsManagerChanged(address indexed oldAddress, address indexed newAddress); + event MVDFunctionalitiesManagerChanged(address indexed oldAddress, address indexed newAddress); + event StateHolderChanged(address indexed oldAddress, address indexed newAddress); + event ProxyChanged(address indexed newAddress); + + event PaymentReceived(address indexed sender, uint256 value); + event Proposal(address proposal); + event ProposalSet(address indexed proposal, bool success); + event FunctionalitySet(string indexed codeName, uint256 position, address proposal, string indexed replaced, address replacedLocation, bool replacedWasSubmitable, string replacedMethodSignature, bool replacedWasInternal, bool replacedNeededSender, address replacedProposal, uint256 replacedPosition); + + event Event(string indexed key, bytes32 indexed firstIndex, bytes32 indexed secondIndex, bytes data); +} \ No newline at end of file diff --git a/IStateHolder.sol b/IStateHolder.sol new file mode 100644 index 0000000..8b4b4f3 --- /dev/null +++ b/IStateHolder.sol @@ -0,0 +1,25 @@ +pragma solidity ^0.6.0; + +interface IStateHolder { + + function init() external; + + function getProxy() external view returns (address); + function setProxy() external; + function getState(uint256 i) external view returns (string memory name, string memory dataType, bytes memory value); + function getInfo(string calldata varName) external view returns (string memory dataType, bytes memory value, uint256 position); + function getStateSize() external view returns (uint256); + function exists(string calldata varName) external view returns(bool); + function getDataType(string calldata varName) external view returns(string memory dataType); + function clear(string calldata varName) external returns(string memory oldDataType, bytes memory oldVal); + function setBytes(string calldata varName, bytes calldata val) external returns(bytes memory); + function getBytes(string calldata varName) external view returns(bytes memory); + function setString(string calldata varName, string calldata val) external returns(string memory); + function getString(string calldata varName) external view returns (string memory); + function setBool(string calldata varName, bool val) external returns(bool); + function getBool(string calldata varName) external view returns (bool); + function getUint256(string calldata varName) external view returns (uint256); + function setUint256(string calldata varName, uint256 val) external returns(uint256); + function getAddress(string calldata varName) external view returns (address); + function setAddress(string calldata varName, address val) external returns (address); +} \ No newline at end of file diff --git a/IVotingToken.sol b/IVotingToken.sol new file mode 100644 index 0000000..1e20a09 --- /dev/null +++ b/IVotingToken.sol @@ -0,0 +1,18 @@ +pragma solidity ^0.6.0; + +interface IVotingToken { + function init(string calldata name, string calldata symbol, uint256 decimals, uint256 totalSupply) external; + + function getProxy() external view returns (address); + function setProxy() external; + + function name() external view returns(string memory); + function symbol() external view returns(string memory); + function decimals() external view returns(uint256); + + function mint(uint256 amount) external; + function burn(uint256 amount) external; + + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e2e7f6c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Robe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MVDFunctionalitiesManager.sol b/MVDFunctionalitiesManager.sol new file mode 100644 index 0000000..ad0a621 --- /dev/null +++ b/MVDFunctionalitiesManager.sol @@ -0,0 +1,311 @@ +pragma solidity ^0.6.0; + +import "./IMVDFunctionalitiesManager.sol"; +import "./CommonUtilities.sol"; +import "./IMVDProxy.sol"; +import "./IMVDFunctionalityProposal.sol"; +import "./Functionality.sol"; + +contract MVDFunctionalitiesManager is IMVDFunctionalitiesManager, CommonUtilities { + + address private _proxy; + + Functionality[] private _functionalities; + + uint256 private _functionalitiesAmount; + + mapping(string => uint256) private _indexes; + + mapping(address => uint256) private _functionalityCount; + + address private _callingContext; + + constructor(address sourceLocation, + uint256 getMinimumBlockNumberSourceLocationId, address getMinimumBlockNumberFunctionalityAddress, + uint256 getEmergencyMinimumBlockNumberSourceLocationId, address getEmergencyMinimumBlockNumberFunctionalityAddress, + uint256 getEmergencySurveyStakingSourceLocationId, address getEmergencySurveyStakingFunctionalityAddress, + uint256 checkVoteResultSourceLocationId, address checkVoteResultFunctionalityAddress) public override { + if(getMinimumBlockNumberFunctionalityAddress == address(0)) { + return; + } + init(sourceLocation, + getMinimumBlockNumberSourceLocationId, getMinimumBlockNumberFunctionalityAddress, + getEmergencyMinimumBlockNumberSourceLocationId, getEmergencyMinimumBlockNumberFunctionalityAddress, + getEmergencySurveyStakingSourceLocationId, getEmergencySurveyStakingFunctionalityAddress, + checkVoteResultSourceLocationId, checkVoteResultFunctionalityAddress); + } + + function init(address sourceLocation, + uint256 getMinimumBlockNumberSourceLocationId, address getMinimumBlockNumberFunctionalityAddress, + uint256 getEmergencyMinimumBlockNumberSourceLocationId, address getEmergencyMinimumBlockNumberFunctionalityAddress, + uint256 getEmergencySurveyStakingSourceLocationId, address getEmergencySurveyStakingFunctionalityAddress, + uint256 checkVoteResultSourceLocationId, address checkVoteResultFunctionalityAddress) public override { + + require(_functionalitiesAmount == 0, "Init already called!"); + + addFunctionality( + "getMinimumBlockNumberForSurvey", + sourceLocation, + getMinimumBlockNumberSourceLocationId, + getMinimumBlockNumberFunctionalityAddress, + false, + "getMinimumBlockNumberForSurvey()", + '["uint256"]', + false, + false + ); + + addFunctionality( + "getMinimumBlockNumberForEmergencySurvey", + sourceLocation, + getEmergencyMinimumBlockNumberSourceLocationId, + getEmergencyMinimumBlockNumberFunctionalityAddress, + false, + "getMinimumBlockNumberForEmergencySurvey()", + '["uint256"]', + false, + false + ); + + addFunctionality( + "getEmergencySurveyStaking", + sourceLocation, + getEmergencySurveyStakingSourceLocationId, + getEmergencySurveyStakingFunctionalityAddress, + false, + "getEmergencySurveyStaking()", + '["uint256"]', + false, + false + ); + + addFunctionality( + "checkSurveyResult", + sourceLocation, + checkVoteResultSourceLocationId, + checkVoteResultFunctionalityAddress, + false, + "checkSurveyResult(address)", + '["bool"]', + false, + false + ); + } + + function addFunctionality(string memory codeName, address sourceLocation, uint256 sourceLocationId, address location, bool submitable, string memory methodSignature, string memory returnAbiParametersArray, bool isInternal, bool needsSender) public override { + addFunctionality(codeName, sourceLocation, sourceLocationId, location, submitable, methodSignature, returnAbiParametersArray, isInternal, needsSender, _functionalities.length); + } + + function addFunctionality(string memory codeName, address sourceLocation, uint256 sourceLocationId, address location, bool submitable, string memory methodSignature, string memory returnAbiParametersArray, bool isInternal, bool needsSender, uint256 position) public override { + require(_proxy == address(0) || _callingContext == msg.sender, "Unauthorized call!"); + Functionality memory functionality = Functionality( + codeName, + sourceLocation, + sourceLocationId, + location, + submitable, + methodSignature, + returnAbiParametersArray, + isInternal, + needsSender, + address(0), + true, + position + ); + if(position >= _functionalities.length) { + _functionalities.push(functionality); + } else { + removeFunctionality(_functionalities[position].codeName); + _functionalities[position] = functionality; + } + _functionalityCount[location] = _functionalityCount[location] + 1; + _functionalitiesAmount++; + _indexes[codeName] = position; + } + + function removeFunctionality(string memory codeName) public override returns(bool removed, uint256 position) { + require(_proxy == address(0) || _callingContext == msg.sender, "Unauthorized call!"); + Functionality storage functionality = _functionalities[_indexes[codeName]]; + position = functionality.position; + if(compareStrings(codeName, functionality.codeName) && functionality.active) { + functionality.active = false; + _functionalityCount[functionality.location] = _functionalityCount[functionality.location] - 1; + _functionalitiesAmount--; + removed = true; + } + } + + function preConditionCheck(string memory codeName, bytes memory data, uint8 submitable, address sender, uint256 value) public override view returns(address location, bytes memory payload) { + Functionality memory functionality = _functionalities[_indexes[codeName]]; + + require(compareStrings(codeName, functionality.codeName) && functionality.active, "Unauthorized functionality"); + + require(submitable == (functionality.submitable ? 1 : 0), "Functionality called in the wrong context!"); + + require(functionality.isInternal ? _functionalityCount[sender] > 0 || _callingContext == sender : true, "Internal functionalities can be called from other functionalities only!"); + + location = functionality.location; + + if(functionality.needsSender) { + require(data.length >= (submitable == 1 ? 64 : 32), "Insufficient space in data payload"); + assembly { + mstore(add(data, 0x20), sender) + switch iszero(submitable) case 0 { + mstore(add(data, 0x40), value) + } + } + } + + payload = abi.encodePacked(bytes4(keccak256(bytes(functionality.methodSignature))), data); + } + + function getFunctionalitiesAmount() public override view returns(uint256) { + return _functionalitiesAmount; + } + + function isValidFunctionality(address functionality) public override view returns(bool) { + return _functionalityCount[functionality] > 0; + } + + function isAuthorizedFunctionality(address functionality) public override view returns(bool) { + return _callingContext != address(0) && (_functionalityCount[functionality] > 0 || _callingContext == functionality); + } + + function getFunctionalityData(string memory codeName) public override view returns(address, uint256, string memory) { + Functionality memory functionality = _functionalities[_indexes[codeName]]; + return (compareStrings(codeName, functionality.codeName) && functionality.active ? functionality.location : address(0), functionality.position, functionality.methodSignature); + } + + function hasFunctionality(string memory codeName) public override view returns(bool) { + Functionality memory functionality = _functionalities[_indexes[codeName]]; + return compareStrings(codeName, functionality.codeName) && functionality.active; + } + + function functionalitiesToJSON() public override view returns(string memory functionsJSONArray) { + return functionalitiesToJSON(0, _functionalities.length); + } + + function functionalitiesToJSON(uint256 start, uint256 l) public override view returns(string memory functionsJSONArray) { + uint256 length = start + l; + functionsJSONArray = "["; + for(uint256 i = start; i < length; i++) { + functionsJSONArray = !_functionalities[i].active ? functionsJSONArray : string(abi.encodePacked(functionsJSONArray, toJSON(_functionalities[i]), i == length - (_functionalities[i].active ? 1 : 0) ? "]" : ",")); + length += _functionalities[i].active ? 0 : 1; + length = length > _functionalities.length ? _functionalities.length : length; + } + } + + function toJSON(Functionality memory func) private pure returns(bytes memory) { + return abi.encodePacked( + '{', + getFirstJSONPart(func.sourceLocation, func.sourceLocationId, func.location), + '","submitable":', + func.submitable ? "true" : "false", + ',"isInternal":', + func.isInternal ? "true" : "false", + ',"needsSender":', + func.needsSender ? "true" : "false", + ',"proposalAddress":"', + toString(func.proposalAddress), + '","codeName":"', + func.codeName, + '","methodSignature":"', + func.methodSignature, + '","returnAbiParametersArray":', + formatReturnAbiParametersArray(func.returnAbiParametersArray), + ',"position":', + toString(func.position), + '}' + ); + } + + function getProxy() public override view returns(address) { + return _proxy; + } + + function setProxy() public override { + require(_functionalitiesAmount != 0, "Init not called!"); + require(_proxy == address(0) || _proxy == msg.sender, _proxy != address(0) ? "Proxy already set!" : "Only Proxy can toggle itself!"); + _proxy = _proxy == address(0) ? msg.sender : address(0); + } + + function setupFunctionality(address proposalAddress) public override { + + require(_proxy == msg.sender, "Only Proxy can call This!"); + + IMVDFunctionalityProposal proposal = IMVDFunctionalityProposal(proposalAddress); + + string memory codeName = proposal.getCodeName(); + bool hasCodeName = !compareStrings(codeName, ""); + string memory replaces = proposal.getReplaces(); + bool hasReplaces = !compareStrings(replaces, ""); + + Functionality memory replacedFunctionality = _functionalities[_indexes[replaces]]; + + if(!hasCodeName && !hasReplaces) { + IMVDProxy(_proxy).callFromManager(_callingContext = proposal.getLocation(), abi.encodeWithSignature("callOneTime(address)", proposalAddress)); + _callingContext = address(0); + return; + } + + uint256 position = hasReplaces ? replacedFunctionality.position : _functionalities.length; + + if(hasReplaces) { + IMVDProxy(_proxy).callFromManager(_callingContext = replacedFunctionality.location, abi.encodeWithSignature("onStop(address)", proposalAddress)); + } + + replacedFunctionality.active = hasReplaces ? false : replacedFunctionality.active; + + _functionalitiesAmount -= hasReplaces ? 1 : 0; + + _functionalityCount[replacedFunctionality.location] = _functionalityCount[replacedFunctionality.location] - (hasReplaces ? 1 : 0); + + if(hasReplaces) { + _functionalities[position] = replacedFunctionality; + } + + Functionality memory newFunctionality = Functionality( + codeName, + proposal.getSourceLocation(), + proposal.getSourceLocationId(), + proposal.getLocation(), + proposal.isSubmitable(), + proposal.getMethodSignature(), + proposal.getReturnAbiParametersArray(), + proposal.isInternal(), + proposal.needsSender(), + proposalAddress, + true, + position + ); + + _functionalitiesAmount += hasCodeName ? 1 : 0; + + if(hasCodeName && position == _functionalities.length) { + _functionalities.push(newFunctionality); + } else if(hasCodeName) { + _functionalities[position] = newFunctionality; + } + + _indexes[codeName] = hasCodeName ? position : 0; + _functionalityCount[newFunctionality.location] = _functionalityCount[newFunctionality.location] + (hasCodeName ? 1 : 0); + + if(hasCodeName) { + IMVDProxy(_proxy).callFromManager(_callingContext = newFunctionality.location, abi.encodeWithSignature("onStart(address,address)", proposalAddress, hasReplaces ? replacedFunctionality.location : address(0))); + } + _callingContext = address(0); + if(hasCodeName || hasReplaces) { + IMVDProxy(_proxy).emitFromManager(hasCodeName ? codeName : "", hasCodeName ? position : 0, proposalAddress, hasReplaces ? replacedFunctionality.codeName : "", hasReplaces ? replacedFunctionality.location : address(0), hasReplaces ? replacedFunctionality.submitable : false, hasReplaces ? replacedFunctionality.methodSignature : "", hasReplaces ? replacedFunctionality.isInternal : false, hasReplaces ? replacedFunctionality.needsSender : false, hasReplaces ? replacedFunctionality.proposalAddress : address(0), hasReplaces ? replacedFunctionality.position : 0); + } + } + + function setCallingContext(address location) public override returns(bool changed) { + require(msg.sender == _proxy, "Unauthorized Access"); + _callingContext = (changed = _callingContext == address(0)) ? location : _callingContext; + } + + function clearCallingContext() public override { + require(msg.sender == _proxy, "Unauthorized Access"); + _callingContext = address(0); + } +} \ No newline at end of file diff --git a/MVDFunctionalityModelsManager.sol b/MVDFunctionalityModelsManager.sol new file mode 100644 index 0000000..bfc853e --- /dev/null +++ b/MVDFunctionalityModelsManager.sol @@ -0,0 +1,59 @@ +pragma solidity ^0.6.0; + +import "./IMVDFunctionalityModelsManager.sol"; +import "./Functionality.sol"; + +contract MVDFunctionalityModelsManager is IMVDFunctionalityModelsManager { + + mapping(string => Functionality) private _wellKnownFunctionalityModels; + + constructor() public override { + init(); + } + + function init() public override { + + require(compareStrings("", _wellKnownFunctionalityModels["getMinimumBlockNumberForSurvey"].codeName), "Init already called!"); + + _wellKnownFunctionalityModels["getMinimumBlockNumberForSurvey"] = Functionality("getMinimumBlockNumberForSurvey", address(0), 0, address(0), false, "getMinimumBlockNumberForSurvey()", '["uint256"]', false, false, address(0), true, 0); + + _wellKnownFunctionalityModels["getMinimumBlockNumberForEmergencySurvey"] = Functionality("getMinimumBlockNumberForEmergencySurvey", address(0), 0, address(0), false, "getMinimumBlockNumberForEmergencySurvey()", '["uint256"]', false, false, address(0), true, 0); + + _wellKnownFunctionalityModels["getEmergencySurveyStaking"] = Functionality("getEmergencySurveyStaking", address(0), 0, address(0), false, "getEmergencySurveyStaking()", '["uint256"]', false, false, address(0), true, 0); + + _wellKnownFunctionalityModels["checkSurveyResult"] = Functionality("checkSurveyResult", address(0), 0, address(0), false, "checkSurveyResult(address)", '["bool"]', false, false, address(0), true, 0); + + _wellKnownFunctionalityModels["onNewProposal"] = Functionality("onNewProposal", address(0), 0, address(0), true, "onNewProposal(address)", '[]', false, false, address(0), false, 0); + + _wellKnownFunctionalityModels["startProposal"] = Functionality("startProposal", address(0), 0, address(0), true, "startProposal(address,uint256,address)", '[]', false, true, address(0), false, 0); + + _wellKnownFunctionalityModels["disableProposal"] = Functionality("disableProposal", address(0), 0, address(0), true, "disableProposal(address,uint256,address)", '[]', false, true, address(0), false, 0); + + _wellKnownFunctionalityModels["proposalEnd"] = Functionality("proposalEnd", address(0), 0, address(0), true, "proposalEnd(address,bool)", "[]", false, false, address(0), false, 0); + } + + function checkWellKnownFunctionalities(string memory codeName, bool submitable, string memory methodSignature, string memory returnAbiParametersArray, + bool isInternal, bool needsSender, string memory replaces) public override view { + + if(compareStrings(codeName, "") && compareStrings(replaces, "")) { + return; + } + + bool codeNameIsWellKnown = compareStrings(codeName, _wellKnownFunctionalityModels[string(codeName)].codeName); + Functionality memory wellKnownFunctionality = _wellKnownFunctionalityModels[string(codeName)]; + + require(codeNameIsWellKnown ? wellKnownFunctionality.submitable == submitable : true, "Wrong submitable flag for this submission"); + require(codeNameIsWellKnown ? wellKnownFunctionality.needsSender == needsSender : true, "Wrong needsSender flag for this submission"); + require(codeNameIsWellKnown ? wellKnownFunctionality.isInternal == isInternal : true, "Wrong isInternal flag for this submission"); + require(codeNameIsWellKnown ? compareStrings(wellKnownFunctionality.methodSignature, methodSignature) : true, "Wrong method signature for this submission"); + require(codeNameIsWellKnown ? compareStrings(wellKnownFunctionality.returnAbiParametersArray, returnAbiParametersArray) : true, "Wrong return abi parameters array for this submission"); + + require(codeNameIsWellKnown ? wellKnownFunctionality.active ? compareStrings(wellKnownFunctionality.codeName, replaces) : true : true, "Active well known functionality cannot be disabled"); + + require(compareStrings(replaces, _wellKnownFunctionalityModels[replaces].codeName) ? compareStrings(codeName, "") ? !_wellKnownFunctionalityModels[replaces].active : true : true, "Active well known functionality cannot be disabled"); + } + + function compareStrings(string memory a, string memory b) internal pure returns(bool) { + return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); + } +} \ No newline at end of file diff --git a/MVDFunctionalityProposal.sol b/MVDFunctionalityProposal.sol new file mode 100644 index 0000000..2112871 --- /dev/null +++ b/MVDFunctionalityProposal.sol @@ -0,0 +1,316 @@ +pragma solidity ^0.6.0; + +import "./IMVDFunctionalityProposal.sol"; +import "./IMVDProxy.sol"; +import "./IERC20.sol"; +import "./CommonUtilities.sol"; + +contract MVDFunctionalityProposal is IMVDFunctionalityProposal, CommonUtilities { + + bool private _collateralDataSet; + + address private _proxy; + address private _token; + string private _codeName; + bool private _emergency; + address private _sourceLocation; + uint256 private _sourceLocationId; + address private _location; + bool private _submitable; + string private _methodSignature; + string private _returnAbiParametersArray; + bool private _isInternal; + bool private _needsSender; + string private _replaces; + uint256 private _surveyEndBlock; + uint256 private _surveyDuration; + bool private _terminated; + address private _proposer; + bool private _disabled; + + mapping(address => uint256) private _accept; + mapping(address => uint256) private _refuse; + uint256 private _totalAccept; + uint256 private _totalRefuse; + + constructor(string memory codeName, address location, string memory methodSignature, string memory returnAbiParametersArray, + string memory replaces, address proxy) public override { + init(codeName, location, methodSignature, returnAbiParametersArray, replaces, proxy); + } + + function init(string memory codeName, address location, string memory methodSignature, string memory returnAbiParametersArray, + string memory replaces, address proxy) public override { + require(_proxy == address(0), "Already initialized!"); + _token = IMVDProxy(_proxy = proxy).getToken(); + _codeName = codeName; + _location = location; + _methodSignature = methodSignature; + _returnAbiParametersArray = returnAbiParametersArray; + _replaces = replaces; + } + + function setCollateralData(bool emergency, address sourceLocation, uint256 sourceLocationId, bool submitable, bool isInternal, bool needsSender, address proposer) public override { + require(!_collateralDataSet, "setCollateralData already called!"); + require(_proxy == msg.sender, "Only Original Proxy can call this method!"); + _sourceLocation = sourceLocation; + _sourceLocationId = sourceLocationId; + _submitable = submitable; + _isInternal = isInternal; + _needsSender = needsSender; + _proposer = proposer; + _surveyDuration = toUint256(IMVDProxy(_proxy).read((_emergency = emergency) ? "getMinimumBlockNumberForEmergencySurvey" : "getMinimumBlockNumberForSurvey", bytes(""))); + _collateralDataSet = true; + } + + function getProxy() public override view returns(address) { + return _proxy; + } + + function getCodeName() public override view returns(string memory) { + return _codeName; + } + + function isEmergency() public override view returns(bool) { + return _emergency; + } + + function getSourceLocation() public override view returns(address) { + return _sourceLocation; + } + + function getSourceLocationId() public override view returns(uint256) { + return _sourceLocationId; + } + + function getLocation() public override view returns(address) { + return _location; + } + + function isSubmitable() public override view returns(bool) { + return _submitable; + } + + function getMethodSignature() public override view returns(string memory) { + return _methodSignature; + } + + function getReturnAbiParametersArray() public override view returns(string memory) { + return _returnAbiParametersArray; + } + + function isInternal() public override view returns(bool) { + return _isInternal; + } + + function needsSender() public override view returns(bool) { + return _needsSender; + } + + function getReplaces() public override view returns(string memory) { + return _replaces; + } + + function getProposer() public override view returns(address) { + return _proposer; + } + + function getSurveyEndBlock() public override view returns(uint256) { + return _surveyEndBlock; + } + + function getSurveyDuration() public override view returns(uint256) { + return _surveyDuration; + } + + function getVote(address addr) public override view returns(uint256 accept, uint256 refuse) { + accept = _accept[addr]; + refuse = _refuse[addr]; + } + + function getVotes() public override view returns(uint256, uint256) { + return (_totalAccept, _totalRefuse); + } + + function isTerminated() public override view returns(bool) { + return _terminated; + } + + function isDisabled() public override view returns(bool) { + return _disabled; + } + + function start() public override { + require(_collateralDataSet, "Still waiting for setCollateralData to be called!"); + require(msg.sender == _proxy, "Only Proxy can call this function!"); + require(_surveyEndBlock == 0, "Already started!"); + require(!_disabled, "Already disabled!"); + _surveyEndBlock = block.number + _surveyDuration; + } + + function disable() public override { + require(_collateralDataSet, "Still waiting for setCollateralData to be called!"); + require(msg.sender == _proxy, "Only Proxy can call this function!"); + require(_surveyEndBlock == 0, "Already started!"); + _disabled = true; + _terminated = true; + } + + function toJSON() public override view returns(string memory) { + return string(abi.encodePacked( + '{', + getFirstJSONPart(_sourceLocation, _sourceLocationId, _location), + '","submitable":', + _submitable ? "true" : "false", + ',"emergency":', + _emergency ? "true" : "false", + ',"isInternal":', + _isInternal ? "true" : "false", + ',"needsSender":', + _needsSender ? "true" : "false", + ',', + getSecondJSONPart(), + ',"proposer":"', + toString(_proposer), + '","endBlock":', + toString(_surveyEndBlock), + ',"terminated":', + _terminated ? "true" : "false", + ',"accepted":', + toString(_totalAccept), + ',"refused":', + toString(_totalRefuse), + ',"disabled":', + _disabled ? 'true' : 'false', + '}') + ); + } + + function getSecondJSONPart() private view returns (string memory){ + return string(abi.encodePacked( + '"codeName":"', + _codeName, + '","methodSignature":"', + _methodSignature, + '","returnAbiParametersArray":', + formatReturnAbiParametersArray(_returnAbiParametersArray), + ',"replaces":"', + _replaces, + '"')); + } + + modifier duringSurvey() { + require(_collateralDataSet, "Still waiting for setCollateralData to be called!"); + require(!_disabled, "Survey disabled!"); + require(_surveyEndBlock > 0, "Survey Not Started!"); + require(block.number < _surveyEndBlock, "Survey ended!"); + _; + } + + modifier onSurveyEnd() { + require(_collateralDataSet, "Still waiting for setCollateralData to be called!"); + require(!_disabled, "Survey disabled!"); + require(_surveyEndBlock > 0, "Survey Not Started!"); + require(block.number >= _surveyEndBlock, "Survey is still running!"); + _; + } + + function accept(uint256 amount) external override duringSurvey { + IERC20(_token).transferFrom(msg.sender, address(this), amount); + uint256 vote = _accept[msg.sender]; + vote += amount; + _accept[msg.sender] = vote; + _totalAccept += amount; + emit Accept(msg.sender, amount); + } + + function retireAccept(uint256 amount) external override duringSurvey { + require(_accept[msg.sender] >= amount, "Insufficient funds!"); + IERC20(_token).transfer(msg.sender, amount); + uint256 vote = _accept[msg.sender]; + vote -= amount; + _accept[msg.sender] = vote; + _totalAccept -= amount; + emit RetireAccept(msg.sender, amount); + } + + function moveToAccept(uint256 amount) external override duringSurvey { + require(_refuse[msg.sender] >= amount, "Insufficient funds!"); + uint256 vote = _refuse[msg.sender]; + vote -= amount; + _refuse[msg.sender] = vote; + _totalRefuse -= amount; + + vote = _accept[msg.sender]; + vote += amount; + _accept[msg.sender] = vote; + _totalAccept += amount; + emit MoveToAccept(msg.sender, amount); + } + + function refuse(uint256 amount) external override duringSurvey { + IERC20(_token).transferFrom(msg.sender, address(this), amount); + uint256 vote = _refuse[msg.sender]; + vote += amount; + _refuse[msg.sender] = vote; + _totalRefuse += amount; + emit Refuse(msg.sender, amount); + } + + function retireRefuse(uint256 amount) external override duringSurvey { + require(_refuse[msg.sender] >= amount, "Insufficient funds!"); + IERC20(_token).transfer(msg.sender, amount); + uint256 vote = _refuse[msg.sender]; + vote -= amount; + _refuse[msg.sender] = vote; + _totalRefuse -= amount; + emit RetireRefuse(msg.sender, amount); + } + + function moveToRefuse(uint256 amount) external override duringSurvey { + require(_accept[msg.sender] >= amount, "Insufficient funds!"); + uint256 vote = _accept[msg.sender]; + vote -= amount; + _accept[msg.sender] = vote; + _totalAccept -= amount; + + vote = _refuse[msg.sender]; + vote += amount; + _refuse[msg.sender] = vote; + _totalRefuse += amount; + emit MoveToRefuse(msg.sender, amount); + } + + function retireAll() external override duringSurvey { + require(_accept[msg.sender] + _refuse[msg.sender] > 0, "No votes!"); + uint256 acpt = _accept[msg.sender]; + uint256 rfs = _refuse[msg.sender]; + IERC20(_token).transfer(msg.sender, acpt + rfs); + _accept[msg.sender] = 0; + _refuse[msg.sender] = 0; + _totalAccept -= acpt; + _totalRefuse -= rfs; + emit RetireAll(msg.sender, acpt + rfs); + } + + function withdraw() external override onSurveyEnd { + if(!_terminated && !_disabled) { + terminate(); + return; + } + IERC20(_token).transfer(msg.sender, _accept[msg.sender] + _refuse[msg.sender]); + } + + function terminate() public override onSurveyEnd { + require(!_terminated, "Already terminated!"); + IMVDProxy(_proxy).setProposal(); + if(_accept[msg.sender] + _refuse[msg.sender] > 0) { + IERC20(_token).transfer(msg.sender, _accept[msg.sender] + _refuse[msg.sender]); + } + } + + function set() public override onSurveyEnd { + require(msg.sender == _proxy, "Unauthorized Access!"); + require(!_terminated, "Already terminated!"); + _terminated = true; + } +} \ No newline at end of file diff --git a/MVDFunctionalityProposalManager.sol b/MVDFunctionalityProposalManager.sol new file mode 100644 index 0000000..8adad85 --- /dev/null +++ b/MVDFunctionalityProposalManager.sol @@ -0,0 +1,107 @@ +pragma solidity ^0.6.0; + +import "./IMVDFunctionalityProposalManager.sol"; +import "./IMVDProxy.sol"; +import "./MVDFunctionalityProposal.sol"; + +contract MVDFunctionalityProposalManager is IMVDFunctionalityProposalManager { + + address private _proxy; + + mapping(address => bool) private _proposals; + + mapping(string => address) private _pendingProposals; + + modifier onlyProxy() { + require(msg.sender == address(_proxy), "Only Proxy can call this functionality"); + _; + } + + function newProposal(string memory codeName, address location, string memory methodSignature, string memory returnAbiParametersArray, string memory replaces) public override onlyProxy returns(address) { + return setProposal(codeName, location, methodSignature, replaces, address(new MVDFunctionalityProposal(codeName, location, methodSignature, returnAbiParametersArray, replaces, _proxy))); + } + + function preconditionCheck(string memory codeName, address location, string memory methodSignature, string memory replaces) private view returns(bool hasCodeName, bool hasReplaces) { + + require(_pendingProposals[codeName] == address(0), "There actually is a pending proposal for this code name"); + + require(_pendingProposals[replaces] == address(0), "There actually is a pending proposal for this replacing name"); + + hasCodeName = !compareStrings(codeName, ""); + hasReplaces = !compareStrings(replaces, ""); + + require((hasCodeName || !hasCodeName && !hasReplaces) ? location != address(0) : true, "Cannot have zero address for functionality to set or one time functionality to call"); + + require(hasCodeName ? !compareStrings(methodSignature, "") : true, "Cannot have empty string for methodSignature"); + + require(hasCodeName || hasReplaces || location != address(0), "codeName and replaces cannot be both empty"); + + IMVDProxy proxy = IMVDProxy(_proxy); + + require(hasCodeName && proxy.hasFunctionality(codeName) ? compareStrings(codeName, replaces) : true, "codeName is already used by another functionality"); + + require(hasReplaces ? proxy.hasFunctionality(replaces) : true, "Cannot replace unexisting or inactive functionality"); + } + + function setProposal(string memory codeName, address location, string memory methodSignature, string memory replaces, address proposalAddress) private returns(address) { + + (bool hasCodeName, bool hasReplaces) = preconditionCheck(codeName, location, methodSignature, replaces); + + _proposals[proposalAddress] = true; + + _pendingProposals[codeName] = (hasCodeName || !hasReplaces) ? proposalAddress : address(0); + + _pendingProposals[replaces] = (hasReplaces || !hasCodeName) ? proposalAddress : address(0); + + return proposalAddress; + } + + function checkProposal(address proposalAddress) public override onlyProxy { + require(_proposals[proposalAddress], "Unauthorized Access!"); + + IMVDFunctionalityProposal proposal = IMVDFunctionalityProposal(proposalAddress); + + uint256 surveyEndBlock = proposal.getSurveyEndBlock(); + + require(surveyEndBlock > 0, "Survey was not started!"); + + require(!proposal.isDisabled(), "Proposal is disabled!"); + + require(block.number >= surveyEndBlock, "Survey is still running!"); + + require(!proposal.isTerminated(), "Survey already terminated!"); + + _pendingProposals[proposal.getCodeName()] = address(0); + + _pendingProposals[proposal.getReplaces()] = address(0); + } + + function isValidProposal(address proposal) public override view returns (bool) { + return _proposals[proposal]; + } + + function disableProposal(address proposalAddress) public override onlyProxy { + require(_proposals[proposalAddress], "Not a valid proposal"); + IMVDFunctionalityProposal proposal = IMVDFunctionalityProposal(proposalAddress); + _pendingProposals[proposal.getCodeName()] = address(0); + _pendingProposals[proposal.getReplaces()] = address(0); + } + + function getPendingProposal(string memory codeName) public override view returns(address proposalAddress, bool isReallyPending) { + IMVDFunctionalityProposal proposal = IMVDFunctionalityProposal(proposalAddress = _pendingProposals[codeName]); + isReallyPending = proposal.getProxy() == address(this) && block.number < proposal.getSurveyEndBlock() || !proposal.isTerminated(); + } + + function getProxy() public override view returns (address) { + return _proxy; + } + + function setProxy() public override { + require(_proxy == address(0) || _proxy == msg.sender, _proxy != address(0) ? "Proxy already set!" : "Only Proxy can toggle itself!"); + _proxy = _proxy == address(0) ? msg.sender : address(0); + } + + function compareStrings(string memory a, string memory b) private pure returns(bool) { + return keccak256(bytes(a)) == keccak256(bytes(b)); + } +} \ No newline at end of file diff --git a/MVDProxy.sol b/MVDProxy.sol new file mode 100644 index 0000000..9f18121 --- /dev/null +++ b/MVDProxy.sol @@ -0,0 +1,327 @@ +pragma solidity ^0.6.0; + +import "./IMVDProxy.sol"; +import "./IMVDFunctionalityProposalManager.sol"; +import "./IMVDFunctionalityProposal.sol"; +import "./IStateHolder.sol"; +import "./IERC20.sol"; +import "./IVotingToken.sol"; +import "./IMVDFunctionalityModelsManager.sol"; +import "./ICommonUtilities.sol"; +import "./IMVDFunctionalitiesManager.sol"; + +contract MVDProxy is IMVDProxy { + + /*address private _token; + address private _functionalityProposalManagerAddress; + address private _stateHolderAddress; + address private _functionalityModelsManagerAddress; + address private _functionalitiesManagerAddress;*/ + + address[] private _delegates; + + constructor(address votingTokenAddress, address stateHolderAddress, address functionalityModelsManagerAddress, address functionalityProposalManagerAddress, address functionalitiesManagerAddress) public override { + if(votingTokenAddress == address(0)) { + return; + } + init(votingTokenAddress, stateHolderAddress, functionalityModelsManagerAddress, functionalityProposalManagerAddress, functionalitiesManagerAddress); + } + + function init(address votingTokenAddress, address stateHolderAddress, address functionalityModelsManagerAddress, address functionalityProposalManagerAddress, address functionalitiesManagerAddress) public override { + + require(_delegates.length == 0, "Init already called!"); + + _delegates = new address[](5); + + IVotingToken(_delegates[0] = votingTokenAddress).setProxy(); + + IMVDFunctionalityProposalManager(_delegates[1] = functionalityProposalManagerAddress).setProxy(); + + IStateHolder(_delegates[2] = stateHolderAddress).setProxy(); + + _delegates[3] = functionalityModelsManagerAddress; + + IMVDFunctionalitiesManager(_delegates[4] = functionalitiesManagerAddress).setProxy(); + + raiseFunctionalitySetEvent("getMinimumBlockNumberForSurvey"); + raiseFunctionalitySetEvent("getMinimumBlockNumberForEmergencySurvey"); + raiseFunctionalitySetEvent("getEmergencySurveyStaking"); + raiseFunctionalitySetEvent("checkSurveyResult"); + raiseFunctionalitySetEvent("onNewProposal"); + } + + function raiseFunctionalitySetEvent(string memory codeName) private { + (address addr, uint256 position,) = IMVDFunctionalitiesManager(_delegates[4]).getFunctionalityData(codeName); + if(addr != address(0)) { + emit FunctionalitySet(codeName, position, address(0), "", address(0), false, "", false, false, address(0), 0); + } + } + + receive() external payable { + if(msg.value > 0) { + emit PaymentReceived(msg.sender, msg.value); + } + } + + function setDelegate(uint256 position, address newAddress) private returns(address oldAddress) { + require(isAuthorizedFunctionality(msg.sender), "Unauthorized action!"); + require(newAddress != address(0), "Cannot set void address!"); + oldAddress = _delegates[position]; + _delegates[position] = newAddress; + if(position != 3) { + IMVDProxyDelegate(oldAddress).setProxy(); + IMVDProxyDelegate(newAddress).setProxy(); + } + } + + function getToken() public override view returns(address) { + return _delegates[0]; + } + + function setToken(address newAddress) public override { + emit TokenChanged(setDelegate(0, newAddress), newAddress); + } + + function getMVDFunctionalityProposalManagerAddress() public override view returns(address) { + return _delegates[1]; + } + + function setMVDFunctionalityProposalManagerAddress(address newAddress) public override { + emit MVDFunctionalityProposalManagerChanged(setDelegate(1, newAddress), newAddress); + } + + function getStateHolderAddress() public override view returns(address) { + return _delegates[2]; + } + + function setStateHolderAddress(address newAddress) public override { + emit StateHolderChanged(setDelegate(2, newAddress), newAddress); + } + + function getMVDFunctionalityModelsManagerAddress() public override view returns(address) { + return _delegates[3]; + } + + function setMVDFunctionalityModelsManagerAddress(address newAddress) public override { + emit MVDFunctionalityModelsManagerChanged(setDelegate(3, newAddress), newAddress); + } + + function getMVDFunctionalitiesManagerAddress() public override view returns(address) { + return _delegates[4]; + } + + function setMVDFunctionalitiesManagerAddress(address newAddress) public override { + emit MVDFunctionalitiesManagerChanged(setDelegate(4, newAddress), newAddress); + } + + function changeProxy(address payable newAddress) public override payable { + require(isAuthorizedFunctionality(msg.sender), "Unauthorized action!"); + require(newAddress != address(0), "Cannot set void address!"); + newAddress.transfer(msg.value); + IERC20 votingToken = IERC20(_delegates[0]); + votingToken.transfer(newAddress, votingToken.balanceOf(address(this))); + IMVDProxyDelegate(_delegates[0]).setProxy(); + IMVDProxyDelegate(_delegates[1]).setProxy(); + IMVDProxyDelegate(_delegates[2]).setProxy(); + IMVDProxyDelegate(_delegates[4]).setProxy(); + IMVDProxy(newAddress).init(_delegates[0], _delegates[2], _delegates[3], _delegates[1], _delegates[4]); + _delegates = new address[](0); + emit ProxyChanged(newAddress); + } + + function getFunctionalitiesAmount() public override view returns(uint256) { + return IMVDFunctionalitiesManager(_delegates[4]).getFunctionalitiesAmount(); + } + + function isValidProposal(address proposal) public override view returns (bool) { + return IMVDFunctionalityProposalManager(_delegates[1]).isValidProposal(proposal); + } + + function isValidFunctionality(address functionality) public override view returns(bool) { + return IMVDFunctionalitiesManager(_delegates[4]).isValidFunctionality(functionality); + } + + function isAuthorizedFunctionality(address functionality) public override view returns(bool) { + return IMVDFunctionalitiesManager(_delegates[4]).isAuthorizedFunctionality(functionality); + } + + function getFunctionalityAddress(string memory codeName) public override view returns(address location) { + (location,,) = IMVDFunctionalitiesManager(_delegates[4]).getFunctionalityData(codeName); + } + + function hasFunctionality(string memory codeName) public override view returns(bool) { + return IMVDFunctionalitiesManager(_delegates[4]).hasFunctionality(codeName); + } + + function functionalitiesToJSON() public override view returns(string memory functionsJSONArray) { + return IMVDFunctionalitiesManager(_delegates[4]).functionalitiesToJSON(); + } + + function functionalitiesToJSON(uint256 start, uint256 l) public override view returns(string memory functionsJSONArray) { + return IMVDFunctionalitiesManager(_delegates[4]).functionalitiesToJSON(start, l); + } + + function getPendingProposal(string memory codeName) public override view returns(address proposalAddress, bool isReallyPending) { + (proposalAddress, isReallyPending) = IMVDFunctionalityProposalManager(_delegates[1]).getPendingProposal(codeName); + } + + function newProposal(string memory codeName, bool emergency, address sourceLocation, uint256 sourceLocationId, address location, bool submitable, string memory methodSignature, string memory returnAbiParametersArray, bool isInternal, bool needsSender, string memory replaces) public override returns(address proposalAddress) { + emergencyBehavior(emergency); + + IMVDFunctionalityModelsManager(_delegates[3]).checkWellKnownFunctionalities(codeName, submitable, methodSignature, returnAbiParametersArray, isInternal, needsSender, replaces); + + IMVDFunctionalityProposal proposal = IMVDFunctionalityProposal(proposalAddress = IMVDFunctionalityProposalManager(_delegates[1]).newProposal(codeName, location, methodSignature, returnAbiParametersArray, replaces)); + proposal.setCollateralData(emergency, sourceLocation, sourceLocationId, submitable, isInternal, needsSender, msg.sender); + + IMVDFunctionalitiesManager functionalitiesManager = IMVDFunctionalitiesManager(_delegates[4]); + (address loc, , string memory meth) = functionalitiesManager.getFunctionalityData("onNewProposal"); + if(location != address(0)) { + functionalitiesManager.setCallingContext(location); + (bool response,) = loc.call(abi.encodeWithSignature(meth, proposalAddress)); + functionalitiesManager.clearCallingContext(); + require(response, "New Proposal check failed!"); + } + + if(!hasFunctionality("startProposal") || !hasFunctionality("disableProposal")) { + proposal.start(); + } + + emit Proposal(proposalAddress); + } + + function emergencyBehavior(bool emergency) private { + if(!emergency) { + return; + } + (address loc, , string memory meth) = IMVDFunctionalitiesManager(_delegates[4]).getFunctionalityData("getEmergencySurveyStaking"); + (, bytes memory payload) = loc.staticcall(abi.encodeWithSignature(meth)); + uint256 staking = toUint256(payload); + if(staking > 0) { + IERC20(_delegates[0]).transferFrom(msg.sender, address(this), staking); + } + } + + function startProposal(address proposalAddress) public override { + require(isAuthorizedFunctionality(msg.sender), "Unauthorized action!"); + (address location, ,) = IMVDFunctionalitiesManager(_delegates[4]).getFunctionalityData("startProposal"); + require(location == msg.sender, "Only startProposal Functionality can enable a delayed proposal"); + require(isValidProposal(proposalAddress), "Invalid Proposal Address!"); + IMVDFunctionalityProposal(proposalAddress).start(); + } + + function disableProposal(address proposalAddress) public override { + require(isAuthorizedFunctionality(msg.sender), "Unauthorized action!"); + (address location, ,) = IMVDFunctionalitiesManager(_delegates[4]).getFunctionalityData("disableProposal"); + require(location == msg.sender, "Only disableProposal Functionality can disable a delayed proposal"); + IMVDFunctionalityProposal(proposalAddress).disable(); + IMVDFunctionalityProposalManager(_delegates[1]).disableProposal(proposalAddress); + } + + function transfer(address receiver, uint256 value, address token) public override { + require(isAuthorizedFunctionality(msg.sender), "Only functionalities can transfer Proxy balances!"); + if(value == 0) { + return; + } + if(token == address(0)) { + payable(receiver).transfer(value); + return; + } + IERC20(token).transfer(receiver, value); + } + + function setProposal() public override { + + IMVDFunctionalityProposalManager(_delegates[1]).checkProposal(msg.sender); + + IMVDFunctionalitiesManager functionalitiesManager = IMVDFunctionalitiesManager(_delegates[4]); + + (address addressToCall, , string memory methodSignature) = functionalitiesManager.getFunctionalityData("checkSurveyResult"); + + (bool result, bytes memory response) = addressToCall.staticcall(abi.encodeWithSignature(methodSignature, msg.sender)); + + result = toUint256(response) > 0; + + IMVDFunctionalityProposal proposal = IMVDFunctionalityProposal(msg.sender); + proposal.set(); + + (addressToCall, , methodSignature) = functionalitiesManager.getFunctionalityData("proposalEnd"); + if(addressToCall != address(0)) { + functionalitiesManager.setCallingContext(addressToCall); + addressToCall.call(abi.encodeWithSignature(methodSignature, msg.sender, result)); + functionalitiesManager.clearCallingContext(); + } + + emit ProposalSet(msg.sender, result); + + if(!result) { + return; + } + + if(proposal.isEmergency()) { + (addressToCall, , methodSignature) = functionalitiesManager.getFunctionalityData("getEmergencySurveyStaking"); + (, response) = addressToCall.staticcall(abi.encodeWithSignature(methodSignature)); + uint256 staking = toUint256(response); + if(staking > 0) { + IERC20(_delegates[0]).transfer(proposal.getProposer(), staking); + } + } + + functionalitiesManager.setupFunctionality(msg.sender); + } + + function read(string memory codeName, bytes memory data) public override view returns(bytes memory returnData) { + + (address location, bytes memory payload) = IMVDFunctionalitiesManager(_delegates[4]).preConditionCheck(codeName, data, 0, msg.sender, 0); + + bool ok; + (ok, returnData) = location.staticcall(payload); + + require(ok, "Failed to read from functionality"); + } + + function submit(string memory codeName, bytes memory data) public override payable returns(bytes memory returnData) { + IMVDFunctionalitiesManager manager = IMVDFunctionalitiesManager(_delegates[4]); + (address location, bytes memory payload) = manager.preConditionCheck(codeName, data, 1, msg.sender, msg.value); + + bool changed = manager.setCallingContext(location); + + bool ok; + (ok, returnData) = location.call(payload); + + if(changed) { + manager.clearCallingContext(); + } + require(ok, "Failed to submit functionality"); + } + + function callFromManager(address location, bytes memory payload) public override returns(bool, bytes memory) { + require(msg.sender == _delegates[4], "Only Manager can call this!"); + return location.call(payload); + } + + function emitFromManager(string memory codeName, uint256 position, address proposal, string memory replaced, address location, bool submitable, string memory methodSignature, bool isInternal, bool needsSender, address proposalAddress, uint256 replacedPosition) public override { + require(msg.sender == _delegates[4], "Only Manager can call this!"); + emit FunctionalitySet(codeName, position, proposal, replaced, location, submitable, methodSignature, isInternal, needsSender, proposalAddress, replacedPosition); + } + + function emitEvent(string memory eventSignature, bytes memory firstIndex, bytes memory secondIndex, bytes memory data) public override { + require(isAuthorizedFunctionality(msg.sender), "Only authorized functionalities can emit events!"); + emit Event(eventSignature, keccak256(firstIndex), keccak256(secondIndex), data); + } + + function compareStrings(string memory a, string memory b) private pure returns(bool) { + return keccak256(bytes(a)) == keccak256(bytes(b)); + } + + function toUint256(bytes memory bs) internal pure returns(uint256 x) { + if(bs.length >= 32) { + assembly { + x := mload(add(bs, add(0x20, 0))) + } + } + } +} + +interface IMVDProxyDelegate { + function setProxy() external; +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..898e6fc --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# The Minimum Viable Decentralized Flexible Organization + +## Authors: Marco Vasapollo - Alessandro Mario Laganà Toschi | bro@buidl.life +### WP: https://medium.com/risepic/introducing-the-decentralized-flexible-organization-8c9e6fbab6d4 + +The Decentralized Flexible Organization is a new concept, that reshapes how we’re building Decentralized Applications, solving some critical points of failure in the today’s Dapp ecosystem like the needs of a Legal Known Entity to Trust, the slowly Smart Contract Developing and the Centralized or Distributed Servers Needs. + +The Decentralized Flexible Organisation is a basic layer independent from the functions of a Decentralized Applications, with the aim to rule all of the developing and upgrade of it since the beginning in an anonymous way. +In fact, using DFO, the first step to start building a Decentralized Application is to Deploy the DAO side, with the Voting Token information and the Voting Rules. + +Once deployed the DFO Smart Contract works as a Proxy to route functions in Read/Write rules or read-only rules to the connected Smart Contracts or Non-Fungible Tokens, so at the end of the day, Token Holders can vote the edit, kill or add functionality and design features of the Decentralized Application. +Basically, the community can BUIDL, and 100% rule it without needs to trust an entity or to trust each other. + +A DFO is a self-sustainable ecosystem that works like a Proxy to connect permitted SmartContract as microservices defining a new Flexible and Innovative way to build DApps. +Too rule a DApp, the DAO can introduce a new way to build SmartContracts as happened in the Web 2.0 from dangerous monolith coding to fault-tolerant microservices approach. +So, at the end of the day, if a SmartContract fails the DApp remains anyway fully operative. +And this is why we called it Flexible. + +This is the Proxy which represents the DFO itself. + +DFO is composed by a series of functionalities. +Each functionality is a SmartContract. +Each functionality/SmartContract can be plugged-in/out or replaced in any moment and for any reason, like microservices. + +It needs a community survey to create/edit/replace a functionality. +A survey is voted with a ERC20 voting Token, created for the specific DFC. +Survey rules like duration (in blocks) and votes weight are also pluggable DFC functionalities (called Well Known Functionalities, which CANNOT be totally disabled) that can be replaced with Surveys voted by token holders. + +# How Does it work? +In a DFO environment, Anyone or Token Holders (as pre-decided when the DFO is deployed) are able to deploy a Smart Contract or a series of chained NFTs and request to the Token Holders to connect it with the proxy as a new functionality, or to edit an existent functionality, by creating a “Survey Smart Contract” by adding a simple request to the DFO Proxy Smart Contract. +Once the voting period is finished anyone can trigger the finished event and automatically the Proxy Smart Contract will kill the old version of the functionality, if the survey was based on “Edit” or will add the new functionality, if the survey was based on “Add new”. (Voting rules and Block Time for the survey are pre-decided when the DFO is deployed and can be changed in the same way of changing any other functionality) + +To reach this goal, the DFO is introducing a new way to deploy Flexible Decentralized Applications that will enable an interesting, smart way to code and deploy Smart Contracts, more like Microservices, and this is why we called it “Flexible” rather than “Autonomous.” +This Microservices kind of developing is fascinating because can introduce a new wave of Decentralized Application where if a single Smart Contract is broken, don’t block the entire application security and usability, but only the single function and it’ll be killed or edited fast without needs to a single entity to do emergency strategies like to fork the entire application. + +Another important step forward is the idea to introduce NFTs for Front-End Code, so the DFO proxy is able to redirect users to the right NFTs to download the frontend code when a user’s browsers want to surf the Decentralized Application, and at the same time, Token Holders are able to easily vote for the front end connecting the Proxy to the chained NFTs they prefer. +The concept of Non-Fungible Tokens for the Front-End is possible with this open source protocol Developed by me and Marco Vasapollo: https://github.com/b-u-i-d-l/ROBE to build chained NFTs that are able to store pieces of information surpassing the limits of a single NFT. + +With ROBE if an application is able to call the first or any other NFTs in a chain, it can easily take the rooting one and read all of the information stored in every NFT in the chain. +This setup is a step forward for building Decentralized Application, that can be hosted by anyone in every kind of domain name, just adding one or two simple lines of code to redirect users to the Proxy of the DFO, making Dapps more censorship-resistant than ever before. + +## You can already start building a DFO and play with this new awesome way to deploy Flexible Decentralized Applications on Ethereum \ No newline at end of file diff --git a/StateHolder.sol b/StateHolder.sol new file mode 100644 index 0000000..c7ce354 --- /dev/null +++ b/StateHolder.sol @@ -0,0 +1,190 @@ +pragma solidity ^0.6.0; + +import "./IMVDProxy.sol"; +import "./CommonUtilities.sol"; +import "./IStateHolder.sol"; + +contract StateHolder is IStateHolder, CommonUtilities { + + enum DataType { + ADDRESS, + BOOL, + BYTES, + STRING, + UINT256 + } + + struct Var { + string name; + DataType dataType; + bytes value; + uint256 position; + bool active; + } + + Var[] private _state; + mapping(string => uint256) private _index; + address private _proxy; + uint256 private _stateSize; + + constructor() public override { + init(); + } + + function init() public override { + require(_state.length == 0, "Init already called!"); + _state.push(Var("", DataType.BYTES, "", 0, false)); + } + + function getState(uint256 i) public override view returns (string memory, string memory, bytes memory) { + uint256 position = 0; + for(uint256 z = 1; z < _state.length; z++) { + Var memory v = _state[z]; + if(v.active && position++ == i) { + return (v.name, toString(v.dataType), v.value); + } + } + } + + function getInfo(string memory varName) public override view returns (string memory dataType, bytes memory value, uint256 position) { + Var memory v = _state[_index[varName]]; + if(v.active) { + dataType = toString(v.dataType); + value = v.value; + position = v.position - 1; + } + } + + function getStateSize() public override view returns (uint256) { + return _stateSize; + } + + function exists(string memory varName) public override view returns(bool) { + return _state[_index[varName]].active; + } + + function getDataType(string memory varName) public override view returns(string memory dataType) { + Var memory v = _state[_index[varName]]; + if(v.active) { + dataType = toString(v.dataType); + } + } + + function setVal(string memory varName, DataType dataType, bytes memory val) private returns(bytes memory oldVal) { + require(_state.length > 0, "Not Initialized!"); + require(_proxy == address(0) || IMVDProxy(_proxy).isAuthorizedFunctionality(msg.sender), "Only Proxy Functionalities can set vars"); + if(compareStrings(varName, "")) { + return ""; + } + Var memory v = _state[_index[varName]]; + oldVal = v.value; + v.name = varName; + v.value = val; + if(v.position == 0) { + for(uint256 i = 1; i < _state.length; i++) { + if(!_state[i].active) { + v.position = i; + break; + } + } + } else { + require(!v.active || v.dataType == dataType, "Invalid dataType"); + } + v.dataType = dataType; + if(!v.active) { + _stateSize++; + } + v.active = true; + if(v.position == 0) { + v.position = _state.length; + _state.push(v); + } else { + _state[v.position] = v; + } + _index[varName] = v.position; + } + + function clear(string memory varName) public override returns(string memory oldDataType, bytes memory oldVal) { + require(_state.length > 0, "Not Initialized!"); + require(_proxy == address(0) || IMVDProxy(_proxy).isAuthorizedFunctionality(msg.sender), "Only Proxy Functionalities can set vars"); + Var storage v = _state[_index[varName]]; + if(v.position > 0 && v.active) { + oldDataType = toString(v.dataType); + oldVal = v.value; + v.value = ""; + v.position = 0; + _index[v.name] = 0; + v.active = false; + _stateSize--; + } + } + + function setBytes(string memory varName, bytes memory val) public override returns(bytes memory) { + return setVal(varName, DataType.BYTES, val); + } + + function getBytes(string memory varName) public override view returns(bytes memory) { + return _state[_index[varName]].value; + } + + function setString(string memory varName, string memory val) public override returns(string memory) { + return string(setVal(varName, DataType.STRING, bytes(val))); + } + + function getString(string memory varName) public override view returns (string memory) { + return string(_state[_index[varName]].value); + } + + function setBool(string memory varName, bool val) public override returns(bool) { + return toUint256(setVal(varName, DataType.BOOL, abi.encode(val ? 1 : 0))) == 1; + } + + function getBool(string memory varName) public override view returns (bool) { + return toUint256(_state[_index[varName]].value) == 1; + } + + function getUint256(string memory varName) public override view returns (uint256) { + return toUint256(_state[_index[varName]].value); + } + + function setUint256(string memory varName, uint256 val) public override returns(uint256) { + return toUint256(setVal(varName, DataType.UINT256, abi.encode(val))); + } + + function getAddress(string memory varName) public override view returns (address) { + return toAddress(_state[_index[varName]].value); + } + + function setAddress(string memory varName, address val) public override returns (address) { + return toAddress(setVal(varName, DataType.ADDRESS, abi.encodePacked(val))); + } + + function getProxy() public override view returns (address) { + return _proxy; + } + + function setProxy() public override { + require(_state.length != 0, "Init not called!"); + require(_proxy == address(0) || _proxy == msg.sender, _proxy != address(0) ? "Proxy already set!" : "Only Proxy can toggle itself!"); + _proxy = _proxy == address(0) ? msg.sender : address(0); + } + + function toString(DataType dataType) private pure returns (string memory) { + return + dataType == DataType.ADDRESS ? "address" : + dataType == DataType.BOOL ? "bool" : + dataType == DataType.BYTES ? "bytes" : + dataType == DataType.STRING ? "string" : + dataType == DataType.UINT256 ? "uint256" : + ""; + } + + function toDataType(string memory dataType) private pure returns (DataType) { + return + compareStrings(dataType, "address") ? DataType.ADDRESS : + compareStrings(dataType, "bool") ? DataType.BOOL : + compareStrings(dataType, "string") ? DataType.STRING : + compareStrings(dataType, "uint256") ? DataType.UINT256 : + DataType.BYTES; + } +} \ No newline at end of file diff --git a/VotingToken.sol b/VotingToken.sol new file mode 100644 index 0000000..1a619c3 --- /dev/null +++ b/VotingToken.sol @@ -0,0 +1,149 @@ +pragma solidity ^0.6.0; + +import "./IMVDProxy.sol"; +import "./IERC20.sol"; +import "./IVotingToken.sol"; + +contract VotingToken is IERC20, IVotingToken { + + mapping (address => uint256) private _balances; + + mapping (address => mapping (address => uint256)) private _allowances; + + uint256 private _totalSupply; + uint256 private _decimals; + address private _proxy; + string private _name; + string private _symbol; + + constructor(string memory name, string memory symbol, uint256 decimals, uint256 totalSupply) public override { + if(totalSupply == 0) { + return; + } + init(name, symbol, decimals, totalSupply); + } + + function init(string memory name, string memory symbol, uint256 decimals, uint256 totalSupply) public override { + require(_totalSupply == 0, "Init already called!"); + + _name = name; + _symbol = symbol; + _decimals = decimals; + _totalSupply = totalSupply * (10 ** decimals); + _balances[msg.sender] = _totalSupply; + emit Transfer(address(this), msg.sender, _totalSupply); + } + + receive() external payable { + revert("ETH not accepted"); + } + + function getProxy() public override view returns(address) { + return _proxy; + } + + function name() public override view returns(string memory) { + return _name; + } + + function symbol() public override view returns(string memory) { + return _symbol; + } + + function decimals() public override view returns(uint256) { + return _decimals; + } + + function totalSupply() public override view returns (uint256) { + return _totalSupply; + } + + function balanceOf(address account) public override view returns (uint256) { + return _balances[account]; + } + + function transfer(address recipient, uint256 amount) public override returns (bool) { + require(_proxy == address(0) ? true : !IMVDProxy(_proxy).isValidProposal(recipient), "Cannot transfer to DFC Survey Proposal!"); + _transfer(msg.sender, recipient, amount); + return true; + } + + function allowance(address owner, address spender) public override view returns (uint256) { + if(IMVDProxy(_proxy).isValidProposal(spender)) { + return _balances[owner]; + } + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) public override returns (bool) { + require(!IMVDProxy(_proxy).isValidProposal(spender), "Cannot approve Proposals to spend tokens"); + _approve(msg.sender, spender, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) { + _transfer(sender, recipient, amount); + address txSender = msg.sender; + if(!(IMVDProxy(_proxy).isValidProposal(txSender) && recipient == txSender)) { + _approve(sender, txSender, _allowances[sender][txSender] = sub(_allowances[sender][txSender], amount, "ERC20: transfer amount exceeds allowance")); + } + return true; + } + + function increaseAllowance(address spender, uint256 addedValue) public override returns (bool) { + _approve(msg.sender, spender, add(_allowances[msg.sender][spender], addedValue)); + return true; + } + + function decreaseAllowance(address spender, uint256 subtractedValue) public override returns (bool) { + _approve(msg.sender, spender, sub(_allowances[msg.sender][spender], subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + + function _transfer(address sender, address recipient, uint256 amount) internal { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _balances[sender] = sub(_balances[sender], amount, "ERC20: transfer amount exceeds balance"); + _balances[recipient] = add(_balances[recipient], amount); + emit Transfer(sender, recipient, amount); + } + + function _approve(address owner, address spender, uint256 amount) internal { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + function add(uint256 a, uint256 b) internal pure returns (uint256 c) { + c = a + b; + require(c >= a, "SafeMath: addition overflow"); + } + + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256 c) { + require(b <= a, errorMessage); + c = a - b; + } + + function setProxy() public override { + require(_totalSupply != 0, "Init not called!"); + require(_proxy == address(0) || _proxy == msg.sender, _proxy != address(0) ? "Proxy already set!" : "Only Proxy can toggle itself!"); + _proxy = _proxy == address(0) ? msg.sender : address(0); + } + + function mint(uint256 amount) public override { + require(IMVDProxy(_proxy).isAuthorizedFunctionality(msg.sender), "Unauthorized access!"); + + _totalSupply = add(_totalSupply, amount); + _balances[_proxy] = add(_balances[_proxy], amount); + emit Transfer(address(0), _proxy, amount); + } + + function burn(uint256 amount) public override { + _balances[msg.sender] = sub(_balances[msg.sender], amount, "VotingToken: burn amount exceeds balance"); + _totalSupply = sub(_totalSupply, amount, "VotingToken: burn amount exceeds total supply"); + emit Transfer(msg.sender, address(0), amount); + } +} \ No newline at end of file