diff --git a/.gitignore b/.gitignore index c5e82d7..da336f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,269 @@ -bin \ No newline at end of file +/**/.vscode +/**/spa/**/style.css + +docs/md-build +docs/tmp + +# Created by https://www.toptal.com/developers/gitignore/api/python,solidity,visualstudiocode,react +# Edit at https://www.toptal.com/developers/gitignore?templates=python,solidity,visualstudiocode,react + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +### react ### +.DS_* +logs +**/*.backup.* +**/*.back.* + +node_modules +bower_components + +*.sublime* + +psd +thumb +sketch + +### Solidity ### +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env.test + +# parcel-bundler cache (https://parceljs.org/) + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history + +# End of https://www.toptal.com/developers/gitignore/api/python,solidity,visualstudiocode,react diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..555780d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "docs/build"] + path = docs/build + url = git@github.com:b-u-i-d-l/dfo-protocol-docs.git diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..2bf0ebc --- /dev/null +++ b/.prettierrc @@ -0,0 +1,15 @@ +{ + "overrides": [ + { + "files": "*.sol", + "options": { + "printWidth": 100, + "tabWidth": 4, + "useTabs": false, + "singleQuote": false, + "bracketSpacing": false, + "explicitTypes": "always" + } + } + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..d2292da --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,67 @@ +# Contributing guidelines + +## Table of Contents + +- [Table of Contents](#table-of-contents) +- [Coding Style](#coding-style) + - [Solidity](#solidity) + - [JavaScript](#javascript) + - [Python](#python) +- [Documentation](#documentation) + - [mkdocs](#mkdocs) + +## Coding Style + +### Solidity + +* Solidity portions of the codebase adhere follow the official [Solidity Styleguide] + +### JavaScript + +### Python + +* Python portions of the codebase follow standard PEP8 best practices. +* Python code must be formatted using the Black formatter using the provided settings. + +## Documentation + +New addition to the codebase must be fully documented. + +- JavaScript portions of the code should be annotated using JSDoc style docstrings. +- Solidity portions of the code should be fully annotated using [NatSpec]. + +Documentation is generated using [py-solidity-docgen] and rendered via [mkdocs]. +[py-solidity-docgen] parses NatSpec and outputs `.md` files inside `docs/md-build` according +to a pre-specified Jinja2 template. + +**NOTE:** Each `.sol` file should contain only one `Interface` or `Contract`. + +To build the documentation: + +```console +yarn docs:build +``` + +To serve the documentation + +```console +yarn docs:serve +``` + +### mkdocs + +To install [mkdocs] and [py-solidity-docgen] Python must be installed in the system. + +``` +pip install docs/requirements.in +``` + +**NOTE:** Working inside a virtual environment is highly recommended! + +--- + +[Solidity Styleguide]: https://solidity.readthedocs.io/en/v0.7.0/style-guide.html +[NatSpec]: https://solidity.readthedocs.io/en/v0.7.0/style-guide.html#natspec +[Write the Docs!]: docs/source/write_the_docs.rst +[py-solidity-docgen]: https://github.com/b-u-i-d-l/py-solidity-docgen +[mkdocs]: https://www.mkdocs.org/ diff --git a/CommonUtilities.sol b/CommonUtilities.sol deleted file mode 100644 index 111f1b7..0000000 --- a/CommonUtilities.sol +++ /dev/null @@ -1,92 +0,0 @@ -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/ICommonUtilities.sol b/ICommonUtilities.sol deleted file mode 100644 index 7c86a7a..0000000 --- a/ICommonUtilities.sol +++ /dev/null @@ -1,12 +0,0 @@ -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/IDoubleProxy.sol b/IDoubleProxy.sol deleted file mode 100644 index 3e96465..0000000 --- a/IDoubleProxy.sol +++ /dev/null @@ -1,11 +0,0 @@ -pragma solidity ^0.6.0; - -interface IDoubleProxy { - function init(address[] calldata proxies, address currentProxy) external; - function proxy() external view returns(address); - function setProxy() external; - function isProxy(address) external view returns(bool); - function proxiesLength() external view returns(uint256); - function proxies(uint256 start, uint256 offset) external view returns(address[] memory); - function proxies() external view returns(address[] memory); -} \ No newline at end of file diff --git a/IERC20.sol b/IERC20.sol deleted file mode 100644 index 0ec4e7b..0000000 --- a/IERC20.sol +++ /dev/null @@ -1,13 +0,0 @@ -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/IERC721.sol b/IERC721.sol deleted file mode 100644 index 913b8bb..0000000 --- a/IERC721.sol +++ /dev/null @@ -1,7 +0,0 @@ -pragma solidity ^0.6.0; - -interface IERC721 { - function ownerOf(uint256 _tokenId) external view returns (address); - function transferFrom(address _from, address _to, uint256 _tokenId) external payable; - function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; -} \ No newline at end of file diff --git a/IERC721Receiver.sol b/IERC721Receiver.sol deleted file mode 100644 index 62d698e..0000000 --- a/IERC721Receiver.sol +++ /dev/null @@ -1,5 +0,0 @@ -pragma solidity ^0.6.0; - -interface IERC721Receiver { - function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4); -} \ No newline at end of file diff --git a/IMVDFunctionalitiesManager.sol b/IMVDFunctionalitiesManager.sol deleted file mode 100644 index 5948cf1..0000000 --- a/IMVDFunctionalitiesManager.sol +++ /dev/null @@ -1,33 +0,0 @@ -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, address, uint256); - function hasFunctionality(string calldata codeName) external view returns(bool); - function getFunctionalitiesAmount() external view returns(uint256); - function functionalitiesToJSON() external view returns(string memory); - function functionalitiesToJSON(uint256 start, uint256 l) external view returns(string memory functionsJSONArray); - function functionalityNames() external view returns(string memory); - function functionalityNames(uint256 start, uint256 l) external view returns(string memory functionsJSONArray); - function functionalityToJSON(string calldata codeName) external view returns(string memory); - - 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 returns (bool); -} \ No newline at end of file diff --git a/IMVDFunctionalityModelsManager.sol b/IMVDFunctionalityModelsManager.sol deleted file mode 100644 index 4e0c27d..0000000 --- a/IMVDFunctionalityModelsManager.sol +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index 9040423..0000000 --- a/IMVDFunctionalityProposal.sol +++ /dev/null @@ -1,50 +0,0 @@ -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, uint256 votesHardCap) 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 isVotesHardCapReached() external view returns(bool); - function getVotesHardCapToReach() 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 deleted file mode 100644 index d38bd5b..0000000 --- a/IMVDFunctionalityProposalManager.sol +++ /dev/null @@ -1,9 +0,0 @@ -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 getProxy() external view returns (address); - function setProxy() external; - function isValidProposal(address proposal) external view returns (bool); -} \ No newline at end of file diff --git a/IMVDProxy.sol b/IMVDProxy.sol deleted file mode 100644 index bf84ff0..0000000 --- a/IMVDProxy.sol +++ /dev/null @@ -1,42 +0,0 @@ -pragma solidity ^0.6.0; - -interface IMVDProxy { - - function init(address votingTokenAddress, address functionalityProposalManagerAddress, address stateHolderAddress, address functionalityModelsManagerAddress, address functionalitiesManagerAddress, address walletAddress, address doubleProxyAddress) external; - - function getDelegates() external view returns(address[] memory); - function getToken() external view returns(address); - function getMVDFunctionalityProposalManagerAddress() external view returns(address); - function getStateHolderAddress() external view returns(address); - function getMVDFunctionalityModelsManagerAddress() external view returns(address); - function getMVDFunctionalitiesManagerAddress() external view returns(address); - function getMVDWalletAddress() external view returns(address); - function getDoubleProxyAddress() external view returns(address); - function setDelegate(uint256 position, address newAddress) external returns(address oldAddress); - function changeProxy(address newAddress, bytes calldata initPayload) external; - function isValidProposal(address proposal) external view returns (bool); - function isAuthorizedFunctionality(address functionality) external view returns(bool); - 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 transfer721(address receiver, uint256 tokenId, bytes calldata data, bool safe, address token) external; - function flushToWallet(address tokenAddress, bool is721, uint256 tokenId) 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, address proposal, string calldata replaced, address replacedSourceLocation, uint256 replacedSourceLocationId, address location, bool submitable, string calldata methodSignature, bool isInternal, bool needsSender, address proposalAddress) external; - - function emitEvent(string calldata eventSignature, bytes calldata firstIndex, bytes calldata secondIndex, bytes calldata data) external; - - event ProxyChanged(address indexed newAddress); - event DelegateChanged(uint256 position, address indexed oldAddress, address indexed newAddress); - - event Proposal(address proposal); - event ProposalCheck(address indexed proposal); - event ProposalSet(address indexed proposal, bool success); - event FunctionalitySet(string codeName, address indexed proposal, string replaced, address replacedSourceLocation, uint256 replacedSourceLocationId, address indexed replacedLocation, bool replacedWasSubmitable, string replacedMethodSignature, bool replacedWasInternal, bool replacedNeededSender, address indexed replacedProposal); - - event Event(string indexed key, bytes32 indexed firstIndex, bytes32 indexed secondIndex, bytes data); -} \ No newline at end of file diff --git a/IMVDWallet.sol b/IMVDWallet.sol deleted file mode 100644 index d0deefe..0000000 --- a/IMVDWallet.sol +++ /dev/null @@ -1,18 +0,0 @@ -pragma solidity ^0.6.0; - -interface IMVDWallet { - - function getProxy() external view returns (address); - - function setProxy() external; - - function setNewWallet(address payable newWallet, address tokenAddress) external; - - function transfer(address receiver, uint256 value, address tokenAddress) external; - - function transfer(address receiver, uint256 tokenId, bytes calldata data, bool safe, address token) external; - - function flushToNewWallet(address token) external; - - function flush721ToNewWallet(uint256 tokenId, bytes calldata data, bool safe, address tokenAddress) external; -} \ No newline at end of file diff --git a/IStateHolder.sol b/IStateHolder.sol deleted file mode 100644 index 1b4cd7c..0000000 --- a/IStateHolder.sol +++ /dev/null @@ -1,25 +0,0 @@ -pragma solidity ^0.6.0; - -interface IStateHolder { - - function init() external; - - function getProxy() external view returns (address); - function setProxy() external; - function toJSON() external view returns(string memory); - function toJSON(uint256 start, uint256 l) external view returns(string memory); - 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 deleted file mode 100644 index 1e20a09..0000000 --- a/IVotingToken.sol +++ /dev/null @@ -1,18 +0,0 @@ -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 index e2e7f6c..85dc429 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Robe +Copyright (c) 2020 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 diff --git a/MVDFunctionalitiesManager.sol b/MVDFunctionalitiesManager.sol deleted file mode 100644 index a86f0de..0000000 --- a/MVDFunctionalitiesManager.sol +++ /dev/null @@ -1,340 +0,0 @@ -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 { - 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, address, uint256) { - Functionality memory functionality = _functionalities[_indexes[codeName]]; - return (compareStrings(codeName, functionality.codeName) && functionality.active ? functionality.location : address(0), functionality.position, functionality.methodSignature, functionality.sourceLocation, functionality.sourceLocationId); - } - - 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) { - 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; - } - functionsJSONArray = string(abi.encodePacked(functionsJSONArray, "]")); - } - - function functionalityNames() public override view returns(string memory) { - return functionalityNames(0, _functionalities.length); - } - - function functionalityNames(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, '"', _functionalities[i].codeName, '"', i == length - (_functionalities[i].active ? 1 : 0) ? "" : ",")); - length += _functionalities[i].active ? 0 : 1; - length = length > _functionalities.length ? _functionalities.length : length; - } - functionsJSONArray = string(abi.encodePacked(functionsJSONArray, "]")); - } - - function functionalityToJSON(string memory codeName) public override view returns(string memory) { - return string(toJSON(_functionalities[_indexes[codeName]])); - } - - 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 returns(bool result) { - - 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, ""); - - if(!hasCodeName && !hasReplaces) { - (result,) = IMVDProxy(_proxy).callFromManager(_callingContext = proposal.getLocation(), abi.encodeWithSignature("callOneTime(address)", proposalAddress)); - _callingContext = address(0); - return result; - } - - Functionality memory replacedFunctionality = _functionalities[_indexes[replaces]]; - uint256 position = hasReplaces ? replacedFunctionality.position : _functionalities.length; - - if(hasReplaces) { - (result,) = IMVDProxy(_proxy).callFromManager(_callingContext = replacedFunctionality.location, abi.encodeWithSignature("onStop(address)", proposalAddress)); - _callingContext = address(0); - if(!result) { - revert("onStop failed!"); - } - } - - 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) { - (result,) = IMVDProxy(_proxy).callFromManager(_callingContext = newFunctionality.location, abi.encodeWithSignature("onStart(address,address)", proposalAddress, hasReplaces ? replacedFunctionality.location : address(0))); - _callingContext = address(0); - if(!result) { - revert("onStart failed!"); - } - } - - if(hasCodeName || hasReplaces) { - IMVDProxy(_proxy).emitFromManager(hasCodeName ? codeName : "", proposalAddress, hasReplaces ? replacedFunctionality.codeName : "", hasReplaces ? replacedFunctionality.sourceLocation : address(0), hasReplaces ? replacedFunctionality.sourceLocationId : 0, 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)); - } - _callingContext = address(0); - return true; - } - - 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 deleted file mode 100644 index 4e24ce6..0000000 --- a/MVDFunctionalityModelsManager.sol +++ /dev/null @@ -1,61 +0,0 @@ -pragma solidity ^0.6.0; - -import "./IMVDFunctionalityModelsManager.sol"; -import "./Functionality.sol"; - -contract MVDFunctionalityModelsManager is IMVDFunctionalityModelsManager { - - mapping(string => Functionality) private _wellKnownFunctionalityModels; - - constructor() public { - 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["getVotesHardCap"] = Functionality("getVotesHardCap", address(0), 0, address(0), false, "getVotesHardCap()", '["uint256"]', false, false, address(0), false, 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/MVDFunctionalityProposalManager.sol b/MVDFunctionalityProposalManager.sol deleted file mode 100644 index 4a6abc9..0000000 --- a/MVDFunctionalityProposalManager.sol +++ /dev/null @@ -1,84 +0,0 @@ -pragma solidity ^0.6.0; - -import "./IMVDFunctionalityProposalManager.sol"; -import "./IMVDProxy.sol"; -import "./MVDFunctionalityProposal.sol"; -import "./IMVDFunctionalitiesManager.sol"; - -contract MVDFunctionalityProposalManager is IMVDFunctionalityProposalManager { - - address private _proxy; - - mapping(address => bool) private _proposals; - - 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 { - - bool hasCodeName = !compareStrings(codeName, ""); - bool 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(location == address(0) || !compareStrings(methodSignature, ""), "Cannot have empty string for methodSignature"); - - require(hasCodeName || hasReplaces ? true : compareStrings(methodSignature, "callOneTime(address)"), "One Time Functionality method signature allowed is callOneTime(address)"); - - IMVDFunctionalitiesManager functionalitiesManager = IMVDFunctionalitiesManager(IMVDProxy(_proxy).getMVDFunctionalitiesManagerAddress()); - - require(hasCodeName && functionalitiesManager.hasFunctionality(codeName) ? compareStrings(codeName, replaces) : true, "codeName is already used by another functionality"); - - require(hasReplaces ? functionalitiesManager.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) { - - preconditionCheck(codeName, location, methodSignature, replaces); - - _proposals[proposalAddress] = true; - - 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!"); - - if(!proposal.isVotesHardCapReached()) { - require(block.number >= surveyEndBlock, "Survey is still running!"); - } - - require(!proposal.isTerminated(), "Survey already terminated!"); - } - - function isValidProposal(address proposal) public override view returns (bool) { - return _proposals[proposal]; - } - - 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 deleted file mode 100644 index 9ca8ef3..0000000 --- a/MVDProxy.sol +++ /dev/null @@ -1,312 +0,0 @@ -pragma solidity ^0.6.0; - -import "./IMVDProxy.sol"; -import "./IMVDFunctionalityProposalManager.sol"; -import "./IMVDFunctionalityProposal.sol"; -import "./IERC20.sol"; -import "./IMVDFunctionalityModelsManager.sol"; -import "./ICommonUtilities.sol"; -import "./IMVDFunctionalitiesManager.sol"; -import "./IMVDWallet.sol"; -import "./IERC721.sol"; - -contract MVDProxy is IMVDProxy { - - address[] private _delegates; - - constructor(address votingTokenAddress, address functionalityProposalManagerAddress, address stateHolderAddress, address functionalityModelsManagerAddress, address functionalitiesManagerAddress, address walletAddress, address doubleProxyAddress) public { - if(votingTokenAddress == address(0)) { - return; - } - init(votingTokenAddress, functionalityProposalManagerAddress, stateHolderAddress, functionalityModelsManagerAddress, functionalitiesManagerAddress, walletAddress, doubleProxyAddress); - } - - function init(address votingTokenAddress, address functionalityProposalManagerAddress, address stateHolderAddress, address functionalityModelsManagerAddress, address functionalitiesManagerAddress, address walletAddress, address doubleProxyAddress) public override { - - require(_delegates.length == 0, "Init already called!"); - - _delegates = new address[](7); - - IMVDProxyDelegate(_delegates[0] = votingTokenAddress).setProxy(); - - IMVDProxyDelegate(_delegates[1] = functionalityProposalManagerAddress).setProxy(); - - IMVDProxyDelegate(_delegates[2] = stateHolderAddress).setProxy(); - - _delegates[3] = functionalityModelsManagerAddress; - - IMVDProxyDelegate(_delegates[4] = functionalitiesManagerAddress).setProxy(); - - IMVDProxyDelegate(_delegates[5] = walletAddress).setProxy(); - - IMVDProxyDelegate(_delegates[6] = doubleProxyAddress).setProxy(); - } - - receive() external payable { - revert("No Eth Accepted"); - } - - function getDelegates() public override view returns(address[] memory) { - return _delegates; - } - - function getToken() public override view returns(address) { - return _delegates[0]; - } - - function getMVDFunctionalityProposalManagerAddress() public override view returns(address) { - return _delegates[1]; - } - - function getStateHolderAddress() public override view returns(address) { - return _delegates[2]; - } - - function getMVDFunctionalityModelsManagerAddress() public override view returns(address) { - return _delegates[3]; - } - - function getMVDFunctionalitiesManagerAddress() public override view returns(address) { - return _delegates[4]; - } - - function getMVDWalletAddress() public override view returns(address) { - return _delegates[5]; - } - - function getDoubleProxyAddress() public override view returns(address) { - return _delegates[6]; - } - - function flushToWallet(address tokenAddress, bool is721, uint256 tokenId) public override { - require(IMVDFunctionalitiesManager(_delegates[4]).isAuthorizedFunctionality(msg.sender), "Unauthorized action!"); - if(tokenAddress == address(0)) { - payable(_delegates[5]).transfer(payable(address(this)).balance); - return; - } - if(is721) { - IERC721(tokenAddress).transferFrom(address(this), _delegates[5], tokenId); - return; - } - IERC20 token = IERC20(tokenAddress); - token.transfer(_delegates[5], token.balanceOf(address(this))); - } - - function setDelegate(uint256 position, address newAddress) public override returns(address oldAddress) { - require(IMVDFunctionalitiesManager(_delegates[4]).isAuthorizedFunctionality(msg.sender), "Unauthorized action!"); - require(newAddress != address(0), "Cannot set void address!"); - if(position == 5) { - IMVDWallet(_delegates[5]).setNewWallet(payable(newAddress), _delegates[0]); - } - oldAddress = _delegates[position]; - _delegates[position] = newAddress; - if(position != 3) { - IMVDProxyDelegate(oldAddress).setProxy(); - IMVDProxyDelegate(newAddress).setProxy(); - } - emit DelegateChanged(position, oldAddress, newAddress); - } - - function changeProxy(address newAddress, bytes memory initPayload) public override { - require(IMVDFunctionalitiesManager(_delegates[4]).isAuthorizedFunctionality(msg.sender), "Unauthorized action!"); - require(newAddress != address(0), "Cannot set void address!"); - for(uint256 i = 0; i < _delegates.length; i++) { - if(i != 3) { - IMVDProxyDelegate(_delegates[i]).setProxy(); - } - } - _delegates = new address[](0); - emit ProxyChanged(newAddress); - (bool response,) = newAddress.call(initPayload); - require(response, "New Proxy initPayload failed!"); - } - - function isValidProposal(address proposal) public override view returns (bool) { - return IMVDFunctionalityProposalManager(_delegates[1]).isValidProposal(proposal); - } - - function isAuthorizedFunctionality(address functionality) public override view returns(bool) { - return IMVDFunctionalitiesManager(_delegates[4]).isAuthorizedFunctionality(functionality); - } - - 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); - - IMVDFunctionalitiesManager functionalitiesManager = IMVDFunctionalitiesManager(_delegates[4]); - - IMVDFunctionalityProposal proposal = IMVDFunctionalityProposal(proposalAddress = IMVDFunctionalityProposalManager(_delegates[1]).newProposal(codeName, location, methodSignature, returnAbiParametersArray, replaces)); - proposal.setCollateralData(emergency, sourceLocation, sourceLocationId, submitable, isInternal, needsSender, msg.sender, functionalitiesManager.hasFunctionality("getVotesHardCap") ? toUint256(read("getVotesHardCap", "")) : 0); - - if(functionalitiesManager.hasFunctionality("onNewProposal")) { - submit("onNewProposal", abi.encode(proposalAddress)); - } - - if(!IMVDFunctionalitiesManager(_delegates[4]).hasFunctionality("startProposal") || !IMVDFunctionalitiesManager(_delegates[4]).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(IMVDFunctionalitiesManager(_delegates[4]).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(IMVDFunctionalityProposalManager(_delegates[1]).isValidProposal(proposalAddress), "Invalid Proposal Address!"); - IMVDFunctionalityProposal(proposalAddress).start(); - } - - function disableProposal(address proposalAddress) public override { - require(IMVDFunctionalitiesManager(_delegates[4]).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(); - } - - function transfer(address receiver, uint256 value, address token) public override { - require(IMVDFunctionalitiesManager(_delegates[4]).isAuthorizedFunctionality(msg.sender), "Only functionalities can transfer Proxy balances!"); - IMVDWallet(_delegates[5]).transfer(receiver, value, token); - } - - function transfer721(address receiver, uint256 tokenId, bytes memory data, bool safe, address token) public override { - require(IMVDFunctionalitiesManager(_delegates[4]).isAuthorizedFunctionality(msg.sender), "Only functionalities can transfer Proxy balances!"); - IMVDWallet(_delegates[5]).transfer(receiver, tokenId, data, safe, token); - } - - function setProposal() public override { - - IMVDFunctionalityProposalManager(_delegates[1]).checkProposal(msg.sender); - - emit ProposalCheck(msg.sender); - - IMVDFunctionalitiesManager functionalitiesManager = IMVDFunctionalitiesManager(_delegates[4]); - - (address addressToCall,,string memory methodSignature,,) = functionalitiesManager.getFunctionalityData("checkSurveyResult"); - - (bool surveyResult, bytes memory response) = addressToCall.staticcall(abi.encodeWithSignature(methodSignature, msg.sender)); - - surveyResult = toUint256(response) > 0; - - bool collateralCallResult = true; - (addressToCall,,methodSignature,,) = functionalitiesManager.getFunctionalityData("proposalEnd"); - if(addressToCall != address(0)) { - functionalitiesManager.setCallingContext(addressToCall); - (collateralCallResult,) = addressToCall.call(abi.encodeWithSignature(methodSignature, msg.sender, surveyResult)); - functionalitiesManager.clearCallingContext(); - } - - IMVDFunctionalityProposal proposal = IMVDFunctionalityProposal(msg.sender); - - uint256 staking = 0; - address tokenAddress = _delegates[0]; - address walletAddress = _delegates[5]; - - if(proposal.isEmergency()) { - (addressToCall,,methodSignature,,) = functionalitiesManager.getFunctionalityData("getEmergencySurveyStaking"); - (, response) = addressToCall.staticcall(abi.encodeWithSignature(methodSignature)); - staking = toUint256(response); - } - - if(!surveyResult) { - if(collateralCallResult) { - proposal.set(); - emit ProposalSet(msg.sender, surveyResult); - if(staking > 0) { - IERC20(tokenAddress).transfer(walletAddress, staking); - } - } - return; - } - - if(collateralCallResult) { - try functionalitiesManager.setupFunctionality(msg.sender) returns(bool managerResult) { - collateralCallResult = managerResult; - } catch { - collateralCallResult = false; - } - } - - if(collateralCallResult) { - proposal.set(); - emit ProposalSet(msg.sender, surveyResult); - if(staking > 0) { - IERC20(tokenAddress).transfer(surveyResult ? proposal.getProposer() : walletAddress, staking); - } - } - } - - 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) { - - if(msg.value > 0) { - payable(_delegates[5]).transfer(msg.value); - } - - 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 Functionalities Manager can call this!"); - return location.call(payload); - } - - function emitFromManager(string memory codeName, address proposal, string memory replaced, address replacedSourceLocation, uint256 replacedSourceLocationId, address location, bool submitable, string memory methodSignature, bool isInternal, bool needsSender, address proposalAddress) public override { - require(msg.sender == _delegates[4], "Only Functionalities Manager can call this!"); - emit FunctionalitySet(codeName, proposal, replaced, replacedSourceLocation, replacedSourceLocationId, location, submitable, methodSignature, isInternal, needsSender, proposalAddress); - } - - function emitEvent(string memory eventSignature, bytes memory firstIndex, bytes memory secondIndex, bytes memory data) public override { - require(IMVDFunctionalitiesManager(_delegates[4]).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 index 628ca16..73c6384 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Decentralized Flexible Organization - DFO -## Microservices on Ethereum 👽 for Independent Dapps ugradable by voting 😱 +## Microservices on Ethereum 👽 for Independent Dapps upgradable by voting 😱 ##### Authors: Marco Vasapollo ([@vasapower](https://twitter.com/vasapower_9)) and Alessandro Mario Laganà Toschi ([@alessandromlt](https://twitter.com/alessandromlt)) | diff --git a/StateHolder.sol b/StateHolder.sol deleted file mode 100644 index 183a7f5..0000000 --- a/StateHolder.sol +++ /dev/null @@ -1,191 +0,0 @@ -pragma solidity ^0.6.0; - -import "./IMVDProxy.sol"; -import "./CommonUtilities.sol"; -import "./IStateHolder.sol"; -import "./IMVDFunctionalitiesManager.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 { - init(); - } - - function init() public override { - require(_state.length == 0, "Init already called!"); - _state.push(Var("", DataType.BYTES, "", 0, false)); - } - - modifier canSet { - require(_state.length > 0, "Not Initialized!"); - if(_proxy != address(0)) { - require(IMVDFunctionalitiesManager(IMVDProxy(_proxy).getMVDFunctionalitiesManagerAddress()).isAuthorizedFunctionality(msg.sender), "UnauthorizedAccess"); - } - _; - } - - function toJSON() public override view returns(string memory) { - return toJSON(0, _state.length - 1); - } - - function toJSON(uint256 start, uint256 l) public override view returns(string memory json) { - uint256 length = start + 1 + l; - json = "["; - for(uint256 i = start; i < length; i++) { - json = !_state[i].active ? json : string(abi.encodePacked(json, '{"name":"', _state[i].name, '","type":"', toString(_state[i].dataType), '"}', i == length - (_state[i].active ? 1 : 0) ? "" : ",")); - length += _state[i].active ? 0 : 1; - length = length > _state.length ? _state.length : length; - } - json = string(abi.encodePacked(json, ']')); - } - - 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 canSet returns(bytes memory oldVal) { - 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 canSet override returns(string memory oldDataType, bytes memory oldVal) { - 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 deleted file mode 100644 index 7da22d4..0000000 --- a/VotingToken.sol +++ /dev/null @@ -1,146 +0,0 @@ -pragma solidity ^0.6.0; - -import "./IMVDProxy.sol"; -import "./IERC20.sol"; -import "./IVotingToken.sol"; -import "./IMVDFunctionalityProposalManager.sol"; -import "./IMVDFunctionalitiesManager.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 { - 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) { - _transfer(msg.sender, recipient, amount); - return true; - } - - function allowance(address owner, address spender) public override view returns (uint256) { - return _allowances[owner][spender]; - } - - function approve(address spender, uint256 amount) public override returns (bool) { - _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(_proxy == address(0) || !(IMVDFunctionalityProposalManager(IMVDProxy(_proxy).getMVDFunctionalityProposalManagerAddress()).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(IMVDFunctionalitiesManager(IMVDProxy(_proxy).getMVDFunctionalitiesManagerAddress()).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 diff --git a/contracts/CommonUtilities.sol b/contracts/CommonUtilities.sol new file mode 100644 index 0000000..1143ae9 --- /dev/null +++ b/contracts/CommonUtilities.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.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 (uint256 i = 0; i < 20; i++) { + str[2 + i * 2] = alphabet[uint256(uint8(value[i + 12] >> 4))]; + str[3 + i * 2] = alphabet[uint256(uint8(value[i + 12] & 0x0f))]; + } + return string(str); + } + + function toString(uint256 _i) public override pure returns (string memory) { + if (_i == 0) { + return "0"; + } + uint256 j = _i; + uint256 len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint256 k = len - 1; + while (_i != 0) { + bstr[k--] = bytes1(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 (uint256 i = 0; i < bStr.length; i++) { + bStr[i] = bStr[i] >= 0x41 && bStr[i] <= 0x5A + ? bytes1(uint8(bStr[i]) + 0x20) + : bStr[i]; + } + return string(bStr); + } +} diff --git a/DoubleProxy.sol b/contracts/DoubleProxy.sol similarity index 55% rename from DoubleProxy.sol rename to contracts/DoubleProxy.sol index e791b39..5ef576a 100644 --- a/DoubleProxy.sol +++ b/contracts/DoubleProxy.sol @@ -1,9 +1,9 @@ -pragma solidity ^0.6.0; +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; import "./IDoubleProxy.sol"; contract DoubleProxy is IDoubleProxy { - address private _proxy; mapping(address => bool) private _isProxy; @@ -14,57 +14,70 @@ contract DoubleProxy is IDoubleProxy { init(proxies, currentProxy); } - function init(address[] memory proxies, address currentProxy) public override { + function init(address[] memory proxies, address currentProxy) + public + override + { require(_proxies.length == 0, "Init already called!"); - for(uint256 i = 0; i < proxies.length; i++) { - if(proxies[i] != address(0)) { + for (uint256 i = 0; i < proxies.length; i++) { + if (proxies[i] != address(0)) { _proxies.push(proxies[i]); _isProxy[proxies[i]] = true; } } - if(currentProxy != address(0)) { + if (currentProxy != address(0)) { _proxy = currentProxy; - if(!_isProxy[currentProxy]) { + if (!_isProxy[currentProxy]) { _proxies.push(currentProxy); _isProxy[currentProxy] = true; } } } - function proxy() public override view returns(address) { + function proxy() 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); - if(_proxy != address(0) && !_isProxy[_proxy]) { + 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); + if (_proxy != address(0) && !_isProxy[_proxy]) { _proxies.push(_proxy); _isProxy[_proxy] = true; } } - function isProxy(address addr) public override view returns(bool) { + function isProxy(address addr) public override view returns (bool) { return _isProxy[addr]; } - function proxiesLength() public override view returns(uint256) { + function proxiesLength() public override view returns (uint256) { return _proxies.length; } - function proxies() public override view returns(address[] memory) { + function proxies() public override view returns (address[] memory) { return proxies(0, _proxies.length); } - function proxies(uint256 start, uint256 offset) public override view returns(address[] memory out) { + function proxies(uint256 start, uint256 offset) + public + override + view + returns (address[] memory out) + { require(start < _proxies.length, "Invalid start"); uint256 length = offset > _proxies.length ? _proxies.length : offset; out = new address[](length); length += start; length = length > _proxies.length ? _proxies.length : length; uint256 pos = 0; - for(uint256 i = start; i < length; i++) { + for (uint256 i = start; i < length; i++) { out[pos++] = _proxies[i]; } } -} \ No newline at end of file +} diff --git a/Functionality.sol b/contracts/Functionality.sol similarity index 76% rename from Functionality.sol rename to contracts/Functionality.sol index 450e525..20e4746 100644 --- a/Functionality.sol +++ b/contracts/Functionality.sol @@ -1,11 +1,13 @@ -pragma solidity ^0.6.0; +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; +// DOCUMENT struct Functionality { string codeName; address sourceLocation; uint256 sourceLocationId; address location; - bool submitable; + bool submittable; string methodSignature; string returnAbiParametersArray; bool isInternal; @@ -13,4 +15,4 @@ struct Functionality { address proposalAddress; bool active; uint256 position; -} \ No newline at end of file +} diff --git a/contracts/ICommonUtilities.sol b/contracts/ICommonUtilities.sol new file mode 100644 index 0000000..2a7ea96 --- /dev/null +++ b/contracts/ICommonUtilities.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +/** + * @title Common Utilities + * @dev Collection of simple utilities + */ +interface ICommonUtilities { + /** + * @dev Convert address to string + */ + function toString(address _addr) external pure returns (string memory); + + /** + * @dev Convert uint to string + */ + function toString(uint256 _i) external pure returns (string memory); + + /** + * @dev Convert bytes address to uint + */ + function toUint256(bytes calldata bs) external pure returns (uint256 x); + + /** + * @dev Convert bytes address to address + */ + function toAddress(bytes calldata b) external pure returns (address addr); + + /** + * @dev Compare to strings + */ + function compareStrings(string calldata a, string calldata b) + external + pure + returns (bool); + + // DOCUMENT + function getFirstJSONPart( + address sourceLocation, + uint256 sourceLocationId, + address location + ) external pure returns (bytes memory); + + // DOCUMENT + function formatReturnAbiParametersArray(string calldata m) + external + pure + returns (string memory); + + /** + * @dev Convert a string to lowercase + */ + function toLowerCase(string calldata str) + external + pure + returns (string memory); +} diff --git a/contracts/IDoubleProxy.sol b/contracts/IDoubleProxy.sol new file mode 100644 index 0000000..408e6e3 --- /dev/null +++ b/contracts/IDoubleProxy.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +/** + * @title Double Proxy Interface + * @dev Double Proxy is the easiest way for side/external Contracts to locate and query the DFO. + * As the main Core Contract of a DFO is the Proxy, it also can be updated by Proposals. + * So, directly link the Proxy to external Contracts can be really a problem. + * To avoid this, it is better to link external contracts with the DoubleProxy. + * DoubleProxy is the Proxy of the Proxy, and is a Delegate that keeps track of the most recent Proxy address and all the other previous Proxies. + * Because it has a very lightweight and simple structure and logic, it does not need of changes, so it can be used as a secure anchor. + */ +interface IDoubleProxy { + /** + * @dev Initializer logic used during the constructor call + * @param proxies Array of address of the old proxies, only used in legacy scenarios. Can be left empty. + * @param currentProxy Address of the current Proxy + */ + function init(address[] calldata proxies, address currentProxy) external; + + /** + * @dev GET the current Proxy + */ + function proxy() external view returns (address); + + /** + * @dev Method callable by the current Proxy only. It is used when the Proxy or this delegate changes. + */ + function setProxy() external; + + /** + * @dev Check if the address is or has been a Proxy + */ + function isProxy(address) external view returns (bool); + + /** + * @dev GET the number of proxies + */ + function proxiesLength() external view returns (uint256); + + /** + * @dev Retrieve a portion of the proxies + * @param start Start Position + * @param offset End Position + */ + function proxies(uint256 start, uint256 offset) + external + view + returns (address[] memory); + + /** + * @dev Retrieve all the proxies + */ + function proxies() external view returns (address[] memory); +} diff --git a/contracts/IERC20.sol b/contracts/IERC20.sol new file mode 100644 index 0000000..052555b --- /dev/null +++ b/contracts/IERC20.sol @@ -0,0 +1,31 @@ +pragma solidity >=0.7.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 + ); +} diff --git a/contracts/IERC721.sol b/contracts/IERC721.sol new file mode 100644 index 0000000..a5395ab --- /dev/null +++ b/contracts/IERC721.sol @@ -0,0 +1,18 @@ +pragma solidity >=0.7.0; + +interface IERC721 { + function ownerOf(uint256 _tokenId) external view returns (address); + + function transferFrom( + address _from, + address _to, + uint256 _tokenId + ) external payable; + + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; +} diff --git a/contracts/IERC721Receiver.sol b/contracts/IERC721Receiver.sol new file mode 100644 index 0000000..84fe3b5 --- /dev/null +++ b/contracts/IERC721Receiver.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +interface IERC721Receiver { + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); +} diff --git a/contracts/IMVDFunctionalitiesManager.sol b/contracts/IMVDFunctionalitiesManager.sol new file mode 100644 index 0000000..4ffe997 --- /dev/null +++ b/contracts/IMVDFunctionalitiesManager.sol @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +/** + * @title Interface for the Functionalities Manager + * @dev Functionalities Manager is the one that keeps track of all the Microservices of a DFO. + * It also contains all the logic to set/unset Microservices after a Proposal. + */ +interface IMVDFunctionalitiesManager { + /** + * @dev GET the Proxy + */ + function getProxy() external view returns (address); + + /** + * @dev SET the Proxy + */ + function setProxy() external; + + /** + * @dev Initializer logic used during the constructor call + * @param sourceLocation Location of the source code, saved in concatenated Base64 data chunks + * @param getMinimumBlockNumberSourceLocationId Base64 data chunk id of the corresponding Microservice + * @param getMinimumBlockNumberFunctionalityAddress Address of the Functionality that controls + * the block duration of regular proposals + * @param getEmergencyMinimumBlockNumberSourceLocationId Base64 data chunk id of the corresponding Microservice + * @param getEmergencyMinimumBlockNumberFunctionalityAddress Address for the Functionality that controls + * the block duration of emergency proposals + * @param getEmergencySurveyStakingSourceLocationId Base64 data chunk id of the corresponding Microservice + * @param getEmergencySurveyStakingFunctionalityAddress Address for the Functionality that controls the + * minimum amount of vote to be staked in order to start an Emergency Proposal + * @param checkVoteResultSourceLocationId Base64 data chunk id of the corresponding Microservice + * @param checkVoteResultFunctionalityAddress Address for the Functionality that controls the + * check for determining if a Proposal was successful or it failed + */ + function init( + address sourceLocation, + uint256 getMinimumBlockNumberSourceLocationId, + address getMinimumBlockNumberFunctionalityAddress, + uint256 getEmergencyMinimumBlockNumberSourceLocationId, + address getEmergencyMinimumBlockNumberFunctionalityAddress, + uint256 getEmergencySurveyStakingSourceLocationId, + address getEmergencySurveyStakingFunctionalityAddress, + uint256 checkVoteResultSourceLocationId, + address checkVoteResultFunctionalityAddress + ) external; + + /** + * @dev Add a functionality to the Functionalities Manager + * @param codeName ID of the microservice, to be called by the user through Proxy. + * @param sourceLocation Location of the source code, saved in concatenated Base64 data chunks + * @param sourceLocationId Base64 data chunk id of the corresponding Microservice + * @param location Address of the functionality/microservice to call + * @param submittable Boolean flag controlling wether the microservice writes data to the chain + * @param methodSignature Name of the method of the microservice you want to call + * @param returnAbiParametersArray Array of return values obtained from the called microservice's method + * @param isInternal Boolean flag controlling wether the microservice can be called from anyone (false) or + * can be called only by other microservices (true) + * @param needsSender All microservices calls are made by the Proxy, with this boolean flag you can + * forward the address that called the Proxy in the first place + */ + function addFunctionality( + string calldata codeName, + address sourceLocation, + uint256 sourceLocationId, + address location, + bool submittable, + string calldata methodSignature, + string calldata returnAbiParametersArray, + bool isInternal, + bool needsSender + ) external; + + /** + * @dev Replace a Functionality in the Functionalities Manager + * @param codeName ID of the microservice, to be called by the user through Proxy. + * @param sourceLocation Location of the source code, saved in concatenated Base64 data chunks + * @param sourceLocationId Base64 data chunk id of the corresponding Microservice + * @param location Address of the functionality/microservice to call + * @param submittable Boolean flag controlling wether the microservice writes data to the chain + * @param methodSignature Name of the method of the microservice you want to call + * @param returnAbiParametersArray Array of return values obtained from the called microservice's method + * @param isInternal Boolean flag controlling wether the microservice can be called from anyone (false) or + * can be called only by other microservices (true) + * @param needsSender All microservices calls are made py the Proxy, with this boolean flag you can + * forward the address that called the Proxy in the first place + * @param position Position of the Functionality to replace + */ + function addFunctionality( + string calldata codeName, + address sourceLocation, + uint256 sourceLocationId, + address location, + bool submittable, + string calldata methodSignature, + string calldata returnAbiParametersArray, + bool isInternal, + bool needsSender, + uint256 position + ) external; + + /** + * @dev Remove a Functionality from the Functionalities Manager + * @param codeName ID of the Functionality to remove + * @return removed Boolean flag representing the success status of the operation + * @return position Position of the removed functionality + */ + function removeFunctionality(string calldata codeName) + external + returns (bool removed, uint256 position); + + /** + * @dev Check that the Functionality is a valid one + * @param functionality Functionality to be checked + * @return valid Boolean flag indicating the validity of the Functionality + */ + function isValidFunctionality(address functionality) external view returns (bool valid); + + /** + * @dev Check that the Functionality is an authorized one + * @param functionality Functionality to be checked + * @return success Boolean flag indicating the authorization status of the Functionality + */ + function isAuthorizedFunctionality(address functionality) external view returns (bool success); + + /** + * @dev This method can be called only by the Proxy. + * When a new submitable Microservice is called, this method is used to let other DFO Delegates (e.g. StateHolder) to be fully operative. + * If you call a Microservice directly, bypassing the Proxy, the context will be blank and Delegates cannot allow you to do any operation. + * @param location The address of the currently running Microservice + * @return changed True if the calling context is correctly set, false if the context was already set (this happens, for example, when someone calls a Microservice including a logic to call another Microservice through the Proxy). + */ + function setCallingContext(address location) external returns (bool changed); + + /** + * @dev This method can be called only by the Proxy. + * Clears the context at the end of the Microservice execution + */ + function clearCallingContext() external; + + /** + * @dev Utility method to retrieve all important stuff to call a Microservice + * @param codeName the codeName of the Microservice you need info + * @return the address of the contract including the logic of the Microservice, the method signature of the Microservice, the position in the Functionalities array, the location of the source code, saved in byte64 concatenated data chunks, the locationId of the source code. + */ + function getFunctionalityData(string calldata codeName) + external + view + returns ( + address, + uint256, + string memory, + address, + uint256 + ); + + /** + * @dev Check that the FunctionalitiesManager has a specific Functionality + * @param codeName ID of the Functionality to be checked + * @return output Boolean flag indicating wether the Functionalities Manager has the given Functionality. + */ + function hasFunctionality(string calldata codeName) external view returns (bool output); + + /** + * @dev GET the amount of functionalities present in the Functionalities Manager + */ + function getFunctionalitiesAmount() external view returns (uint256); + + /** + * @dev For frontend purposes. Gives back the info about functionalities using the JSON Array format + */ + function functionalitiesToJSON() external view returns (string memory); + + /** + * @dev For frontend purposes. Gives back the info about functionalities using the JSON Array format + * @param start the start position of the array + * @param l the array offset + */ + function functionalitiesToJSON(uint256 start, uint256 l) + external + view + returns (string memory functionsJSONArray); + + /** + * @dev GET all Functionalities Names (IDs) + */ + function functionalityNames() external view returns (string memory); + + /** + * @dev GET all Functionalities Names (IDs) in a portion of the array + */ + function functionalityNames(uint256 start, uint256 l) + external + view + returns (string memory functionsJSONArray); + + /** + * @dev Given a Functionality ID return its JSON encoded version + */ + function functionalityToJSON(string calldata codeName) external view returns (string memory); + + /** + * @dev Method called by the Proxy when someone calls a Microservice. + * It has a double function: checks if you are in the correct context (e.g. are you trying to call a non-submitable Microservice through the correct "read" function of the Proxy?) + * and gives back the address of the Microservice and the correct payload that the proxy will use to execute a .call() method. + * @param codeName the Microservice to be called + * @param data the payload to be used within the Microservice (ABI encoded) + * @param submittable 1 true, 0 false + * @param sender the original msg.sender of the Proxy read/submit call, to be used if the Microservice has the needsSender flag set to true + * @param value the original msg.value of the Proxy submit call, to be used if the Microservice is submitable and has the needsSender flag set to true + */ + function preConditionCheck( + string calldata codeName, + bytes calldata data, + uint8 submittable, + address sender, + uint256 value + ) external view returns (address location, bytes memory payload); + + /** + * @dev callable by the Proxy only. + * Sets up the new Microservice add/replace/remove action, grabbing the data from the MVDFunctionalityProposal at the given address + * @param proposalAddress the address of the Proposal to be set + */ + function setupFunctionality(address proposalAddress) external returns (bool); +} diff --git a/contracts/IMVDFunctionalityModelsManager.sol b/contracts/IMVDFunctionalityModelsManager.sol new file mode 100644 index 0000000..d6ee7da --- /dev/null +++ b/contracts/IMVDFunctionalityModelsManager.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +/** + * @title Functionalities Models Manager + * @dev Well Known Functionalities are "special" Functionalities/Microservices must be implemented according to + * a specific pattern. Well Known Functionalities can be found in the implementation of this Interface. + */ +interface IMVDFunctionalityModelsManager { + function init() external; + + /** + * @dev Check Well Known Functionalities. If the check fails it will raise its own errors. + * @param codeName ID of the microservice, to be called by the user through Proxy. + * @param submittable Boolean flag controlling wether the microservice writes data to the chain + * @param methodSignature Name of the method of the microservice you want to call + * @param returnAbiParametersArray Array of return values obtained from the called microservice's method + * @param isInternal Boolean flag controlling wether the microservice can be called from anyone (false) or + * can be called only by other microservices (true) + * @param needsSender All microservices calls are made py the Proxy, with this boolean flag you can + * forward the address that called the Proxy in the first place + * @param replaces codeName of the microservice that will be replaced by the Proposal to be created, can be blank. + */ + function checkWellKnownFunctionalities( + string calldata codeName, + bool submittable, + string calldata methodSignature, + string calldata returnAbiParametersArray, + bool isInternal, + bool needsSender, + string calldata replaces + ) external view; +} diff --git a/contracts/IMVDFunctionalityProposal.sol b/contracts/IMVDFunctionalityProposal.sol new file mode 100644 index 0000000..eecfd75 --- /dev/null +++ b/contracts/IMVDFunctionalityProposal.sol @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +/** + * @title Interface for Proposal + * @dev Proposals are the defacto heart of the protocol since they allow voting token holders potentially + * alter all of the logic of a DFO and even extend it via custom logic. + */ +interface IMVDFunctionalityProposal { + /** + * @dev Functionality Initializer + * @param codeName ID of the microservice, to be called by the user through Proxy, can be blank. + * @param location Address of the functionality/microservice to call + * @param methodSignature Name of the method of the microservice you want to call + * @param returnAbiParametersArray Array of return values obtained from the called microservice's method + * @param replaces codeName of the microservice that will be replaced by this Proposal, can be blank. + * @param proxy Address of the proxy + */ + function init( + string calldata codeName, + address location, + string calldata methodSignature, + string calldata returnAbiParametersArray, + string calldata replaces, + address proxy + ) external; + + /** + * @dev set the collateral attributes of the proposal + * @param emergency Bool flag controlling wether this is a standard or emergency proposal + * @param sourceLocation Location of the source code, saved in concatenated Base64 data chunks + * @param sourceLocationId Base64 data chunk id of the corresponding Microservice + * @param submittable Boolean flag controlling wether the microservice writes data to the chain + * @param isInternal Boolean flag controlling wether the microservice can be called from anyone (false) or + * can be called only by other microservices (true) + * @param needsSender All microservices calls are made py the Proxy, with this boolean flag you can + * forward the address that called the Proxy in the first place + * @param proposer Address of the proposer + * @param votesHardCap Hardcap value + */ + function setCollateralData( + bool emergency, + address sourceLocation, + uint256 sourceLocationId, + bool submittable, + bool isInternal, + bool needsSender, + address proposer, + uint256 votesHardCap + ) external; + + /** + * @dev GET the Proxy address + */ + function getProxy() external view returns (address); + + /** + * @dev GET the Proposal Functionality string ID + */ + function getCodeName() external view returns (string memory); + + /** + * @dev GET the boolean flag indicating wether the Proposal is an Emergency Proposal + */ + function isEmergency() external view returns (bool); + + /** + * @dev GET the Location of the source code, saved in concatenated Base64 data chunks + */ + function getSourceLocation() external view returns (address); + + /** + * @dev GET the Base64 data chunk id of the corresponding Microservice + */ + function getSourceLocationId() external view returns (uint256); + + /** + * @dev GET address of the microservice + */ + function getLocation() external view returns (address); + + /** + * @dev GET the boolean flag controlling wether the microservice writes data to the chain + */ + function issubmittable() external view returns (bool); + + /** + * @dev GET the name of the microservice method to invoke + */ + function getMethodSignature() external view returns (string memory); + + /** + * @dev GET the array of return values obtained from the called microservice's method + */ + function getReturnAbiParametersArray() external view returns (string memory); + + /** + * @dev GET the boolean flag controlling wether the microservice can be called from anyone (false) or + * can be called only by other microservices (true) + */ + function isInternal() external view returns (bool); + + /** + * @dev GET the boolean flag controlling wether the original Proxy caller address should be forwarded or not + */ + function needsSender() external view returns (bool); + + /** + * @dev codeName of the microservice that will be replaced by this Proposal, can be blank. + */ + function getReplaces() external view returns (string memory); + + /** + * @dev GET the address of the proposer + */ + function getProposer() external view returns (address); + + /** + * @dev GET The proposal end block + */ + function getSurveyEndBlock() external view returns (uint256); + + /** + * @dev GET the duration of the Proposal in number of blocks + */ + function getSurveyDuration() external view returns (uint256); + + /** + * @dev Check that the HardCap has been reached + */ + function isVotesHardCapReached() external view returns (bool); + + /** + * @dev GET the HardCap value + */ + function getVotesHardCapToReach() external view returns (uint256); + + /** + * @dev GET the json representation of the Functionality + */ + function toJSON() external view returns (string memory); + + /** + * @dev GET the current status of the voting of the given address + * @param addr The address of the voter you want to know status + * @return accept Amount of YES votes + * @return refuse Amount of NO votes + */ + function getVote(address addr) external view returns (uint256 accept, uint256 refuse); + + /** + * @dev Get the current votes status + * @return accept Amount of YES votes + * @return refuse Amount of NO votes + */ + function getVotes() external view returns (uint256, uint256); + + /** + * @dev Can be used by external Proposal Managers to delay the Survey Start + */ + function start() external; + + /** + * @dev Can be used by external Proposal Managers to disable not-yet started Surveys + */ + function disable() external; + + /** + * @dev Check if a Proposal was canceled before its start + */ + function isDisabled() external view returns (bool); + + /** + * @dev Check if the Proposal reached the natural time termination or the reachement of the Hard Cap. + */ + function isTerminated() external view returns (bool); + + /** + * @dev Vote to accept the Proposal, staking your voting tokens. Can be called only if the Proposal is still running. + */ + function accept(uint256 amount) external; + + /** + * @dev Retire your votes. Can be called only if the Proposal is still running. + */ + function retireAccept(uint256 amount) external; + + /** + * @dev Move some "refuse" votes to "accept". Can be called only if the Proposal is still running. + */ + function moveToAccept(uint256 amount) external; + + /** + * @dev Vote to refuse the Proposal, staking your voting tokens. Can be called only if the Proposal is still running. + */ + function refuse(uint256 amount) external; + + /** + * @dev Retire your votes. Can be called only if the Proposal is still running. + */ + function retireRefuse(uint256 amount) external; + + /** + * @dev Move some "accept" votes to "refuse". Can be called only if the Proposal is still running. + */ + function moveToRefuse(uint256 amount) external; + + /** + * @dev Retire all your votes, retreiving back your staked voting tokens. Can be called only if the Proposal is still running. + */ + function retireAll() external; + + /** + * @dev Withdraw all the token you staked. Can be called only if the Proposal reaches the Hard Cap or the final Block. + */ + function withdraw() external; + + /** + * @dev Force the Proposal to call the Proxy and execute the finalization operations. Can be called only if the Proposal reaches the Hard Cap or the final Block. + */ + function terminate() external; + + /** + * @dev Callable by the Proxy only. Marks this Proposal as definitively terminate. User can still call the withdrawAll() function to withdraw the tokens, of course. + */ + 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); +} diff --git a/contracts/IMVDFunctionalityProposalManager.sol b/contracts/IMVDFunctionalityProposalManager.sol new file mode 100644 index 0000000..130987b --- /dev/null +++ b/contracts/IMVDFunctionalityProposalManager.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +/** + * @title Functionality Proposal Manager + * @dev Microservice for adding new proposals + */ +interface IMVDFunctionalityProposalManager { + /** + * @dev Add a new Proposal + * Work explanation: + * codeName is set and replaces is blank: the Proposal will add a new Microservice/Functionality + * codeName is blank and replaces is set: the Proposal will remove an existing Microservice/Functionality + * codeName and replaces are both set: the Proposal will replace an existing Functionality/Microservice with a new one + * codeName and replaces are both blank: the Proposal represents a One-Time Functionality, that will be executed just one time, if the Token Holders Pccepts this proposal. + * @param codeName ID of the microservice, to be called by the user through Proxy, can be blank. + * @param location Address of the functionality/microservice to call + * @param methodSignature Name of the method of the microservice you want to call + * @param returnAbiParametersArray Array of return values obtained from the called microservice's method + * @param replaces codeName of the microservice that will be replaced by this Proposal, can be blank. + * @return proposal Address of the newly added proposal + */ + function newProposal( + string calldata codeName, + address location, + string calldata methodSignature, + string calldata returnAbiParametersArray, + string calldata replaces + ) external returns (address proposal); + + /** + * @dev Callable by the Proxy only. + * Contains the logic to check if a proposal is ready to be finalized (e.g. last block reached or hard cap reached). + */ + function checkProposal(address proposalAddress) external; + + /** + * @dev GET the Proxy contract address + */ + function getProxy() external view returns (address); + + /** + * @dev SET the Proxy contract address + */ + function setProxy() external; + + /** + * @dev Check that a proposal is valid + * @param proposal Address of the proposal to check + * @return isValid Boolean indicating the validity of the function + */ + function isValidProposal(address proposal) external view returns (bool isValid); +} diff --git a/contracts/IMVDProxy.sol b/contracts/IMVDProxy.sol new file mode 100644 index 0000000..639bdc5 --- /dev/null +++ b/contracts/IMVDProxy.sol @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +/** + * // DOCUMENT Add detailed explanation of what a proxy is + * @title Proxy Interface + */ +interface IMVDProxy { + /** + * @dev Initialization logic used during the constructor call + * @param votingTokenAddress Address of the Voting Token + * @param functionalityProposalManagerAddress Address of the Functionality Proposal Manager + * @param stateHolderAddress Address of the State Holder contract + * @param functionalityModelsManagerAddress Address of the Functionality Models Manager + * @param functionalitiesManagerAddress Address of the Functionalities Manager + * @param walletAddress Address of the wallet + * @param doubleProxyAddress Address of the double proxy + */ + function init( + address votingTokenAddress, + address functionalityProposalManagerAddress, + address stateHolderAddress, + address functionalityModelsManagerAddress, + address functionalitiesManagerAddress, + address walletAddress, + address doubleProxyAddress + ) external; + + function getDelegates() external view returns (address[] memory); + + /** + * @dev GET the voting token contract address + */ + function getToken() external view returns (address); + + /** + * @dev GET the Functionality Proposal Manager contract address + */ + function getMVDFunctionalityProposalManagerAddress() external view returns (address); + + /** + * @dev GET the State Holder contract address + */ + function getStateHolderAddress() external view returns (address); + + /** + * @dev GET the Functionality Models Manager contract address + */ + function getMVDFunctionalityModelsManagerAddress() external view returns (address); + + /** + * @dev GET the Functionalities Manager contract address + */ + function getMVDFunctionalitiesManagerAddress() external view returns (address); + + /** + * @dev GET the Wallet contract address + */ + function getMVDWalletAddress() external view returns (address); + + /** + * @dev GET the Double Proxy contract address + */ + function getDoubleProxyAddress() external view returns (address); + + function setDelegate(uint256 position, address newAddress) + external + returns (address oldAddress); + + function changeProxy(address newAddress, bytes calldata initPayload) external; + + function isValidProposal(address proposal) external view returns (bool); + + function isAuthorizedFunctionality(address functionality) external view returns (bool); + + /** + * @dev Add a new proposal + * @param codeName ID of the microservice, to be called by the user through Proxy, can be blank. + * @param emergency Boolean, true -> Emergency Proposal, false -> Standard Proposal + * @param sourceLocation Location of the source code, saved in concatenated Base64 data chunks + * @param sourceLocationId Base64 data chunk id of the corresponding Microservice + * @param location Address of the functionality/microservice to call + * @param submittable Boolean flag controlling wether the microservice writes data to the chain + * @param methodSignature Name of the method of the microservice you want to call + * @param returnParametersJSONArray Array of json encoded return parameters of the proposal + * @param isInternal Boolean flag controlling wether the microservice can be called from anyone (false) or + * can be called only by other microservices (true) + * @param needsSender All microservices calls are made py the Proxy, with this boolean flag you can + * @param replaces codeName of the microservice that will be replaced by this Proposal, can be blank. + * @return proposalAddress Address of the newly created proposal + */ + function newProposal( + string calldata codeName, + bool emergency, + address sourceLocation, + uint256 sourceLocationId, + address location, + bool submittable, + string calldata methodSignature, + string calldata returnParametersJSONArray, + bool isInternal, + bool needsSender, + string calldata replaces + ) external returns (address proposalAddress); + + /** + * @dev Can be used by external Proposal Managers to delay the Survey Start + */ + function startProposal(address proposalAddress) external; + + /** + * @dev Can be used by external Proposal Managers to disable not-yet started Surveys + */ + function disableProposal(address proposalAddress) external; + + /** + * @dev Transfer a token to an address + * @param receiver Address of the receiver + * @param value Amount of token to transfer + * @param token Address of the token to transfer + */ + function transfer( + address receiver, + uint256 value, + address token + ) external; + + /** + * @dev Transfer an ERC721 to an address + * @param receiver Address of the receiver + * @param tokenId ID of the ERC721 to transfer + * @param data The optional payload to pass in the safeTransferFrom function + * @param safe Boolean flag for triggering the SafeTransfer + * @param token Address of the token to transfer + */ + function transfer721( + address receiver, + uint256 tokenId, + bytes calldata data, + bool safe, + address token + ) external; + + /** + * @dev Utility public method callable by everyone to send all ether/tokens/NFT accidentally sent to the Proxy. + * It flushes all in the DFO Wallet + * @param tokenAddress the ERC20/ERC721 token to transfer. address(0) means flush ether + * @param is721 tokenAddress is 721 or ERC20 + * @param tokenId the id of the eventual ERC721 Token to transfer + */ + function flushToWallet( + address tokenAddress, + bool is721, + uint256 tokenId + ) external; + + /** + * @dev Callable by the Proposals only. Starts the Proposal finalization procedure + */ + function setProposal() external; + + /** + * @dev Call a non-submitable (readonly function marked as pure or view) Microservice + * @param codeName the ID of the Microservice to be called + * @param data ABI encoded data payload to be passed to the Microservice + */ + function read(string calldata codeName, bytes calldata data) + external + view + returns (bytes memory returnData); + + /** + * @dev Call a submitable (which writes on the Blockchain State) Microservice + * @param codeName the ID of the Microservice to be called + * @param data ABI encoded data payload to be passed to the Microservice + */ + function submit(string calldata codeName, bytes calldata data) + external + payable + returns (bytes memory returnData); + + /** + * @dev callable by the MVDFunctionalitiesManager only. + * Calls a Microservice using the Proxy as msg.sender + */ + function callFromManager(address location, bytes calldata payload) + external + returns (bool, bytes memory); + + /** + * @dev callable by the MVDFunctionalitiesManager only. + * Emits the FunctionalitySet event by the Proxy + */ + function emitFromManager( + string calldata codeName, + address proposal, + string calldata replaced, + address replacedSourceLocation, + uint256 replacedSourceLocationId, + address location, + bool submittable, + string calldata methodSignature, + bool isInternal, + bool needsSender, + address proposalAddress + ) external; + + /** + * @dev callable by Microservices only. + * Emits the general purpose "Event" event by the Proxy + */ + function emitEvent( + string calldata eventSignature, + bytes calldata firstIndex, + bytes calldata secondIndex, + bytes calldata data + ) external; + + event ProxyChanged(address indexed newAddress); + event DelegateChanged(uint256 position, address indexed oldAddress, address indexed newAddress); + + event Proposal(address proposal); + event ProposalCheck(address indexed proposal); + event ProposalSet(address indexed proposal, bool success); + event FunctionalitySet( + string codeName, + address indexed proposal, + string replaced, + address replacedSourceLocation, + uint256 replacedSourceLocationId, + address indexed replacedLocation, + bool replacedWassubmittable, + string replacedMethodSignature, + bool replacedWasInternal, + bool replacedNeededSender, + address indexed replacedProposal + ); + + event Event( + string indexed key, + bytes32 indexed firstIndex, + bytes32 indexed secondIndex, + bytes data + ); +} diff --git a/contracts/IMVDWallet.sol b/contracts/IMVDWallet.sol new file mode 100644 index 0000000..46d0165 --- /dev/null +++ b/contracts/IMVDWallet.sol @@ -0,0 +1,74 @@ +pragma solidity >=0.7.0; + +/** + * @title Wallet + * @dev The wallet is used to store all the assets of a DFO (ethers, ERC20 Tokens, ERC721 NFTs). + * All the functions to transfer assets can be called by the Proxy only. + */ +interface IMVDWallet { + /** + * @dev GET the proxy address + */ + function getProxy() external view returns (address); + + /** + * @dev SET the proxy address + */ + function setProxy() external; + + /** + * @dev SET new wallet + * @param newWallet New wallet address + * @param tokenAddress The voting token, used to flush balance to new wallet + */ + function setNewWallet(address payable newWallet, address tokenAddress) + external; + + /** + * @dev Transfer a token to an address + * @param receiver Address of the receiver + * @param value Amount of token to transfer + * @param tokenAddress Address of the token to transfer + */ + function transfer( + address receiver, + uint256 value, + address tokenAddress + ) external; + + /** + * @dev Transfer an ERC721 to an address + * @param receiver Address of the receiver + * @param tokenId ID of the ERC721 to transfer + * @param data The optional payload to pass in the safeTransferFrom function + * @param safe Boolean flag for triggering the SafeTransfer + * @param token Address of the token to transfer + */ + function transfer( + address receiver, + uint256 tokenId, + bytes calldata data, + bool safe, + address token + ) external; + + /** + * @dev Send all of the specified tokens to the NewWallet + * @param token Address of the token to send + */ + function flushToNewWallet(address token) external; + + /** + * @dev Transfer an ERC721 to the NewWallet + * @param tokenId ID of the ERC721 to transfer + * @param data The optional payload passed in the safeTransferFrom function + * @param safe Boolean flag for triggering the SafeTransfer + * @param tokenAddress Address of the token to transfer + */ + function flush721ToNewWallet( + uint256 tokenId, + bytes calldata data, + bool safe, + address tokenAddress + ) external; +} diff --git a/contracts/IStateHolder.sol b/contracts/IStateHolder.sol new file mode 100644 index 0000000..8b8b77a --- /dev/null +++ b/contracts/IStateHolder.sol @@ -0,0 +1,86 @@ +pragma solidity >=0.7.0; + +/** + * @title State Holder + * StateHolder is the Database of a DFO. It stores all data the DFO needs to run its business logic. + * It works like a Key/Value storage and can save the main Solidity data types (address, bool, uint256, string, bytes). + * The read capabilities are of course public. While se write capabilities (set/clear) can be called by DFO Microservices only. + * The set methods also return the previous value of the set variable + */ +interface IStateHolder { + /** + * @dev Initialization logic using during the constructor Call + */ + function init() external; + + /** + * @dev GET the Proxy + */ + function getProxy() external view returns (address); + + /** + * @dev SET the Proxy + */ + function setProxy() external; + + /** + * @dev For frontend purposes. Returns the StateHolder's keys and values in JSON Format + */ + function toJSON() external view returns (string memory); + + /** + * @dev For frontend purposes. Returns the StateHolder's keys and values in JSON Format + * @param start the values array start + * @param l the values array offset + */ + function toJSON(uint256 start, uint256 l) external view returns (string memory); + + /** + * @dev returns the number of values set in the StateHolder + */ + function getStateSize() external view returns (uint256); + + /** + * @param varName the name of the variable to check + * @return true if varName is set, false otherwise + */ + function exists(string calldata varName) external view returns (bool); + + /** + * @param varName the name of the variable to check + * @return dataType the data type of this var, if any. + */ + function getDataType(string calldata varName) external view returns (string memory dataType); + + /** + * @dev delete the variable from the StateHolder + * @param varName the name of the variable to delete + * @return oldDataType the data type of the deleted variable + * @return oldVal the old value of the deleted variable + */ + 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); +} diff --git a/contracts/IVotingToken.sol b/contracts/IVotingToken.sol new file mode 100644 index 0000000..00c241d --- /dev/null +++ b/contracts/IVotingToken.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +/** + * @title Voting Token interface. + * @dev A voting token is an ERC20 used inside a DFO to vote proposals. + * Since proposals are code and voting tokens control which proposal get accepted, they are the + * programmable equities of the DFO. + */ +interface IVotingToken { + /** + * @dev Initialization logic using during the constructor Call + * @param name Name of the token used + * @param symbol Ticker symbol of the token used + * @param decimals Amount of decimals supported by the token + * @param totalSupply Total Supply of the token + */ + function init( + string calldata name, + string calldata symbol, + uint256 decimals, + uint256 totalSupply + ) external; + + /** + * @dev GET the Proxy + */ + function getProxy() external view returns (address); + + /** + * @dev SET the Proxy + */ + function setProxy() external; + + /** + * @dev GET the token name + */ + function name() external view returns (string memory); + + /** + * @dev GET the token ticker symbol + */ + function symbol() external view returns (string memory); + + /** + * @dev GET amount of decimals supported by the token + */ + function decimals() external view returns (uint256); + + /** + * @dev Mint functionality of the voting token + */ + function mint(uint256 amount) external; + + /** + * @dev Burn functionality of the voting token + */ + function burn(uint256 amount) external; + + /** + * @dev see the OpenZeppelin's documentation + */ + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + + /** + * @dev see the OpenZeppelin's documentation + */ + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); +} diff --git a/contracts/MVDFunctionalitiesManager.sol b/contracts/MVDFunctionalitiesManager.sol new file mode 100644 index 0000000..288757a --- /dev/null +++ b/contracts/MVDFunctionalitiesManager.sol @@ -0,0 +1,587 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.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 { + 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 submittable, + string memory methodSignature, + string memory returnAbiParametersArray, + bool isInternal, + bool needsSender + ) public override { + addFunctionality( + codeName, + sourceLocation, + sourceLocationId, + location, + submittable, + methodSignature, + returnAbiParametersArray, + isInternal, + needsSender, + _functionalities.length + ); + } + + function addFunctionality( + string memory codeName, + address sourceLocation, + uint256 sourceLocationId, + address location, + bool submittable, + 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, + submittable, + 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 submittable, + 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( + submittable == (functionality.submittable ? 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 >= (submittable == 1 ? 64 : 32), + "Insufficient space in data payload" + ); + assembly { + mstore(add(data, 0x20), sender) + switch iszero(submittable) + 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, + address, + uint256 + ) + { + + Functionality memory functionality + = _functionalities[_indexes[codeName]]; + return ( + compareStrings(codeName, functionality.codeName) && + functionality.active + ? functionality.location + : address(0), + functionality.position, + functionality.methodSignature, + functionality.sourceLocation, + functionality.sourceLocationId + ); + } + + 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) + { + 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; + } + functionsJSONArray = string(abi.encodePacked(functionsJSONArray, "]")); + } + + function functionalityNames() public override view returns (string memory) { + return functionalityNames(0, _functionalities.length); + } + + function functionalityNames(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, + '"', + _functionalities[i].codeName, + '"', + i == length - (_functionalities[i].active ? 1 : 0) + ? "" + : "," + ) + ); + length += _functionalities[i].active ? 0 : 1; + length = length > _functionalities.length + ? _functionalities.length + : length; + } + functionsJSONArray = string(abi.encodePacked(functionsJSONArray, "]")); + } + + function functionalityToJSON(string memory codeName) + public + override + view + returns (string memory) + { + return string(toJSON(_functionalities[_indexes[codeName]])); + } + + function toJSON(Functionality memory func) + private + pure + returns (bytes memory) + { + return + abi.encodePacked( + "{", + getFirstJSONPart( + func.sourceLocation, + func.sourceLocationId, + func.location + ), + '","submittable":', + func.submittable ? "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 + returns (bool result) + { + 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, ""); + + if (!hasCodeName && !hasReplaces) { + (result, ) = IMVDProxy(_proxy).callFromManager( + _callingContext = proposal.getLocation(), + abi.encodeWithSignature("callOneTime(address)", proposalAddress) + ); + _callingContext = address(0); + return result; + } + + + Functionality memory replacedFunctionality + = _functionalities[_indexes[replaces]]; + uint256 position = hasReplaces + ? replacedFunctionality.position + : _functionalities.length; + + if (hasReplaces) { + (result, ) = IMVDProxy(_proxy).callFromManager( + _callingContext = replacedFunctionality.location, + abi.encodeWithSignature("onStop(address)", proposalAddress) + ); + _callingContext = address(0); + if (!result) { + revert("onStop failed!"); + } + } + + 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.issubmittable(), + 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) { + (result, ) = IMVDProxy(_proxy).callFromManager( + _callingContext = newFunctionality.location, + abi.encodeWithSignature( + "onStart(address,address)", + proposalAddress, + hasReplaces ? replacedFunctionality.location : address(0) + ) + ); + _callingContext = address(0); + if (!result) { + revert("onStart failed!"); + } + } + + if (hasCodeName || hasReplaces) { + IMVDProxy(_proxy).emitFromManager( + hasCodeName ? codeName : "", + proposalAddress, + hasReplaces ? replacedFunctionality.codeName : "", + hasReplaces ? replacedFunctionality.sourceLocation : address(0), + hasReplaces ? replacedFunctionality.sourceLocationId : 0, + hasReplaces ? replacedFunctionality.location : address(0), + hasReplaces ? replacedFunctionality.submittable : false, + hasReplaces ? replacedFunctionality.methodSignature : "", + hasReplaces ? replacedFunctionality.isInternal : false, + hasReplaces ? replacedFunctionality.needsSender : false, + hasReplaces ? replacedFunctionality.proposalAddress : address(0) + ); + } + _callingContext = address(0); + return true; + } + + 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); + } +} diff --git a/contracts/MVDFunctionalityModelsManager.sol b/contracts/MVDFunctionalityModelsManager.sol new file mode 100644 index 0000000..0ee9616 --- /dev/null +++ b/contracts/MVDFunctionalityModelsManager.sol @@ -0,0 +1,247 @@ +pragma solidity >=0.7.0; + +import "./IMVDFunctionalityModelsManager.sol"; +import "./Functionality.sol"; + +contract MVDFunctionalityModelsManager is IMVDFunctionalityModelsManager { + mapping(string => Functionality) private _wellKnownFunctionalityModels; + + constructor() public { + 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["getVotesHardCap"] = Functionality( + "getVotesHardCap", + address(0), + 0, + address(0), + false, + "getVotesHardCap()", + '["uint256"]', + false, + false, + address(0), + false, + 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 submittable, + 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.submittable == submittable + : true, + "Wrong submittable 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)); + } +} diff --git a/MVDFunctionalityProposal.sol b/contracts/MVDFunctionalityProposal.sol similarity index 51% rename from MVDFunctionalityProposal.sol rename to contracts/MVDFunctionalityProposal.sol index 28d83b9..9099e21 100644 --- a/MVDFunctionalityProposal.sol +++ b/contracts/MVDFunctionalityProposal.sol @@ -1,11 +1,10 @@ -pragma solidity ^0.6.0; +pragma solidity >=0.7.0; import "./IMVDFunctionalityProposal.sol"; import "./IMVDProxy.sol"; import "./IERC20.sol"; -contract MVDFunctionalityProposal is IMVDFunctionalityProposal{ - +contract MVDFunctionalityProposal is IMVDFunctionalityProposal { bool private _collateralDataSet; address private _proxy; @@ -15,7 +14,7 @@ contract MVDFunctionalityProposal is IMVDFunctionalityProposal{ address private _sourceLocation; uint256 private _sourceLocationId; address private _location; - bool private _submitable; + bool private _submittable; string private _methodSignature; string private _returnAbiParametersArray; bool private _isInternal; @@ -31,18 +30,37 @@ contract MVDFunctionalityProposal is IMVDFunctionalityProposal{ mapping(address => uint256) private _refuse; uint256 private _totalAccept; uint256 private _totalRefuse; - mapping(address => bool) private _withdrawed; + mapping(address => bool) private _withdrawn; uint256 private _votesHardCap; bool private _votesHardCapReached; - constructor(string memory codeName, address location, string memory methodSignature, string memory returnAbiParametersArray, - string memory replaces, address proxy) public { - init(codeName, location, methodSignature, returnAbiParametersArray, replaces, proxy); + constructor( + string memory codeName, + address location, + string memory methodSignature, + string memory returnAbiParametersArray, + string memory replaces, + address proxy + ) public { + 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 { + 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; @@ -52,107 +70,139 @@ contract MVDFunctionalityProposal is IMVDFunctionalityProposal{ _replaces = replaces; } - function setCollateralData(bool emergency, address sourceLocation, uint256 sourceLocationId, bool submitable, bool isInternal, bool needsSender, address proposer, uint256 votesHardCap) public override { + function setCollateralData( + bool emergency, + address sourceLocation, + uint256 sourceLocationId, + bool submittable, + bool isInternal, + bool needsSender, + address proposer, + uint256 votesHardCap + ) public override { require(!_collateralDataSet, "setCollateralData already called!"); - require(_proxy == msg.sender, "Only Original Proxy can call this method!"); + require( + _proxy == msg.sender, + "Only Original Proxy can call this method!" + ); _sourceLocation = sourceLocation; _sourceLocationId = sourceLocationId; - _submitable = submitable; + _submittable = submittable; _isInternal = isInternal; _needsSender = needsSender; _proposer = proposer; - _surveyDuration = toUint256(IMVDProxy(_proxy).read((_emergency = emergency) ? "getMinimumBlockNumberForEmergencySurvey" : "getMinimumBlockNumberForSurvey", bytes(""))); + _surveyDuration = toUint256( + IMVDProxy(_proxy).read( + (_emergency = emergency) + ? "getMinimumBlockNumberForEmergencySurvey" + : "getMinimumBlockNumberForSurvey", + bytes("") + ) + ); _votesHardCap = votesHardCap; _collateralDataSet = true; } - function getProxy() public override view returns(address) { + function getProxy() public override view returns (address) { return _proxy; } - function getCodeName() public override view returns(string memory) { + function getCodeName() public override view returns (string memory) { return _codeName; } - function isEmergency() public override view returns(bool) { + function isEmergency() public override view returns (bool) { return _emergency; } - function getSourceLocation() public override view returns(address) { + function getSourceLocation() public override view returns (address) { return _sourceLocation; } - function getSourceLocationId() public override view returns(uint256) { + function getSourceLocationId() public override view returns (uint256) { return _sourceLocationId; } - function getLocation() public override view returns(address) { + function getLocation() public override view returns (address) { return _location; } - function isSubmitable() public override view returns(bool) { - return _submitable; + function issubmittable() public override view returns (bool) { + return _submittable; } - function getMethodSignature() public override view returns(string memory) { + function getMethodSignature() public override view returns (string memory) { return _methodSignature; } - function getReturnAbiParametersArray() public override view returns(string memory) { + function getReturnAbiParametersArray() + public + override + view + returns (string memory) + { return _returnAbiParametersArray; } - function isInternal() public override view returns(bool) { + function isInternal() public override view returns (bool) { return _isInternal; } - function needsSender() public override view returns(bool) { + function needsSender() public override view returns (bool) { return _needsSender; } - function getReplaces() public override view returns(string memory) { + function getReplaces() public override view returns (string memory) { return _replaces; } - function getProposer() public override view returns(address) { + function getProposer() public override view returns (address) { return _proposer; } - function getSurveyEndBlock() public override view returns(uint256) { + function getSurveyEndBlock() public override view returns (uint256) { return _surveyEndBlock; } - function getSurveyDuration() public override view returns(uint256) { + function getSurveyDuration() public override view returns (uint256) { return _surveyDuration; } - function getVote(address addr) public override view returns(uint256 accept, uint256 refuse) { + 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) { + function getVotes() public override view returns (uint256, uint256) { return (_totalAccept, _totalRefuse); } - function isTerminated() public override view returns(bool) { + function isTerminated() public override view returns (bool) { return _terminated; } - function isDisabled() public override view returns(bool) { + function isDisabled() public override view returns (bool) { return _disabled; } - function isVotesHardCapReached() public override view returns(bool) { + function isVotesHardCapReached() public override view returns (bool) { return _votesHardCapReached; } - function getVotesHardCapToReach() public override view returns(uint256) { + function getVotesHardCapToReach() public override view returns (uint256) { return _votesHardCap; } function start() public override { - require(_collateralDataSet, "Still waiting for setCollateralData to be called!"); + 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!"); @@ -160,58 +210,75 @@ contract MVDFunctionalityProposal is IMVDFunctionalityProposal{ } function disable() public override { - require(_collateralDataSet, "Still waiting for setCollateralData to be called!"); + 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, - '"')); + function toJSON() public override view returns (string memory) { + return + string( + abi.encodePacked( + "{", + getFirstJSONPart( + _sourceLocation, + _sourceLocationId, + _location + ), + '","submittable":', + _submittable ? "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( + _collateralDataSet, + "Still waiting for setCollateralData to be called!" + ); require(!_disabled, "Survey disabled!"); require(!_terminated, "Survey Terminated!"); require(!_votesHardCapReached, "Votes Hard Cap reached!"); @@ -221,17 +288,26 @@ contract MVDFunctionalityProposal is IMVDFunctionalityProposal{ } modifier onSurveyEnd() { - require(_collateralDataSet, "Still waiting for setCollateralData to be called!"); + require( + _collateralDataSet, + "Still waiting for setCollateralData to be called!" + ); require(!_disabled, "Survey disabled!"); require(_surveyEndBlock > 0, "Survey Not Started!"); - if(!_votesHardCapReached) { - require(block.number >= _surveyEndBlock, "Survey is still running!"); + if (!_votesHardCapReached) { + require( + block.number >= _surveyEndBlock, + "Survey is still running!" + ); } _; } function _checkVotesHardCap() private { - if(_votesHardCap == 0 || (_totalAccept < _votesHardCap && _totalRefuse < _votesHardCap)) { + if ( + _votesHardCap == 0 || + (_totalAccept < _votesHardCap && _totalRefuse < _votesHardCap) + ) { return; } _votesHardCapReached = true; @@ -321,7 +397,7 @@ contract MVDFunctionalityProposal is IMVDFunctionalityProposal{ } function withdraw() external override onSurveyEnd { - if(!_terminated && !_disabled) { + if (!_terminated && !_disabled) { terminate(); return; } @@ -335,11 +411,20 @@ contract MVDFunctionalityProposal is IMVDFunctionalityProposal{ } function _withdraw(bool launchError) private { - require(!launchError || _accept[msg.sender] + _refuse[msg.sender] > 0, "Nothing to Withdraw!"); - require(!launchError || !_withdrawed[msg.sender], "Already Withdrawed!"); - if(_accept[msg.sender] + _refuse[msg.sender] > 0 && !_withdrawed[msg.sender]) { - IERC20(_token).transfer(msg.sender, _accept[msg.sender] + _refuse[msg.sender]); - _withdrawed[msg.sender] = true; + require( + !launchError || _accept[msg.sender] + _refuse[msg.sender] > 0, + "Nothing to Withdraw!" + ); + require(!launchError || !_withdrawn[msg.sender], "Already Withdrawed!"); + if ( + _accept[msg.sender] + _refuse[msg.sender] > 0 && + !_withdrawn[msg.sender] + ) { + IERC20(_token).transfer( + msg.sender, + _accept[msg.sender] + _refuse[msg.sender] + ); + _withdrawn[msg.sender] = true; } } @@ -349,69 +434,78 @@ contract MVDFunctionalityProposal is IMVDFunctionalityProposal{ _terminated = true; } - function toUint256(bytes memory bs) public pure returns(uint256 x) { - if(bs.length >= 32) { + function toUint256(bytes memory bs) public pure returns (uint256 x) { + if (bs.length >= 32) { assembly { x := mload(add(bs, add(0x20, 0))) } } } - function toString(address _addr) public pure returns(string memory) { + function toString(address _addr) public 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))]; + str[0] = "0"; + str[1] = "x"; + for (uint256 i = 0; i < 20; i++) { + str[2 + i * 2] = alphabet[uint256(uint8(value[i + 12] >> 4))]; + str[3 + i * 2] = alphabet[uint256(uint8(value[i + 12] & 0x0f))]; } return string(str); } - function toString(uint _i) public pure returns(string memory) { + function toString(uint256 _i) public pure returns (string memory) { if (_i == 0) { return "0"; } - uint j = _i; - uint len; + uint256 j = _i; + uint256 len; while (j != 0) { len++; j /= 10; } bytes memory bstr = new bytes(len); - uint k = len - 1; + uint256 k = len - 1; while (_i != 0) { - bstr[k--] = byte(uint8(48 + _i % 10)); + bstr[k--] = bytes1(uint8(48 + (_i % 10))); _i /= 10; } return string(bstr); } - function getFirstJSONPart(address sourceLocation, uint256 sourceLocationId, address location) public pure returns(bytes memory) { - return abi.encodePacked( - '"sourceLocation":"', - toString(sourceLocation), - '","sourceLocationId":', - toString(sourceLocationId), - ',"location":"', - toString(location) - ); - } - - function formatReturnAbiParametersArray(string memory m) public pure returns(string memory) { + function getFirstJSONPart( + address sourceLocation, + uint256 sourceLocationId, + address location + ) public pure returns (bytes memory) { + return + abi.encodePacked( + '"sourceLocation":"', + toString(sourceLocation), + '","sourceLocationId":', + toString(sourceLocationId), + ',"location":"', + toString(location) + ); + } + + function formatReturnAbiParametersArray(string memory m) + public + pure + returns (string memory) + { bytes memory b = bytes(m); - if(b.length < 2) { + if (b.length < 2) { return "[]"; } - if(b[0] != bytes1("[")) { + if (b[0] != bytes1("[")) { return "[]"; } - if(b[b.length - 1] != bytes1("]")) { + if (b[b.length - 1] != bytes1("]")) { return "[]"; } return m; } -} \ No newline at end of file +} diff --git a/contracts/MVDFunctionalityProposalManager.sol b/contracts/MVDFunctionalityProposalManager.sol new file mode 100644 index 0000000..1500039 --- /dev/null +++ b/contracts/MVDFunctionalityProposalManager.sol @@ -0,0 +1,160 @@ +pragma solidity >=0.7.0; + +import "./IMVDFunctionalityProposalManager.sol"; +import "./IMVDProxy.sol"; +import "./MVDFunctionalityProposal.sol"; +import "./IMVDFunctionalitiesManager.sol"; + +contract MVDFunctionalityProposalManager is IMVDFunctionalityProposalManager { + address private _proxy; + + mapping(address => bool) private _proposals; + + 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 { + bool hasCodeName = !compareStrings(codeName, ""); + bool 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( + location == address(0) || !compareStrings(methodSignature, ""), + "Cannot have empty string for methodSignature" + ); + + require( + hasCodeName || hasReplaces + ? true + : compareStrings(methodSignature, "callOneTime(address)"), + "One Time Functionality method signature allowed is callOneTime(address)" + ); + + + IMVDFunctionalitiesManager functionalitiesManager + = IMVDFunctionalitiesManager( + IMVDProxy(_proxy).getMVDFunctionalitiesManagerAddress() + ); + + require( + hasCodeName && functionalitiesManager.hasFunctionality(codeName) + ? compareStrings(codeName, replaces) + : true, + "codeName is already used by another functionality" + ); + + require( + hasReplaces + ? functionalitiesManager.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) { + preconditionCheck(codeName, location, methodSignature, replaces); + + _proposals[proposalAddress] = true; + + 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!"); + + if (!proposal.isVotesHardCapReached()) { + require(block.number >= surveyEndBlock, "Survey is still running!"); + } + + require(!proposal.isTerminated(), "Survey already terminated!"); + } + + function isValidProposal(address proposal) + public + override + view + returns (bool) + { + return _proposals[proposal]; + } + + 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)); + } +} diff --git a/contracts/MVDProxy.sol b/contracts/MVDProxy.sol new file mode 100644 index 0000000..574d159 --- /dev/null +++ b/contracts/MVDProxy.sol @@ -0,0 +1,608 @@ +pragma solidity >=0.7.0; + +import "./IMVDProxy.sol"; +import "./IMVDFunctionalityProposalManager.sol"; +import "./IMVDFunctionalityProposal.sol"; +import "./IERC20.sol"; +import "./IMVDFunctionalityModelsManager.sol"; +import "./ICommonUtilities.sol"; +import "./IMVDFunctionalitiesManager.sol"; +import "./IMVDWallet.sol"; +import "./IERC721.sol"; + +contract MVDProxy is IMVDProxy { + address[] private _delegates; + + constructor( + address votingTokenAddress, + address functionalityProposalManagerAddress, + address stateHolderAddress, + address functionalityModelsManagerAddress, + address functionalitiesManagerAddress, + address walletAddress, + address doubleProxyAddress + ) public { + if (votingTokenAddress == address(0)) { + return; + } + init( + votingTokenAddress, + functionalityProposalManagerAddress, + stateHolderAddress, + functionalityModelsManagerAddress, + functionalitiesManagerAddress, + walletAddress, + doubleProxyAddress + ); + } + + function init( + address votingTokenAddress, + address functionalityProposalManagerAddress, + address stateHolderAddress, + address functionalityModelsManagerAddress, + address functionalitiesManagerAddress, + address walletAddress, + address doubleProxyAddress + ) public override { + require(_delegates.length == 0, "Init already called!"); + + _delegates = new address[](7); + + IMVDProxyDelegate(_delegates[0] = votingTokenAddress).setProxy(); + + IMVDProxyDelegate(_delegates[1] = functionalityProposalManagerAddress) + .setProxy(); + + IMVDProxyDelegate(_delegates[2] = stateHolderAddress).setProxy(); + + _delegates[3] = functionalityModelsManagerAddress; + + IMVDProxyDelegate(_delegates[4] = functionalitiesManagerAddress) + .setProxy(); + + IMVDProxyDelegate(_delegates[5] = walletAddress).setProxy(); + + IMVDProxyDelegate(_delegates[6] = doubleProxyAddress).setProxy(); + } + + receive() external payable { + revert("No Eth Accepted"); + } + + function getDelegates() public override view returns (address[] memory) { + return _delegates; + } + + function getToken() public override view returns (address) { + return _delegates[0]; + } + + function getMVDFunctionalityProposalManagerAddress() + public + override + view + returns (address) + { + return _delegates[1]; + } + + function getStateHolderAddress() public override view returns (address) { + return _delegates[2]; + } + + function getMVDFunctionalityModelsManagerAddress() + public + override + view + returns (address) + { + return _delegates[3]; + } + + function getMVDFunctionalitiesManagerAddress() + public + override + view + returns (address) + { + return _delegates[4]; + } + + function getMVDWalletAddress() public override view returns (address) { + return _delegates[5]; + } + + function getDoubleProxyAddress() public override view returns (address) { + return _delegates[6]; + } + + function flushToWallet( + address tokenAddress, + bool is721, + uint256 tokenId + ) public override { + require( + IMVDFunctionalitiesManager(_delegates[4]).isAuthorizedFunctionality( + msg.sender + ), + "Unauthorized action!" + ); + if (tokenAddress == address(0)) { + payable(_delegates[5]).transfer(payable(address(this)).balance); + return; + } + if (is721) { + IERC721(tokenAddress).transferFrom( + address(this), + _delegates[5], + tokenId + ); + return; + } + IERC20 token = IERC20(tokenAddress); + token.transfer(_delegates[5], token.balanceOf(address(this))); + } + + function setDelegate(uint256 position, address newAddress) + public + override + returns (address oldAddress) + { + require( + IMVDFunctionalitiesManager(_delegates[4]).isAuthorizedFunctionality( + msg.sender + ), + "Unauthorized action!" + ); + require(newAddress != address(0), "Cannot set void address!"); + if (position == 5) { + IMVDWallet(_delegates[5]).setNewWallet( + payable(newAddress), + _delegates[0] + ); + } + oldAddress = _delegates[position]; + _delegates[position] = newAddress; + if (position != 3) { + IMVDProxyDelegate(oldAddress).setProxy(); + IMVDProxyDelegate(newAddress).setProxy(); + } + emit DelegateChanged(position, oldAddress, newAddress); + } + + function changeProxy(address newAddress, bytes memory initPayload) + public + override + { + require( + IMVDFunctionalitiesManager(_delegates[4]).isAuthorizedFunctionality( + msg.sender + ), + "Unauthorized action!" + ); + require(newAddress != address(0), "Cannot set void address!"); + for (uint256 i = 0; i < _delegates.length; i++) { + if (i != 3) { + IMVDProxyDelegate(_delegates[i]).setProxy(); + } + } + _delegates = new address[](0); + emit ProxyChanged(newAddress); + (bool response, ) = newAddress.call(initPayload); + require(response, "New Proxy initPayload failed!"); + } + + function isValidProposal(address proposal) + public + override + view + returns (bool) + { + return + IMVDFunctionalityProposalManager(_delegates[1]).isValidProposal( + proposal + ); + } + + function isAuthorizedFunctionality(address functionality) + public + override + view + returns (bool) + { + return + IMVDFunctionalitiesManager(_delegates[4]).isAuthorizedFunctionality( + functionality + ); + } + + function newProposal( + string memory codeName, + bool emergency, + address sourceLocation, + uint256 sourceLocationId, + address location, + bool submittable, + 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, + submittable, + methodSignature, + returnAbiParametersArray, + isInternal, + needsSender, + replaces + ); + + + IMVDFunctionalitiesManager functionalitiesManager + = IMVDFunctionalitiesManager(_delegates[4]); + + IMVDFunctionalityProposal proposal = IMVDFunctionalityProposal( + proposalAddress = IMVDFunctionalityProposalManager(_delegates[1]) + .newProposal( + codeName, + location, + methodSignature, + returnAbiParametersArray, + replaces + ) + ); + proposal.setCollateralData( + emergency, + sourceLocation, + sourceLocationId, + submittable, + isInternal, + needsSender, + msg.sender, + functionalitiesManager.hasFunctionality("getVotesHardCap") + ? toUint256(read("getVotesHardCap", "")) + : 0 + ); + + if (functionalitiesManager.hasFunctionality("onNewProposal")) { + submit("onNewProposal", abi.encode(proposalAddress)); + } + + if ( + !IMVDFunctionalitiesManager(_delegates[4]).hasFunctionality( + "startProposal" + ) || + !IMVDFunctionalitiesManager(_delegates[4]).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( + IMVDFunctionalitiesManager(_delegates[4]).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( + IMVDFunctionalityProposalManager(_delegates[1]).isValidProposal( + proposalAddress + ), + "Invalid Proposal Address!" + ); + IMVDFunctionalityProposal(proposalAddress).start(); + } + + function disableProposal(address proposalAddress) public override { + require( + IMVDFunctionalitiesManager(_delegates[4]).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(); + } + + function transfer( + address receiver, + uint256 value, + address token + ) public override { + require( + IMVDFunctionalitiesManager(_delegates[4]).isAuthorizedFunctionality( + msg.sender + ), + "Only functionalities can transfer Proxy balances!" + ); + IMVDWallet(_delegates[5]).transfer(receiver, value, token); + } + + function transfer721( + address receiver, + uint256 tokenId, + bytes memory data, + bool safe, + address token + ) public override { + require( + IMVDFunctionalitiesManager(_delegates[4]).isAuthorizedFunctionality( + msg.sender + ), + "Only functionalities can transfer Proxy balances!" + ); + IMVDWallet(_delegates[5]).transfer( + receiver, + tokenId, + data, + safe, + token + ); + } + + function setProposal() public override { + IMVDFunctionalityProposalManager(_delegates[1]).checkProposal( + msg.sender + ); + + emit ProposalCheck(msg.sender); + + + IMVDFunctionalitiesManager functionalitiesManager + = IMVDFunctionalitiesManager(_delegates[4]); + + ( + address addressToCall, + , + string memory methodSignature, + , + + ) = functionalitiesManager.getFunctionalityData("checkSurveyResult"); + + (bool surveyResult, bytes memory response) = addressToCall.staticcall( + abi.encodeWithSignature(methodSignature, msg.sender) + ); + + surveyResult = toUint256(response) > 0; + + bool collateralCallResult = true; + (addressToCall, , methodSignature, , ) = functionalitiesManager + .getFunctionalityData("proposalEnd"); + if (addressToCall != address(0)) { + functionalitiesManager.setCallingContext(addressToCall); + (collateralCallResult, ) = addressToCall.call( + abi.encodeWithSignature( + methodSignature, + msg.sender, + surveyResult + ) + ); + functionalitiesManager.clearCallingContext(); + } + + IMVDFunctionalityProposal proposal = IMVDFunctionalityProposal( + msg.sender + ); + + uint256 staking = 0; + address tokenAddress = _delegates[0]; + address walletAddress = _delegates[5]; + + if (proposal.isEmergency()) { + (addressToCall, , methodSignature, , ) = functionalitiesManager + .getFunctionalityData("getEmergencySurveyStaking"); + (, response) = addressToCall.staticcall( + abi.encodeWithSignature(methodSignature) + ); + staking = toUint256(response); + } + + if (!surveyResult) { + if (collateralCallResult) { + proposal.set(); + emit ProposalSet(msg.sender, surveyResult); + if (staking > 0) { + IERC20(tokenAddress).transfer(walletAddress, staking); + } + } + return; + } + + if (collateralCallResult) { + try functionalitiesManager.setupFunctionality(msg.sender) returns ( + bool managerResult + ) { + collateralCallResult = managerResult; + } catch { + collateralCallResult = false; + } + } + + if (collateralCallResult) { + proposal.set(); + emit ProposalSet(msg.sender, surveyResult); + if (staking > 0) { + IERC20(tokenAddress).transfer( + surveyResult ? proposal.getProposer() : walletAddress, + staking + ); + } + } + } + + 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) + { + if (msg.value > 0) { + payable(_delegates[5]).transfer(msg.value); + } + + 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 Functionalities Manager can call this!" + ); + return location.call(payload); + } + + function emitFromManager( + string memory codeName, + address proposal, + string memory replaced, + address replacedSourceLocation, + uint256 replacedSourceLocationId, + address location, + bool submittable, + string memory methodSignature, + bool isInternal, + bool needsSender, + address proposalAddress + ) public override { + require( + msg.sender == _delegates[4], + "Only Functionalities Manager can call this!" + ); + emit FunctionalitySet( + codeName, + proposal, + replaced, + replacedSourceLocation, + replacedSourceLocationId, + location, + submittable, + methodSignature, + isInternal, + needsSender, + proposalAddress + ); + } + + function emitEvent( + string memory eventSignature, + bytes memory firstIndex, + bytes memory secondIndex, + bytes memory data + ) public override { + require( + IMVDFunctionalitiesManager(_delegates[4]).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; +} diff --git a/MVDWallet.sol b/contracts/MVDWallet.sol similarity index 54% rename from MVDWallet.sol rename to contracts/MVDWallet.sol index b87a258..0490ea8 100644 --- a/MVDWallet.sol +++ b/contracts/MVDWallet.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.6.0; +pragma solidity >=0.7.0; import "./IMVDWallet.sol"; import "./IMVDProxy.sol"; @@ -7,11 +7,13 @@ import "./IERC721.sol"; import "./IERC721Receiver.sol"; contract MVDWallet is IMVDWallet, IERC721Receiver { - address private _proxy; address payable private _newWallet; - function setNewWallet(address payable newWallet, address tokenAddress) public override { + function setNewWallet(address payable newWallet, address tokenAddress) + public + override + { require(msg.sender == _proxy, "Unauthorized Access!"); _newWallet = newWallet; _newWallet.transfer(address(this).balance); @@ -21,7 +23,7 @@ contract MVDWallet is IMVDWallet, IERC721Receiver { function flushToNewWallet(address tokenAddress) public override { require(_newWallet != address(0), "Unauthorized Access!"); - if(tokenAddress == address(0)) { + if (tokenAddress == address(0)) { payable(_newWallet).transfer(address(this).balance); return; } @@ -29,55 +31,91 @@ contract MVDWallet is IMVDWallet, IERC721Receiver { token.transfer(_newWallet, token.balanceOf(address(this))); } - function flush721ToNewWallet(uint256 tokenId, bytes memory data, bool safe, address tokenAddress) public override { + function flush721ToNewWallet( + uint256 tokenId, + bytes memory data, + bool safe, + address tokenAddress + ) public override { require(_newWallet != address(0), "Unauthorized Access!"); _transfer(_newWallet, tokenId, data, safe, tokenAddress); } - function transfer(address receiver, uint256 value, address token) public override { + function transfer( + address receiver, + uint256 value, + address token + ) public override { require(msg.sender == _proxy, "Unauthorized Access!"); - if(value == 0) { + if (value == 0) { return; } - if(token == address(0)) { + if (token == address(0)) { payable(receiver).transfer(value); return; } IERC20(token).transfer(receiver, value); } - function transfer(address receiver, uint256 tokenId, bytes memory data, bool safe, address token) public override { + function transfer( + address receiver, + uint256 tokenId, + bytes memory data, + bool safe, + address token + ) public override { require(msg.sender == _proxy, "Unauthorized Access!"); _transfer(receiver, tokenId, data, safe, token); } - function _transfer(address receiver, uint256 tokenId, bytes memory data, bool safe, address token) private { - if(safe) { - IERC721(token).safeTransferFrom(address(this), receiver, tokenId, data); + function _transfer( + address receiver, + uint256 tokenId, + bytes memory data, + bool safe, + address token + ) private { + if (safe) { + IERC721(token).safeTransferFrom( + address(this), + receiver, + tokenId, + data + ); } else { IERC721(token).transferFrom(address(this), receiver, tokenId); } } receive() external payable { - if(_newWallet != address(0)) { + if (_newWallet != address(0)) { _newWallet.transfer(address(this).balance); } } - function getProxy() public override view returns(address) { + 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); + 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 onERC721Received(address, address, uint256 tokenId, bytes memory data) public override returns (bytes4) { - if(_newWallet != address(0)) { + function onERC721Received( + address, + address, + uint256 tokenId, + bytes memory data + ) public override returns (bytes4) { + if (_newWallet != address(0)) { _transfer(_newWallet, tokenId, data, true, msg.sender); } return 0x150b7a02; } -} \ No newline at end of file +} diff --git a/contracts/StateHolder.sol b/contracts/StateHolder.sol new file mode 100644 index 0000000..c5e4b50 --- /dev/null +++ b/contracts/StateHolder.sol @@ -0,0 +1,286 @@ +pragma solidity >=0.7.0; + +import "./IMVDProxy.sol"; +import "./CommonUtilities.sol"; +import "./IStateHolder.sol"; +import "./IMVDFunctionalitiesManager.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 { + init(); + } + + function init() public override { + require(_state.length == 0, "Init already called!"); + _state.push(Var("", DataType.BYTES, "", 0, false)); + } + + modifier canSet { + require(_state.length > 0, "Not Initialized!"); + if (_proxy != address(0)) { + require( + IMVDFunctionalitiesManager( + IMVDProxy(_proxy).getMVDFunctionalitiesManagerAddress() + ) + .isAuthorizedFunctionality(msg.sender), + "UnauthorizedAccess" + ); + } + _; + } + + function toJSON() public override view returns (string memory) { + return toJSON(0, _state.length - 1); + } + + function toJSON(uint256 start, uint256 l) + public + override + view + returns (string memory json) + { + uint256 length = start + 1 + l; + json = "["; + for (uint256 i = start; i < length; i++) { + json = !_state[i].active + ? json + : string( + abi.encodePacked( + json, + '{"name":"', + _state[i].name, + '","type":"', + toString(_state[i].dataType), + '"}', + i == length - (_state[i].active ? 1 : 0) ? "" : "," + ) + ); + length += _state[i].active ? 0 : 1; + length = length > _state.length ? _state.length : length; + } + json = string(abi.encodePacked(json, "]")); + } + + 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 canSet returns (bytes memory oldVal) { + 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 + canSet + returns (string memory oldDataType, bytes memory oldVal) + { + 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; + } +} diff --git a/contracts/VotingToken.sol b/contracts/VotingToken.sol new file mode 100644 index 0000000..3c1a228 --- /dev/null +++ b/contracts/VotingToken.sol @@ -0,0 +1,241 @@ +pragma solidity >=0.7.0; + +import "./IMVDProxy.sol"; +import "./IERC20.sol"; +import "./IVotingToken.sol"; +import "./IMVDFunctionalityProposalManager.sol"; +import "./IMVDFunctionalitiesManager.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 { + 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) + { + _transfer(msg.sender, recipient, amount); + return true; + } + + function allowance(address owner, address spender) + public + override + view + returns (uint256) + { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) + public + override + returns (bool) + { + _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 ( + _proxy == address(0) || + !(IMVDFunctionalityProposalManager( + IMVDProxy(_proxy).getMVDFunctionalityProposalManagerAddress() + ) + .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( + IMVDFunctionalitiesManager( + IMVDProxy(_proxy).getMVDFunctionalitiesManagerAddress() + ) + .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); + } +} diff --git a/docs/build b/docs/build new file mode 160000 index 0000000..5fd4d62 --- /dev/null +++ b/docs/build @@ -0,0 +1 @@ +Subproject commit 5fd4d623af6c03c5d98c5a7a774462f19285bc6d diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 0000000..d2a0de2 --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,25 @@ +site_name: DFO Protocol +docs_dir: md-build +site_dir: build +permalink: True +nav: + - Home: README.md + - Contributing: CONTRIBUTING.md + - API: + - Contracts: + - ICommonUtilities: contracts/ICommonUtilities.md + - IDoubleProxy: contracts/IDoubleProxy.md + - IMVDFunctionalitiesManager: contracts/IMVDFunctionalitiesManager.md + - IMVDFunctionalityModelsManager: contracts/IMVDFunctionalityModelsManager.md + - IMVDFunctionalityProposal: contracts/IMVDFunctionalityProposal.md + - IMVDFunctionalityProposalManager: contracts/IMVDFunctionalityProposalManager.md + - IMVDProxy: contracts/IMVDProxy.md + - IMVDWallet: contracts/IMVDWallet.md + - IVotingToken: contracts/IVotingToken.md +theme: + name: readthedocs + highlightjs: true + hljs_languages: + - yaml + - typescript + - javascript diff --git a/docs/requirements.in b/docs/requirements.in new file mode 100644 index 0000000..016bb16 --- /dev/null +++ b/docs/requirements.in @@ -0,0 +1 @@ +mkdocs diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..d9819f7 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# pip-compile requirements.in +# +click==7.1.2 # via mkdocs, nltk +future==0.18.2 # via lunr +jinja2==2.11.2 # via mkdocs +joblib==0.16.0 # via nltk +livereload==2.6.3 # via mkdocs +lunr[languages]==0.5.8 # via mkdocs +markdown==3.2.2 # via mkdocs +markupsafe==1.1.1 # via jinja2 +mkdocs==1.1.2 # via -r requirements.in +nltk==3.5 # via lunr +pyyaml==5.3.1 # via mkdocs +regex==2020.7.14 # via nltk +six==1.15.0 # via livereload, lunr +tornado==6.0.4 # via livereload, mkdocs +tqdm==4.48.2 # via nltk diff --git a/package.json b/package.json new file mode 100644 index 0000000..54eb70d --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "dfo-hub-protocol", + "version": "0.5.0", + "main": "index.html", + "repository": "git@github.com:b-u-i-d-l/unifi.git", + "author": "BUIDL ", + "license": "MIT", + "devDependencies": { + "prettier": "^2.1.0", + "prettier-plugin-solidity": "^1.0.0-alpha.56" + }, + "scripts": { + "docs": "yarn docs:docgen && yarn docs:mkdocs:serve", + "docs:build": "yarn docs:docgen && yarn docs:mkdocs:build", + "docs:clean": "rm -r docs/{md-build,build}", + "docs:serve": "yarn docs:docgen && yarn docs:mkdocs:serve", + "docs:docgen": "py-solidity-docgen --input contracts --output docs/md-build && cp *.md docs/md-build", + "docs:mkdocs:build": "cd docs && mkdocs build", + "docs:mkdocs:serve": "yarn docs:docgen && cd docs && mkdocs serve" + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..754350a --- /dev/null +++ b/yarn.lock @@ -0,0 +1,103 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@solidity-parser/parser@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.8.0.tgz#e56fe5f0e29753f928c8fb1ddf5f59e5d71363e9" + integrity sha512-4Eg1iWe6ZuJC9Ynfd8D2cnu06So0QL6V3i+fgQRqT8twPMr+N+kUvS5K7ILgWpuoAag/jb3r0wBDfmpib+yvaw== + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +dir-to-object@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dir-to-object/-/dir-to-object-2.0.0.tgz#29723e9bd1c3e58e4f307bd04ff634c0370c8f8a" + integrity sha512-sXs0JKIhymON7T1UZuO2Ud6VTNAx/VTBXIl4+3mjb2RgfOpt+hectX0x04YqPOPdkeOAKoJuKqwqnXXURNPNEA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.0.0.tgz#48a2309cc8a1d2e9d23bc6a67c39b63032e76ea4" + integrity sha512-6p1NII1Vm62wni/VR/cUMauVQoxmLVb9csqQlvLz+hO2gk8U2UYDfXHQSUYIBKmZwAKz867IDqG7B+u0mj+M6w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +esprima-extract-comments@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/esprima-extract-comments/-/esprima-extract-comments-1.1.0.tgz#0dacab567a5900240de6d344cf18c33617becbc9" + integrity sha512-sBQUnvJwpeE9QnPrxh7dpI/dp67erYG4WXEAreAMoelPRpMR7NWb4YtwRPn9b+H1uLQKl/qS8WYmyaljTpjIsw== + dependencies: + esprima "^4.0.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +extract-comments@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/extract-comments/-/extract-comments-1.1.0.tgz#b90bca033a056bd69b8ba1c6b6b120fc2ee95c18" + integrity sha512-dzbZV2AdSSVW/4E7Ti5hZdHWbA+Z80RJsJhr5uiL10oyjl/gy7/o+HI1HwK4/WSZhlq4SNKU3oUzXlM13Qx02Q== + dependencies: + esprima-extract-comments "^1.1.0" + parse-code-context "^1.0.0" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +parse-code-context@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-code-context/-/parse-code-context-1.0.0.tgz#718c295c593d0d19a37f898473268cc75e98de1e" + integrity sha512-OZQaqKaQnR21iqhlnPfVisFjBWjhnMl5J9MgbP8xC+EwoVqbXrq78lp+9Zb3ahmLzrIX5Us/qbvBnaS3hkH6OA== + +prettier-plugin-solidity@^1.0.0-alpha.56: + version "1.0.0-alpha.57" + resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.0.0-alpha.57.tgz#972190efe96bce816f3af3f74948c8b86ad0abb1" + integrity sha512-GTcL3OzxPn54TMlopE+wcXOUfEnpw05jvJw3KJrXkWTHER1CbkuAFRP4xiFtbJYq35ssSRO5FN4yMshx2grTIw== + dependencies: + "@solidity-parser/parser" "^0.8.0" + dir-to-object "^2.0.0" + emoji-regex "^9.0.0" + escape-string-regexp "^4.0.0" + extract-comments "^1.1.0" + prettier "^2.0.5" + semver "^7.3.2" + string-width "^4.2.0" + +prettier@^2.0.5, prettier@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5" + integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg== + +semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + +string-width@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0"