├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .solcover.js ├── .soliumignore ├── .soliumrc.json ├── .travis.yml ├── README.md ├── contracts ├── AbstractDeployer.sol ├── BasicMultiToken.sol ├── FeeBasicMultiToken.sol ├── FeeFundMultiToken.sol ├── FeeMultiToken.sol ├── FundMultiToken.sol ├── Migrations.sol ├── MultiToken.sol ├── RemoteToken.sol ├── ext │ ├── CheckedERC20.sol │ ├── ERC1003Token.sol │ ├── EtherToken.sol │ └── ExternalCall.sol ├── implementation │ ├── AstraBasicMultiToken.sol │ ├── AstraMultiToken.sol │ ├── EOSToken.sol │ └── deployers │ │ ├── AstraBasicMultiTokenDeployer.sol │ │ └── AstraMultiTokenDeployer.sol ├── interface │ ├── IBasicMultiToken.sol │ ├── IFundMultiToken.sol │ ├── IMultiToken.sol │ └── IMultiTokenInfo.sol └── network │ ├── MultiBuyer.sol │ ├── MultiChanger.sol │ ├── MultiSeller.sol │ ├── MultiShopper.sol │ ├── MultiTokenInfo.sol │ └── MultiTokenNetwork.sol ├── docs ├── css │ ├── checkbox.css │ └── list.css ├── index.html └── js │ ├── lib │ └── web3.min.js │ ├── multitoken.js │ └── web3.min.js ├── index.html ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── package-lock.json ├── package.json ├── scripts ├── arbiter.js ├── coverage.sh └── test.sh ├── test ├── BasicMultiToken.js ├── MultiToken.js ├── MultiTokenInfo.js ├── helpers │ ├── EVMRevert.js │ ├── EVMThrow.js │ ├── advanceToBlock.js │ ├── assertJump.js │ ├── assertRevert.js │ ├── ether.js │ ├── expectEvent.js │ ├── expectThrow.js │ ├── increaseTime.js │ ├── latestTime.js │ ├── makeInterfaceId.js │ ├── merkleTree.js │ ├── sendTransaction.js │ ├── sign.js │ ├── transactionMined.js │ └── web3.js └── impl │ ├── BadToken.sol │ ├── BrokenTransferFromToken.sol │ ├── BrokenTransferToken.sol │ └── Token.sol └── truffle.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | truffle.js 3 | js/ 4 | coverage/ -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends" : [ 3 | "standard", 4 | "plugin:promise/recommended" 5 | ], 6 | "plugins": [ 7 | "promise" 8 | ], 9 | "env": { 10 | "browser" : true, 11 | "node" : true, 12 | "mocha" : true, 13 | "jest" : true 14 | }, 15 | "globals" : { 16 | "artifacts": false, 17 | "contract": false, 18 | "assert": false, 19 | "web3": false 20 | }, 21 | "rules": { 22 | 23 | // Strict mode 24 | "strict": [2, "global"], 25 | 26 | // Code style 27 | "indent": [2, 4], 28 | "quotes": [2, "single"], 29 | "semi": ["error", "always"], 30 | "space-before-function-paren": ["error", "always"], 31 | "no-use-before-define": 0, 32 | "no-unused-expressions": "off", 33 | "eqeqeq": [2, "smart"], 34 | "dot-notation": [2, {"allowKeywords": true, "allowPattern": ""}], 35 | "no-redeclare": [2, {"builtinGlobals": true}], 36 | "no-trailing-spaces": [2, { "skipBlankLines": true }], 37 | "eol-last": 1, 38 | "comma-spacing": [2, {"before": false, "after": true}], 39 | "camelcase": [2, {"properties": "always"}], 40 | "no-mixed-spaces-and-tabs": [2, "smart-tabs"], 41 | "comma-dangle": [1, "always-multiline"], 42 | "no-dupe-args": 2, 43 | "no-dupe-keys": 2, 44 | "no-debugger": 0, 45 | "no-undef": 2, 46 | "object-curly-spacing": [2, "always"], 47 | "max-len": [2, 200, 2], 48 | "generator-star-spacing": ["error", "before"], 49 | "promise/avoid-new": 0, 50 | "promise/always-return": 0 51 | } 52 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | .DS_Store 4 | .node-xmlhttprequest-sync-* 5 | .idea/* 6 | 7 | coverage.json 8 | coverage/ 9 | coverageEnv/ 10 | allFiredEvents 11 | scTopics -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | copyPackages: ['openzeppelin-solidity'], 3 | skipFiles: ['ext/', 'network/', 'implementation/', 'interface/'], 4 | norpc: true 5 | } 6 | -------------------------------------------------------------------------------- /.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:all", 3 | "plugins": ["security"], 4 | "rules": { 5 | "error-reason": "off", 6 | "indentation": ["error", 4], 7 | "lbrace": "off", 8 | "linebreak-style": ["error", "unix"], 9 | "max-len": ["error", 200], 10 | "arg-overflow": "off", 11 | "no-constant": ["error"], 12 | "no-empty-blocks": "off", 13 | "quotes": ["error", "double"], 14 | "uppercase": "off", 15 | "visibility-first": "error", 16 | "function-order" : "off", 17 | 18 | "security/enforce-explicit-visibility": ["error"], 19 | "security/no-block-members": ["warning"], 20 | "security/no-inline-assembly": ["warning"] 21 | } 22 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # 2 | # https://github.com/sc-forks/solidity-coverage/blob/master/docs/faq.md 3 | # 4 | 5 | sudo: required 6 | dist: trusty 7 | language: node_js 8 | node_js: 9 | - '8' 10 | install: 11 | - npm install 12 | script: 13 | - npm run lint 14 | - npm run test 15 | after_script: 16 | - npm run coverage && cat coverage/lcov.info | coveralls 17 | branches: 18 | only: 19 | - master 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MultiToken 2 | 3 | [](https://travis-ci.org/multitoken/MultiToken) 4 | [](https://coveralls.io/github/multitoken/MultiToken) 5 | 6 | ERC20 token solidity smart contract allowing aggreagate any number of ERC20 tokens in any proportion 7 | 8 | # Installation 9 | 10 | 1. Install local packages with `npm install` 11 | 2. Run tests with `npm test` 12 | 13 | On macOS you also need to install watchman: `brew install watchman` 14 | -------------------------------------------------------------------------------- /contracts/AbstractDeployer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | 5 | 6 | contract AbstractDeployer is Ownable { 7 | function title() public view returns(string); 8 | 9 | function createMultiToken() internal returns(address); 10 | 11 | function deploy(bytes data) 12 | external onlyOwner returns(address result) 13 | { 14 | address mtkn = createMultiToken(); 15 | // solium-disable-next-line security/no-low-level-calls 16 | require(mtkn.call(data), "Bad arbitrary call"); 17 | Ownable(mtkn).transferOwnership(msg.sender); 18 | return mtkn; 19 | } 20 | } -------------------------------------------------------------------------------- /contracts/BasicMultiToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol"; 5 | import "openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol"; 6 | import "openzeppelin-solidity/contracts/introspection/SupportsInterfaceWithLookup.sol"; 7 | import "./ext/CheckedERC20.sol"; 8 | import "./ext/ERC1003Token.sol"; 9 | import "./interface/IBasicMultiToken.sol"; 10 | 11 | 12 | contract BasicMultiToken is Ownable, StandardToken, DetailedERC20, ERC1003Token, IBasicMultiToken, SupportsInterfaceWithLookup { 13 | using CheckedERC20 for ERC20; 14 | using CheckedERC20 for DetailedERC20; 15 | 16 | ERC20[] private _tokens; 17 | uint private _inLendingMode; 18 | bool private _bundlingEnabled = true; 19 | 20 | event Bundle(address indexed who, address indexed beneficiary, uint256 value); 21 | event Unbundle(address indexed who, address indexed beneficiary, uint256 value); 22 | event BundlingStatus(bool enabled); 23 | 24 | modifier notInLendingMode { 25 | require(_inLendingMode == 0, "Operation can't be performed while lending"); 26 | _; 27 | } 28 | 29 | modifier whenBundlingEnabled { 30 | require(_bundlingEnabled, "Bundling is disabled"); 31 | _; 32 | } 33 | 34 | constructor() 35 | public DetailedERC20("", "", 0) 36 | { 37 | } 38 | 39 | function init(ERC20[] tokens, string theName, string theSymbol, uint8 theDecimals) public { 40 | require(decimals == 0, "constructor: decimals should be zero"); 41 | require(theDecimals > 0, "constructor: _decimals should not be zero"); 42 | require(bytes(theName).length > 0, "constructor: name should not be empty"); 43 | require(bytes(theSymbol).length > 0, "constructor: symbol should not be empty"); 44 | require(tokens.length >= 2, "Contract does not support less than 2 inner tokens"); 45 | 46 | name = theName; 47 | symbol = theSymbol; 48 | decimals = theDecimals; 49 | _tokens = tokens; 50 | 51 | _registerInterface(InterfaceId_IBasicMultiToken); 52 | } 53 | 54 | function tokensCount() public view returns(uint) { 55 | return _tokens.length; 56 | } 57 | 58 | function tokens(uint i) public view returns(ERC20) { 59 | return _tokens[i]; 60 | } 61 | 62 | function inLendingMode() public view returns(uint) { 63 | return _inLendingMode; 64 | } 65 | 66 | function bundlingEnabled() public view returns(bool) { 67 | return _bundlingEnabled; 68 | } 69 | 70 | function bundleFirstTokens(address beneficiary, uint256 amount, uint256[] tokenAmounts) public whenBundlingEnabled notInLendingMode { 71 | require(totalSupply_ == 0, "bundleFirstTokens: This method can be used with zero total supply only"); 72 | _bundle(beneficiary, amount, tokenAmounts); 73 | } 74 | 75 | function bundle(address beneficiary, uint256 amount) public whenBundlingEnabled notInLendingMode { 76 | require(totalSupply_ != 0, "This method can be used with non zero total supply only"); 77 | uint256[] memory tokenAmounts = new uint256[](_tokens.length); 78 | for (uint i = 0; i < _tokens.length; i++) { 79 | tokenAmounts[i] = _tokens[i].balanceOf(this).mul(amount).div(totalSupply_); 80 | } 81 | _bundle(beneficiary, amount, tokenAmounts); 82 | } 83 | 84 | function unbundle(address beneficiary, uint256 value) public notInLendingMode { 85 | unbundleSome(beneficiary, value, _tokens); 86 | } 87 | 88 | function unbundleSome(address beneficiary, uint256 value, ERC20[] someTokens) public notInLendingMode { 89 | _unbundle(beneficiary, value, someTokens); 90 | } 91 | 92 | // Admin methods 93 | 94 | function disableBundling() public onlyOwner { 95 | require(_bundlingEnabled, "Bundling is already disabled"); 96 | _bundlingEnabled = false; 97 | emit BundlingStatus(false); 98 | } 99 | 100 | function enableBundling() public onlyOwner { 101 | require(!_bundlingEnabled, "Bundling is already enabled"); 102 | _bundlingEnabled = true; 103 | emit BundlingStatus(true); 104 | } 105 | 106 | // Internal methods 107 | 108 | function _bundle(address beneficiary, uint256 amount, uint256[] tokenAmounts) internal { 109 | require(amount != 0, "Bundling amount should be non-zero"); 110 | require(_tokens.length == tokenAmounts.length, "Lenghts of _tokens and tokenAmounts array should be equal"); 111 | 112 | for (uint i = 0; i < _tokens.length; i++) { 113 | require(tokenAmounts[i] != 0, "Token amount should be non-zero"); 114 | _tokens[i].checkedTransferFrom(msg.sender, this, tokenAmounts[i]); 115 | } 116 | 117 | totalSupply_ = totalSupply_.add(amount); 118 | balances[beneficiary] = balances[beneficiary].add(amount); 119 | emit Bundle(msg.sender, beneficiary, amount); 120 | emit Transfer(0, beneficiary, amount); 121 | } 122 | 123 | function _unbundle(address beneficiary, uint256 value, ERC20[] someTokens) internal { 124 | require(someTokens.length > 0, "Array of someTokens can't be empty"); 125 | 126 | uint256 totalSupply = totalSupply_; 127 | balances[msg.sender] = balances[msg.sender].sub(value); 128 | totalSupply_ = totalSupply.sub(value); 129 | emit Unbundle(msg.sender, beneficiary, value); 130 | emit Transfer(msg.sender, 0, value); 131 | 132 | for (uint i = 0; i < someTokens.length; i++) { 133 | for (uint j = 0; j < i; j++) { 134 | require(someTokens[i] != someTokens[j], "unbundleSome: should not unbundle same token multiple times"); 135 | } 136 | uint256 tokenAmount = someTokens[i].balanceOf(this).mul(value).div(totalSupply); 137 | someTokens[i].checkedTransfer(beneficiary, tokenAmount); 138 | } 139 | } 140 | 141 | // Instant Loans 142 | 143 | function lend(address to, ERC20 token, uint256 amount, address target, bytes data) public payable { 144 | uint256 prevBalance = token.balanceOf(this); 145 | token.asmTransfer(to, amount); 146 | _inLendingMode += 1; 147 | require(caller().makeCall.value(msg.value)(target, data), "lend: arbitrary call failed"); 148 | _inLendingMode -= 1; 149 | require(token.balanceOf(this) >= prevBalance, "lend: lended token must be refilled"); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /contracts/FeeBasicMultiToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 5 | import "./ext/CheckedERC20.sol"; 6 | import "./BasicMultiToken.sol"; 7 | 8 | 9 | contract FeeBasicMultiToken is Ownable, BasicMultiToken { 10 | using CheckedERC20 for ERC20; 11 | 12 | uint256 constant public TOTAL_PERCENTS = 1000000; 13 | uint256 internal _lendFee; 14 | 15 | function lendFee() public view returns(uint256) { 16 | return _lendFee; 17 | } 18 | 19 | function setLendFee(uint256 theLendFee) public onlyOwner { 20 | require(theLendFee <= 30000, "setLendFee: fee should be not greater than 3%"); 21 | _lendFee = theLendFee; 22 | } 23 | 24 | function lend(address to, ERC20 token, uint256 amount, address target, bytes data) public payable { 25 | uint256 expectedBalance = token.balanceOf(this).mul(TOTAL_PERCENTS.add(_lendFee)).div(TOTAL_PERCENTS); 26 | super.lend(to, token, amount, target, data); 27 | require(token.balanceOf(this) >= expectedBalance, "lend: tokens must be returned with lend fee"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contracts/FeeFundMultiToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./FundMultiToken.sol"; 4 | import "./FeeMultiToken.sol"; 5 | 6 | 7 | contract FeeFundMultiToken is FundMultiToken, FeeMultiToken { 8 | } 9 | -------------------------------------------------------------------------------- /contracts/FeeMultiToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 5 | import "./ext/CheckedERC20.sol"; 6 | import "./FeeBasicMultiToken.sol"; 7 | import "./MultiToken.sol"; 8 | 9 | 10 | contract FeeMultiToken is MultiToken, FeeBasicMultiToken { 11 | using CheckedERC20 for ERC20; 12 | 13 | uint256 internal _changeFee; 14 | uint256 internal _referralFee; 15 | 16 | function changeFee() public view returns(uint256) { 17 | return _changeFee; 18 | } 19 | 20 | function referralFee() public view returns(uint256) { 21 | return _referralFee; 22 | } 23 | 24 | function setChangeFee(uint256 theChangeFee) public onlyOwner { 25 | require(theChangeFee <= 30000, "setChangeFee: fee should be not greater than 3%"); 26 | _changeFee = theChangeFee; 27 | } 28 | 29 | function setReferralFee(uint256 theReferralFee) public onlyOwner { 30 | require(theReferralFee <= 500000, "setReferralFee: fee should be not greater than 50% of changeFee"); 31 | _referralFee = theReferralFee; 32 | } 33 | 34 | function getReturn(address fromToken, address toToken, uint256 amount) public view returns(uint256 returnAmount) { 35 | returnAmount = super.getReturn(fromToken, toToken, amount).mul(TOTAL_PERCENTS.sub(_changeFee)).div(TOTAL_PERCENTS); 36 | } 37 | 38 | function change(address fromToken, address toToken, uint256 amount, uint256 minReturn) public returns(uint256 returnAmount) { 39 | returnAmount = changeWithRef(fromToken, toToken, amount, minReturn, 0); 40 | } 41 | 42 | function changeWithRef(address fromToken, address toToken, uint256 amount, uint256 minReturn, address ref) public returns(uint256 returnAmount) { 43 | returnAmount = super.change(fromToken, toToken, amount, minReturn); 44 | uint256 refferalAmount = returnAmount 45 | .mul(_changeFee).div(TOTAL_PERCENTS.sub(_changeFee)) 46 | .mul(_referralFee).div(TOTAL_PERCENTS); 47 | 48 | ERC20(toToken).checkedTransfer(ref, refferalAmount); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/FundMultiToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "./interface/IFundMultiToken.sol"; 5 | import "./MultiToken.sol"; 6 | 7 | 8 | contract OwnableMultiTokenMixin is Ownable, MultiToken { 9 | // 10 | } 11 | 12 | 13 | contract ManageableOrOwnableMultiTokenMixin is OwnableMultiTokenMixin { 14 | // solium-disable-next-line security/no-tx-origin 15 | address private _manager = tx.origin; 16 | 17 | modifier onlyManager { 18 | require(msg.sender == _manager, "Access denied"); 19 | _; 20 | } 21 | 22 | modifier onlyOwnerOrManager { 23 | require(msg.sender == owner || msg.sender == _manager, "Access denied"); 24 | _; 25 | } 26 | 27 | function manager() public view returns(address) { 28 | return _manager; 29 | } 30 | 31 | function transferManager(address newManager) public onlyManager { 32 | require(newManager != address(0), "newManager can't be zero address"); 33 | _manager = newManager; 34 | } 35 | } 36 | 37 | 38 | contract LockableMultiTokenMixin is ManageableOrOwnableMultiTokenMixin { 39 | mapping(address => bool) private _tokenIsLocked; 40 | 41 | function lockToken(address token) public onlyOwnerOrManager { 42 | _tokenIsLocked[token] = true; 43 | } 44 | 45 | function tokenIsLocked(address token) public view returns(bool) { 46 | return _tokenIsLocked[token]; 47 | } 48 | 49 | function getReturn(address fromToken, address toToken, uint256 amount) public view returns(uint256 returnAmount) { 50 | if (!_tokenIsLocked[fromToken] && !_tokenIsLocked[toToken]) { 51 | returnAmount = super.getReturn(fromToken, toToken, amount); 52 | } 53 | } 54 | 55 | function change(address fromToken, address toToken, uint256 amount, uint256 minReturn) public returns(uint256 returnAmount) { 56 | require(!_tokenIsLocked[fromToken], "The _fromToken is locked for exchange by multitoken owner"); 57 | require(!_tokenIsLocked[toToken], "The _toToken is locked for exchange by multitoken owner"); 58 | returnAmount = super.change(fromToken, toToken, amount, minReturn); 59 | } 60 | } 61 | 62 | 63 | contract FundMultiToken is IFundMultiToken, LockableMultiTokenMixin { 64 | mapping(address => uint256) private _nextWeights; 65 | uint256 private _nextMinimalWeight; 66 | uint256 private _nextWeightStartBlock; 67 | uint256 private _nextWeightBlockDelay = 100; 68 | uint256 private _nextWeightBlockDelayUpdate; 69 | 70 | event WeightsChanged(uint256 startingBlockNumber, uint256 endingBlockNumber, uint256 nextWeightBlockDelay); 71 | 72 | function init(ERC20[] tokens, uint256[] tokenWeights, string theName, string theSymbol, uint8 theDecimals) public { 73 | super.init(tokens, tokenWeights, theName, theSymbol, theDecimals); 74 | _registerInterface(InterfaceId_IFundMultiToken); 75 | } 76 | 77 | function nextWeights(address token) public view returns(uint256) { 78 | return _nextWeights[token]; 79 | } 80 | 81 | function nextWeightStartBlock() public view returns(uint256) { 82 | return _nextWeightStartBlock; 83 | } 84 | 85 | function nextWeightBlockDelay() public view returns(uint256) { 86 | return _nextWeightBlockDelay; 87 | } 88 | 89 | function weights(address token) public view returns(uint256) { 90 | if (_nextWeightStartBlock == 0) { 91 | return weights(token); 92 | } 93 | 94 | uint256 blockProgress = block.number - _nextWeightStartBlock; 95 | if (blockProgress < _nextWeightBlockDelay) { 96 | linearInterpolation(weights(token), _nextWeights[token], blockProgress, _nextWeightBlockDelay); 97 | } 98 | return _nextWeights[token]; 99 | } 100 | 101 | function setNextWeightBlockDelay(uint256 theNextWeightBlockDelay) public onlyOwner { 102 | if (block.number > _nextWeightStartBlock.add(_nextWeightBlockDelay)) { 103 | _nextWeightBlockDelay = theNextWeightBlockDelay; 104 | } else { 105 | _nextWeightBlockDelayUpdate = theNextWeightBlockDelay; 106 | } 107 | } 108 | 109 | function changeWeights(uint256[] theNextWeights) public onlyManager { 110 | require(theNextWeights.length == tokensCount(), "theNextWeights array length should match tokens length"); 111 | require(block.number.sub(_nextWeightStartBlock) > _nextWeightBlockDelay, "Previous weights changed is not completed yet"); 112 | 113 | // Migrate previous weights 114 | if (_nextWeightStartBlock != 0) { 115 | for (uint i = 0; i < tokensCount(); i++) { 116 | setWeight(tokens(i), _nextWeights[tokens(i)]); 117 | } 118 | _minimalWeight = _nextMinimalWeight; 119 | if (_nextWeightBlockDelayUpdate > 0) { 120 | _nextWeightBlockDelay = _nextWeightBlockDelayUpdate; 121 | _nextWeightBlockDelayUpdate = 0; 122 | } 123 | } 124 | 125 | uint256 nextMinimalWeight = 0; 126 | _nextWeightStartBlock = block.number; 127 | for (i = 0; i < tokensCount(); i++) { 128 | require(theNextWeights[i] != 0, "The theNextWeights array should not contains zeros"); 129 | _nextWeights[tokens(i)] = theNextWeights[i]; 130 | if (nextMinimalWeight == 0 || theNextWeights[i] < nextMinimalWeight) { 131 | nextMinimalWeight = theNextWeights[i]; 132 | } 133 | } 134 | _nextMinimalWeight = nextMinimalWeight; 135 | } 136 | 137 | function getReturn(address fromToken, address toToken, uint256 amount) public view returns(uint256) { 138 | if (fromToken == toToken) { 139 | return 0; 140 | } 141 | 142 | uint256 blockProgress = block.number - _nextWeightStartBlock; 143 | uint256 scaledFromWeight = _minimalWeight; 144 | uint256 scaledToWeight = weights(fromToken); 145 | uint256 scaledMinWeight = weights(toToken); 146 | if (blockProgress < _nextWeightBlockDelay) { 147 | scaledFromWeight = linearInterpolation(weights(fromToken), _nextWeights[fromToken], blockProgress, _nextWeightBlockDelay); 148 | scaledToWeight = linearInterpolation(weights(toToken), _nextWeights[toToken], blockProgress, _nextWeightBlockDelay); 149 | scaledMinWeight = linearInterpolation(_minimalWeight, _nextMinimalWeight, blockProgress, _nextWeightBlockDelay); 150 | } 151 | 152 | // uint256 fromBalance = ERC20(fromToken).balanceOf(this); 153 | // uint256 toBalance = ERC20(toToken).balanceOf(this); 154 | return amount.mul(ERC20(toToken).balanceOf(this)).mul(scaledFromWeight).div( 155 | amount.mul(scaledFromWeight).div(scaledMinWeight).add(ERC20(fromToken).balanceOf(this)).mul(scaledToWeight) 156 | ); 157 | } 158 | 159 | function change(address fromToken, address toToken, uint256 amount, uint256 minReturn) public whenChangesEnabled notInLendingMode returns(uint256) { 160 | if (block.number > _nextWeightStartBlock.add(_nextWeightBlockDelay)) { 161 | _nextWeightStartBlock = 0; 162 | } 163 | return super.change(fromToken, toToken, amount, minReturn); 164 | } 165 | 166 | function _bundle(address beneficiary, uint256 amount, uint256[] tokenAmounts) internal { 167 | if (totalSupply_ > 0) { 168 | _nextWeightBlockDelay = _nextWeightBlockDelay.mul(totalSupply_.add(amount)).div(totalSupply_); 169 | } else { 170 | _nextWeightBlockDelay = 100; 171 | } 172 | return super._bundle(beneficiary, amount, tokenAmounts); 173 | } 174 | 175 | function _unbundle(address beneficiary, uint256 value, ERC20[] someTokens) internal { 176 | if (totalSupply_ > value) { 177 | _nextWeightBlockDelay = _nextWeightBlockDelay.mul(totalSupply_.sub(value)).div(totalSupply_); 178 | } else { 179 | _nextWeightBlockDelay = 100; 180 | } 181 | return super._unbundle(beneficiary, value, someTokens); 182 | } 183 | 184 | function linearInterpolation(uint256 a, uint256 b, uint256 _mul, uint256 _notDiv) internal pure returns(uint256) { 185 | if (a < b) { 186 | return a.mul(_notDiv).add(b.sub(a).mul(_mul)); 187 | } 188 | return b.mul(_notDiv).add(a.sub(b).mul(_mul)); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | 4 | contract Migrations { 5 | 6 | address public owner; 7 | uint public lastCompletedMigration; 8 | 9 | modifier restricted() { 10 | if (msg.sender == owner) _; 11 | } 12 | 13 | constructor() public { 14 | owner = msg.sender; 15 | } 16 | 17 | function setCompleted(uint completed) public restricted { 18 | lastCompletedMigration = completed; 19 | } 20 | 21 | function upgrade(address newAddress) public restricted { 22 | Migrations upgraded = Migrations(newAddress); 23 | upgraded.setCompleted(lastCompletedMigration); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /contracts/MultiToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./ext/CheckedERC20.sol"; 4 | import "./interface/IMultiToken.sol"; 5 | import "./BasicMultiToken.sol"; 6 | 7 | 8 | contract MultiToken is IMultiToken, BasicMultiToken { 9 | using CheckedERC20 for ERC20; 10 | 11 | mapping(address => uint256) private _weights; 12 | uint256 internal _minimalWeight; 13 | bool private _changesEnabled = true; 14 | 15 | event ChangesDisabled(); 16 | 17 | modifier whenChangesEnabled { 18 | require(_changesEnabled, "Operation can't be performed because changes are disabled"); 19 | _; 20 | } 21 | 22 | function weights(address _token) public view returns(uint256) { 23 | return _weights[_token]; 24 | } 25 | 26 | function changesEnabled() public view returns(bool) { 27 | return _changesEnabled; 28 | } 29 | 30 | function init(ERC20[] tokens, uint256[] tokenWeights, string theName, string theSymbol, uint8 theDecimals) public { 31 | super.init(tokens, theName, theSymbol, theDecimals); 32 | require(tokenWeights.length == tokens.length, "Lenghts of tokens and tokenWeights array should be equal"); 33 | 34 | uint256 minimalWeight = 0; 35 | for (uint i = 0; i < tokens.length; i++) { 36 | require(tokenWeights[i] != 0, "The tokenWeights array should not contains zeros"); 37 | require(_weights[tokens[i]] == 0, "The tokens array have duplicates"); 38 | _weights[tokens[i]] = tokenWeights[i]; 39 | if (minimalWeight == 0 || tokenWeights[i] < minimalWeight) { 40 | minimalWeight = tokenWeights[i]; 41 | } 42 | } 43 | _minimalWeight = minimalWeight; 44 | 45 | _registerInterface(InterfaceId_IMultiToken); 46 | } 47 | 48 | function getReturn(address fromToken, address toToken, uint256 amount) public view returns(uint256 returnAmount) { 49 | if (_weights[fromToken] > 0 && _weights[toToken] > 0 && fromToken != toToken) { 50 | uint256 fromBalance = ERC20(fromToken).balanceOf(this); 51 | uint256 toBalance = ERC20(toToken).balanceOf(this); 52 | returnAmount = amount.mul(toBalance).mul(_weights[fromToken]).div( 53 | amount.mul(_weights[fromToken]).div(_minimalWeight).add(fromBalance).mul(_weights[toToken]) 54 | ); 55 | } 56 | } 57 | 58 | function change(address fromToken, address toToken, uint256 amount, uint256 minReturn) public whenChangesEnabled notInLendingMode returns(uint256 returnAmount) { 59 | returnAmount = getReturn(fromToken, toToken, amount); 60 | require(returnAmount > 0, "The return amount is zero"); 61 | require(returnAmount >= minReturn, "The return amount is less than minReturn value"); 62 | 63 | ERC20(fromToken).checkedTransferFrom(msg.sender, this, amount); 64 | ERC20(toToken).checkedTransfer(msg.sender, returnAmount); 65 | 66 | emit Change(fromToken, toToken, msg.sender, amount, returnAmount); 67 | } 68 | 69 | // Admin methods 70 | 71 | function disableChanges() public onlyOwner { 72 | require(_changesEnabled, "Changes are already disabled"); 73 | _changesEnabled = false; 74 | emit ChangesDisabled(); 75 | } 76 | 77 | // Internal methods 78 | 79 | function setWeight(address token, uint256 newWeight) internal { 80 | _weights[token] = newWeight; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /contracts/RemoteToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/token/ERC20/MintableToken.sol"; 4 | import "openzeppelin-solidity/contracts/token/ERC20/BurnableToken.sol"; 5 | import "openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol"; 6 | import "openzeppelin-solidity/contracts/ECRecovery.sol"; 7 | 8 | 9 | contract RemoteToken is MintableToken, BurnableToken { 10 | mapping(bytes32 => bool) private _spentSignature; 11 | 12 | modifier isUpToDate(uint256 blockNumber) { 13 | require(block.number <= blockNumber, "Signature is outdated"); 14 | _; 15 | } 16 | 17 | modifier spendSignature(bytes32 r) { 18 | require(!_spentSignature[r], "Signature was used"); 19 | _spentSignature[r] = true; 20 | _; 21 | } 22 | 23 | constructor() public { 24 | } 25 | 26 | function depositEther() public payable onlyOwner { 27 | } 28 | 29 | function withdrawEther(uint256 value) public onlyOwner { 30 | msg.sender.transfer(value); 31 | } 32 | 33 | function mint(address /*to*/, uint256 amount) public onlyOwner returns(bool) { 34 | return super.mint(this, amount); 35 | } 36 | 37 | function burn(uint256 amount) public onlyOwner { 38 | _burn(this, amount); 39 | } 40 | 41 | function buy( 42 | uint256 priceMul, 43 | uint256 priceDiv, 44 | uint256 blockNumber, 45 | bytes32 r, 46 | bytes32 s, 47 | uint8 v 48 | ) 49 | public 50 | payable 51 | spendSignature(r) 52 | isUpToDate(blockNumber) 53 | returns(uint256 amount) 54 | { 55 | bytes memory data = abi.encodePacked(this.buy.selector, msg.value, priceMul, priceDiv, blockNumber); 56 | require(checkOwnerSignature(data, r, s, v), "Signature is invalid"); 57 | amount = msg.value.mul(priceMul).div(priceDiv); 58 | require(this.transfer(msg.sender, amount), "There are no enough tokens available for buying"); 59 | } 60 | 61 | function sell( 62 | uint256 amount, 63 | uint256 priceMul, 64 | uint256 priceDiv, 65 | uint256 blockNumber, 66 | bytes32 r, 67 | bytes32 s, 68 | uint8 v 69 | ) 70 | public 71 | spendSignature(r) 72 | isUpToDate(blockNumber) 73 | returns(uint256 value) 74 | { 75 | bytes memory data = abi.encodePacked(this.sell.selector, amount, priceMul, priceDiv, blockNumber); 76 | require(checkOwnerSignature(data, r, s, v), "Signature is invalid"); 77 | require(this.transferFrom(msg.sender, this, amount), "There are not enough tokens available for selling"); 78 | value = amount.mul(priceMul).div(priceDiv); 79 | msg.sender.transfer(value); 80 | } 81 | 82 | function checkOwnerSignature( 83 | bytes data, 84 | bytes32 r, 85 | bytes32 s, 86 | uint8 v 87 | ) public view returns(bool) { 88 | require(v == 0 || v == 1 || v == 27 || v == 28, "Signature version is invalid"); 89 | bytes32 messageHash = keccak256(data); 90 | bytes32 signedHash = ECRecovery.toEthSignedMessageHash(messageHash); 91 | return owner == ecrecover(signedHash, v < 27 ? v + 27 : v, r, s); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /contracts/ext/CheckedERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 4 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 5 | 6 | 7 | library CheckedERC20 { 8 | using SafeMath for uint; 9 | 10 | function isContract(address addr) internal view returns(bool result) { 11 | // solium-disable-next-line security/no-inline-assembly 12 | assembly { 13 | result := gt(extcodesize(addr), 0) 14 | } 15 | } 16 | 17 | function handleReturnBool() internal pure returns(bool result) { 18 | // solium-disable-next-line security/no-inline-assembly 19 | assembly { 20 | switch returndatasize() 21 | case 0 { // not a std erc20 22 | result := 1 23 | } 24 | case 32 { // std erc20 25 | returndatacopy(0, 0, 32) 26 | result := mload(0) 27 | } 28 | default { // anything else, should revert for safety 29 | revert(0, 0) 30 | } 31 | } 32 | } 33 | 34 | function handleReturnBytes32() internal pure returns(bytes32 result) { 35 | // solium-disable-next-line security/no-inline-assembly 36 | assembly { 37 | switch eq(returndatasize(), 32) // not a std erc20 38 | case 1 { 39 | returndatacopy(0, 0, 32) 40 | result := mload(0) 41 | } 42 | 43 | switch gt(returndatasize(), 32) // std erc20 44 | case 1 { 45 | returndatacopy(0, 64, 32) 46 | result := mload(0) 47 | } 48 | 49 | switch lt(returndatasize(), 32) // anything else, should revert for safety 50 | case 1 { 51 | revert(0, 0) 52 | } 53 | } 54 | } 55 | 56 | function asmTransfer(address token, address to, uint256 value) internal returns(bool) { 57 | require(isContract(token)); 58 | // solium-disable-next-line security/no-low-level-calls 59 | require(token.call(bytes4(keccak256("transfer(address,uint256)")), to, value)); 60 | return handleReturnBool(); 61 | } 62 | 63 | function asmTransferFrom(address token, address from, address to, uint256 value) internal returns(bool) { 64 | require(isContract(token)); 65 | // solium-disable-next-line security/no-low-level-calls 66 | require(token.call(bytes4(keccak256("transferFrom(address,address,uint256)")), from, to, value)); 67 | return handleReturnBool(); 68 | } 69 | 70 | function asmApprove(address token, address spender, uint256 value) internal returns(bool) { 71 | require(isContract(token)); 72 | // solium-disable-next-line security/no-low-level-calls 73 | require(token.call(bytes4(keccak256("approve(address,uint256)")), spender, value)); 74 | return handleReturnBool(); 75 | } 76 | 77 | // 78 | 79 | function checkedTransfer(ERC20 token, address to, uint256 value) internal { 80 | if (value > 0) { 81 | uint256 balance = token.balanceOf(this); 82 | asmTransfer(token, to, value); 83 | require(token.balanceOf(this) == balance.sub(value), "checkedTransfer: Final balance didn't match"); 84 | } 85 | } 86 | 87 | function checkedTransferFrom(ERC20 token, address from, address to, uint256 value) internal { 88 | if (value > 0) { 89 | uint256 toBalance = token.balanceOf(to); 90 | asmTransferFrom(token, from, to, value); 91 | require(token.balanceOf(to) == toBalance.add(value), "checkedTransfer: Final balance didn't match"); 92 | } 93 | } 94 | 95 | // 96 | 97 | function asmName(address token) internal view returns(bytes32) { 98 | require(isContract(token)); 99 | // solium-disable-next-line security/no-low-level-calls 100 | require(token.call(bytes4(keccak256("name()")))); 101 | return handleReturnBytes32(); 102 | } 103 | 104 | function asmSymbol(address token) internal view returns(bytes32) { 105 | require(isContract(token)); 106 | // solium-disable-next-line security/no-low-level-calls 107 | require(token.call(bytes4(keccak256("symbol()")))); 108 | return handleReturnBytes32(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /contracts/ext/ERC1003Token.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 5 | 6 | 7 | contract ERC1003Caller is Ownable { 8 | function makeCall(address target, bytes data) external payable onlyOwner returns (bool) { 9 | // solium-disable-next-line security/no-call-value 10 | return target.call.value(msg.value)(data); 11 | } 12 | } 13 | 14 | 15 | contract ERC1003Token is ERC20 { 16 | ERC1003Caller private _caller = new ERC1003Caller(); 17 | address[] internal _sendersStack; 18 | 19 | function caller() public view returns(ERC1003Caller) { 20 | return _caller; 21 | } 22 | 23 | function approveAndCall(address to, uint256 value, bytes data) public payable returns (bool) { 24 | _sendersStack.push(msg.sender); 25 | approve(to, value); 26 | require(_caller.makeCall.value(msg.value)(to, data)); 27 | _sendersStack.length -= 1; 28 | return true; 29 | } 30 | 31 | function transferAndCall(address to, uint256 value, bytes data) public payable returns (bool) { 32 | transfer(to, value); 33 | require(_caller.makeCall.value(msg.value)(to, data)); 34 | return true; 35 | } 36 | 37 | function transferFrom(address from, address to, uint256 value) public returns (bool) { 38 | address spender = (from != address(_caller)) ? from : _sendersStack[_sendersStack.length - 1]; 39 | return super.transferFrom(spender, to, value); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contracts/ext/EtherToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/token/ERC20/MintableToken.sol"; 4 | import "openzeppelin-solidity/contracts/token/ERC20/BurnableToken.sol"; 5 | 6 | 7 | contract EtherToken is MintableToken, BurnableToken { 8 | constructor() public { 9 | delete owner; 10 | } 11 | 12 | function() public payable { 13 | deposit(); 14 | } 15 | 16 | function deposit() public payable { 17 | depositTo(msg.sender); 18 | } 19 | 20 | function depositTo(address to) public payable { 21 | owner = to; 22 | mint(to, msg.value); 23 | delete owner; 24 | } 25 | 26 | function withdraw(uint amount) public { 27 | withdrawTo(msg.sender, amount); 28 | } 29 | 30 | function withdrawTo(address to, uint amount) public { 31 | burn(amount); 32 | to.transfer(amount); 33 | } 34 | 35 | function withdrawFrom(address from, uint amount) public { 36 | this.transferFrom(from, this, amount); 37 | this.burn(amount); 38 | from.transfer(amount); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/ext/ExternalCall.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | 4 | library ExternalCall { 5 | // Source: https://github.com/gnosis/MultiSigWallet/blob/master/contracts/MultiSigWallet.sol 6 | // call has been separated into its own function in order to take advantage 7 | // of the Solidity's code generator to produce a loop that copies tx.data into memory. 8 | function externalCall(address destination, uint value, bytes data, uint dataOffset, uint dataLength) internal returns(bool result) { 9 | // solium-disable-next-line security/no-inline-assembly 10 | assembly { 11 | let x := mload(0x40) // "Allocate" memory for output (0x40 is where "free memory" pointer is stored by convention) 12 | let d := add(data, 32) // First 32 bytes are the padded length of data, so exclude that 13 | result := call( 14 | sub(gas, 34710), // 34710 is the value that solidity is currently emitting 15 | // It includes callGas (700) + callVeryLow (3, to pay for SUB) + callValueTransferGas (9000) + 16 | // callNewAccountGas (25000, in case the destination address does not exist and needs creating) 17 | destination, 18 | value, 19 | add(d, dataOffset), 20 | dataLength, // Size of the input (in bytes) - this is what fixes the padding problem 21 | x, 22 | 0 // Output is ignored, therefore the output size is zero 23 | ) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/implementation/AstraBasicMultiToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../FeeBasicMultiToken.sol"; 4 | 5 | 6 | contract AstraBasicMultiToken is FeeBasicMultiToken { 7 | function init(ERC20[] tokens, string theName, string theSymbol, uint8 /*theDecimals*/) public { 8 | super.init(tokens, theName, theSymbol, 18); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /contracts/implementation/AstraMultiToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../FeeMultiToken.sol"; 4 | 5 | 6 | contract AstraMultiToken is FeeMultiToken { 7 | function init(ERC20[] tokens, uint256[] tokenWeights, string theName, string theSymbol, uint8 /*theDecimals*/) public { 8 | super.init(tokens, tokenWeights, theName, theSymbol, 18); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /contracts/implementation/EOSToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../RemoteToken.sol"; 4 | 5 | 6 | contract EOSToken is RemoteToken, DetailedERC20 { 7 | constructor() public DetailedERC20("EOSToken", "EOST", 18) { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /contracts/implementation/deployers/AstraBasicMultiTokenDeployer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../../AbstractDeployer.sol"; 4 | import "../AstraBasicMultiToken.sol"; 5 | 6 | 7 | contract AstraBasicMultiTokenDeployer is AbstractDeployer { 8 | function title() public view returns(string) { 9 | return "AstraBasicMultiTokenDeployer"; 10 | } 11 | 12 | function createMultiToken() internal returns(address) { 13 | return new AstraBasicMultiToken(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/implementation/deployers/AstraMultiTokenDeployer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../../AbstractDeployer.sol"; 4 | import "../AstraMultiToken.sol"; 5 | 6 | 7 | contract AstraMultiTokenDeployer is AbstractDeployer { 8 | function title() public view returns(string) { 9 | return "AstraMultiTokenDeployer"; 10 | } 11 | 12 | function createMultiToken() internal returns(address) { 13 | return new AstraMultiToken(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/interface/IBasicMultiToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 4 | 5 | 6 | contract IBasicMultiToken is ERC20 { 7 | event Bundle(address indexed who, address indexed beneficiary, uint256 value); 8 | event Unbundle(address indexed who, address indexed beneficiary, uint256 value); 9 | 10 | function tokensCount() public view returns(uint256); 11 | function tokens(uint i) public view returns(ERC20); 12 | function bundlingEnabled() public view returns(bool); 13 | 14 | function bundleFirstTokens(address _beneficiary, uint256 _amount, uint256[] _tokenAmounts) public; 15 | function bundle(address _beneficiary, uint256 _amount) public; 16 | 17 | function unbundle(address _beneficiary, uint256 _value) public; 18 | function unbundleSome(address _beneficiary, uint256 _value, ERC20[] _tokens) public; 19 | 20 | // Owner methods 21 | function disableBundling() public; 22 | function enableBundling() public; 23 | 24 | bytes4 public constant InterfaceId_IBasicMultiToken = 0xd5c368b6; 25 | /** 26 | * 0xd5c368b6 === 27 | * bytes4(keccak256('tokensCount()')) ^ 28 | * bytes4(keccak256('tokens(uint256)')) ^ 29 | * bytes4(keccak256('bundlingEnabled()')) ^ 30 | * bytes4(keccak256('bundleFirstTokens(address,uint256,uint256[])')) ^ 31 | * bytes4(keccak256('bundle(address,uint256)')) ^ 32 | * bytes4(keccak256('unbundle(address,uint256)')) ^ 33 | * bytes4(keccak256('unbundleSome(address,uint256,address[])')) ^ 34 | * bytes4(keccak256('disableBundling()')) ^ 35 | * bytes4(keccak256('enableBundling()')) 36 | */ 37 | } 38 | -------------------------------------------------------------------------------- /contracts/interface/IFundMultiToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./IMultiToken.sol"; 4 | 5 | 6 | contract IFundMultiToken is IMultiToken { 7 | function tokenIsLocked(address token) public view returns(bool); 8 | function nextWeights(address token) public view returns(uint256); 9 | function nextWeightStartBlock() public view returns(uint256); 10 | function nextWeightBlockDelay() public view returns(uint256); 11 | 12 | // Manager methods 13 | function changeWeights(uint256[] theNextWeights) public; 14 | 15 | // Owner methods 16 | function lockToken(address token) public; 17 | function setNextWeightBlockDelay(uint256 theNextWeightBlockDelay) public; 18 | 19 | bytes4 public constant InterfaceId_IFundMultiToken = 0xc123b9ad; 20 | /** 21 | * 0xc123b9ad === 22 | * InterfaceId_IMultiToken(0x81624e24) ^ 23 | * bytes4(keccak256('tokenIsLocked(address)')) ^ 24 | * bytes4(keccak256('nextWeights(address)')) ^ 25 | * bytes4(keccak256('nextWeightStartBlock()')) ^ 26 | * bytes4(keccak256('nextWeightBlockDelay()')) ^ 27 | * bytes4(keccak256('changeWeights(uint256[])')) ^ 28 | * bytes4(keccak256('lockToken(address)')) ^ 29 | * bytes4(keccak256('setNextWeightBlockDelay(uint256)')) 30 | */ 31 | } 32 | -------------------------------------------------------------------------------- /contracts/interface/IMultiToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./IBasicMultiToken.sol"; 4 | 5 | 6 | contract IMultiToken is IBasicMultiToken { 7 | event Update(); 8 | event Change(address indexed _fromToken, address indexed _toToken, address indexed _changer, uint256 _amount, uint256 _return); 9 | 10 | function weights(address _token) public view returns(uint256); 11 | function changesEnabled() public view returns(bool); 12 | 13 | function getReturn(address _fromToken, address _toToken, uint256 _amount) public view returns (uint256 returnAmount); 14 | function change(address _fromToken, address _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256 returnAmount); 15 | 16 | // Owner methods 17 | function disableChanges() public; 18 | 19 | bytes4 public constant InterfaceId_IMultiToken = 0x81624e24; 20 | /** 21 | * 0x81624e24 === 22 | * InterfaceId_IBasicMultiToken(0xd5c368b6) ^ 23 | * bytes4(keccak256('weights(address)')) ^ 24 | * bytes4(keccak256('changesEnabled()')) ^ 25 | * bytes4(keccak256('getReturn(address,address,uint256)')) ^ 26 | * bytes4(keccak256('change(address,address,uint256,uint256)')) ^ 27 | * bytes4(keccak256('disableChanges()')) 28 | */ 29 | } 30 | -------------------------------------------------------------------------------- /contracts/interface/IMultiTokenInfo.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./IBasicMultiToken.sol"; 4 | import "./IMultiToken.sol"; 5 | 6 | 7 | contract IMultiTokenInfo { 8 | function allTokens(IBasicMultiToken _mtkn) public view returns(ERC20[] _tokens); 9 | 10 | function allBalances(IBasicMultiToken _mtkn) public view returns(uint256[] _balances); 11 | 12 | function allDecimals(IBasicMultiToken _mtkn) public view returns(uint8[] _decimals); 13 | 14 | function allNames(IBasicMultiToken _mtkn) public view returns(bytes32[] _names); 15 | 16 | function allSymbols(IBasicMultiToken _mtkn) public view returns(bytes32[] _symbols); 17 | 18 | function allTokensBalancesDecimalsNamesSymbols(IBasicMultiToken _mtkn) public view returns( 19 | ERC20[] _tokens, 20 | uint256[] _balances, 21 | uint8[] _decimals, 22 | bytes32[] _names, 23 | bytes32[] _symbols 24 | ); 25 | 26 | // MultiToken 27 | 28 | function allWeights(IMultiToken _mtkn) public view returns(uint256[] _weights); 29 | 30 | function allTokensBalancesDecimalsNamesSymbolsWeights(IMultiToken _mtkn) public view returns( 31 | ERC20[] _tokens, 32 | uint256[] _balances, 33 | uint8[] _decimals, 34 | bytes32[] _names, 35 | bytes32[] _symbols, 36 | uint256[] _weights 37 | ); 38 | 39 | bytes4 public constant InterfaceId_IMultiTokenInfo = 0x6d429c45; 40 | /** 41 | * 0x6d429c45 === 42 | * bytes4(keccak256('allTokens(address)')) ^ 43 | * bytes4(keccak256('allBalances(address)')) ^ 44 | * bytes4(keccak256('allDecimals(address)')) ^ 45 | * bytes4(keccak256('allNames(address)')) ^ 46 | * bytes4(keccak256('allSymbols(address)')) ^ 47 | * bytes4(keccak256('allTokensBalancesDecimalsNamesSymbols(address)')) ^ 48 | * bytes4(keccak256('allWeights(address)')) ^ 49 | * bytes4(keccak256('allTokensBalancesDecimalsNamesSymbolsWeights(address)')) 50 | */ 51 | } -------------------------------------------------------------------------------- /contracts/network/MultiBuyer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 4 | import "../interface/IMultiToken.sol"; 5 | import "../ext/CheckedERC20.sol"; 6 | import "./MultiShopper.sol"; 7 | 8 | 9 | contract MultiBuyer is MultiShopper { 10 | using CheckedERC20 for ERC20; 11 | 12 | function buy( 13 | IMultiToken mtkn, 14 | uint256 minimumReturn, 15 | bytes callDatas, 16 | uint[] starts // including 0 and LENGTH values 17 | ) 18 | public 19 | payable 20 | { 21 | change(callDatas, starts); 22 | 23 | uint mtknTotalSupply = mtkn.totalSupply(); // optimization totalSupply 24 | uint256 bestAmount = uint256(-1); 25 | for (uint i = mtkn.tokensCount(); i > 0; i--) { 26 | ERC20 token = mtkn.tokens(i - 1); 27 | if (token.allowance(this, mtkn) == 0) { 28 | token.asmApprove(mtkn, uint256(-1)); 29 | } 30 | 31 | uint256 amount = mtknTotalSupply.mul(token.balanceOf(this)).div(token.balanceOf(mtkn)); 32 | if (amount < bestAmount) { 33 | bestAmount = amount; 34 | } 35 | } 36 | 37 | require(bestAmount >= minimumReturn, "buy: return value is too low"); 38 | mtkn.bundle(msg.sender, bestAmount); 39 | if (address(this).balance > 0) { 40 | msg.sender.transfer(address(this).balance); 41 | } 42 | for (i = mtkn.tokensCount(); i > 0; i--) { 43 | token = mtkn.tokens(i - 1); 44 | if (token.balanceOf(this) > 0) { 45 | token.asmTransfer(msg.sender, token.balanceOf(this)); 46 | } 47 | } 48 | } 49 | 50 | function buyFirstTokens( 51 | IMultiToken mtkn, 52 | bytes callDatas, 53 | uint[] starts, // including 0 and LENGTH values 54 | uint ethPriceMul, 55 | uint ethPriceDiv 56 | ) 57 | public 58 | payable 59 | { 60 | change(callDatas, starts); 61 | 62 | uint tokensCount = mtkn.tokensCount(); 63 | uint256[] memory amounts = new uint256[](tokensCount); 64 | for (uint i = 0; i < tokensCount; i++) { 65 | ERC20 token = mtkn.tokens(i); 66 | amounts[i] = token.balanceOf(this); 67 | if (token.allowance(this, mtkn) == 0) { 68 | token.asmApprove(mtkn, uint256(-1)); 69 | } 70 | } 71 | 72 | mtkn.bundleFirstTokens(msg.sender, msg.value.mul(ethPriceMul).div(ethPriceDiv), amounts); 73 | if (address(this).balance > 0) { 74 | msg.sender.transfer(address(this).balance); 75 | } 76 | for (i = mtkn.tokensCount(); i > 0; i--) { 77 | token = mtkn.tokens(i - 1); 78 | if (token.balanceOf(this) > 0) { 79 | token.asmTransfer(msg.sender, token.balanceOf(this)); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /contracts/network/MultiChanger.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 4 | import "../interface/IMultiToken.sol"; 5 | import "../ext/CheckedERC20.sol"; 6 | import "../ext/ExternalCall.sol"; 7 | 8 | 9 | contract IEtherToken is ERC20 { 10 | function deposit() public payable; 11 | function withdraw(uint256 amount) public; 12 | } 13 | 14 | 15 | contract MultiChanger { 16 | using SafeMath for uint256; 17 | using CheckedERC20 for ERC20; 18 | using ExternalCall for address; 19 | 20 | function() public payable { 21 | // solium-disable-next-line security/no-tx-origin 22 | require(tx.origin != msg.sender); 23 | } 24 | 25 | function change(bytes callDatas, uint[] starts) public payable { // starts should include 0 and callDatas.length 26 | for (uint i = 0; i < starts.length - 1; i++) { 27 | require(address(this).externalCall(0, callDatas, starts[i], starts[i + 1] - starts[i])); 28 | } 29 | } 30 | 31 | // Ether 32 | 33 | function sendEthValue(address target, uint256 value) external { 34 | // solium-disable-next-line security/no-call-value 35 | require(target.call.value(value)()); 36 | } 37 | 38 | function sendEthProportion(address target, uint256 mul, uint256 div) external { 39 | uint256 value = address(this).balance.mul(mul).div(div); 40 | // solium-disable-next-line security/no-call-value 41 | require(target.call.value(value)()); 42 | } 43 | 44 | // Ether token 45 | 46 | function depositEtherTokenAmount(IEtherToken etherToken, uint256 amount) external { 47 | etherToken.deposit.value(amount)(); 48 | } 49 | 50 | function depositEtherTokenProportion(IEtherToken etherToken, uint256 mul, uint256 div) external { 51 | uint256 amount = address(this).balance.mul(mul).div(div); 52 | etherToken.deposit.value(amount)(); 53 | } 54 | 55 | function withdrawEtherTokenAmount(IEtherToken etherToken, uint256 amount) external { 56 | etherToken.withdraw(amount); 57 | } 58 | 59 | function withdrawEtherTokenProportion(IEtherToken etherToken, uint256 mul, uint256 div) external { 60 | uint256 amount = etherToken.balanceOf(this).mul(mul).div(div); 61 | etherToken.withdraw(amount); 62 | } 63 | 64 | // Token 65 | 66 | function transferTokenAmount(address target, ERC20 fromToken, uint256 amount) external { 67 | require(fromToken.asmTransfer(target, amount)); 68 | } 69 | 70 | function transferTokenProportion(address target, ERC20 fromToken, uint256 mul, uint256 div) external { 71 | uint256 amount = fromToken.balanceOf(this).mul(mul).div(div); 72 | require(fromToken.asmTransfer(target, amount)); 73 | } 74 | 75 | function transferFromTokenAmount(ERC20 fromToken, uint256 amount) external { 76 | // solium-disable-next-line security/no-tx-origin 77 | require(fromToken.asmTransferFrom(tx.origin, this, amount)); 78 | } 79 | 80 | function transferFromTokenProportion(ERC20 fromToken, uint256 mul, uint256 div) external { 81 | uint256 amount = fromToken.balanceOf(this).mul(mul).div(div); 82 | // solium-disable-next-line security/no-tx-origin 83 | require(fromToken.asmTransferFrom(tx.origin, this, amount)); 84 | } 85 | 86 | // MultiToken 87 | 88 | function multitokenChangeAmount(IMultiToken mtkn, ERC20 fromToken, ERC20 toToken, uint256 minReturn, uint256 amount) external { 89 | if (fromToken.allowance(this, mtkn) == 0) { 90 | fromToken.asmApprove(mtkn, uint256(-1)); 91 | } 92 | mtkn.change(fromToken, toToken, amount, minReturn); 93 | } 94 | 95 | function multitokenChangeProportion(IMultiToken mtkn, ERC20 fromToken, ERC20 toToken, uint256 minReturn, uint256 mul, uint256 div) external { 96 | uint256 amount = fromToken.balanceOf(this).mul(mul).div(div); 97 | this.multitokenChangeAmount(mtkn, fromToken, toToken, minReturn, amount); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /contracts/network/MultiSeller.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 4 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 5 | import { IMultiToken } from "../interface/IMultiToken.sol"; 6 | import "../ext/CheckedERC20.sol"; 7 | import "./MultiShopper.sol"; 8 | 9 | 10 | contract MultiSeller is MultiShopper { 11 | using CheckedERC20 for ERC20; 12 | using CheckedERC20 for IMultiToken; 13 | 14 | function() public payable { 15 | // solium-disable-next-line security/no-tx-origin 16 | require(tx.origin != msg.sender); 17 | } 18 | 19 | function sellForOrigin( 20 | IMultiToken mtkn, 21 | uint256 amount, 22 | bytes callDatas, 23 | uint[] starts // including 0 and LENGTH values 24 | ) 25 | public 26 | { 27 | sell( 28 | mtkn, 29 | amount, 30 | callDatas, 31 | starts, 32 | tx.origin // solium-disable-line security/no-tx-origin 33 | ); 34 | } 35 | 36 | function sell( 37 | IMultiToken mtkn, 38 | uint256 amount, 39 | bytes callDatas, 40 | uint[] starts, // including 0 and LENGTH values 41 | address to 42 | ) 43 | public 44 | { 45 | mtkn.asmTransferFrom(msg.sender, this, amount); 46 | mtkn.unbundle(this, amount); 47 | change(callDatas, starts); 48 | to.transfer(address(this).balance); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/network/MultiShopper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 4 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 5 | import "openzeppelin-solidity/contracts/ownership/CanReclaimToken.sol"; 6 | import "../interface/IMultiToken.sol"; 7 | import "../ext/CheckedERC20.sol"; 8 | import "../ext/ExternalCall.sol"; 9 | 10 | 11 | contract IEtherToken is ERC20 { 12 | function deposit() public payable; 13 | function withdraw(uint256 amount) public; 14 | } 15 | 16 | 17 | contract IBancorNetwork { 18 | function convert( 19 | address[] path, 20 | uint256 amount, 21 | uint256 minReturn 22 | ) 23 | public 24 | payable 25 | returns(uint256); 26 | 27 | function claimAndConvert( 28 | address[] path, 29 | uint256 amount, 30 | uint256 minReturn 31 | ) 32 | public 33 | payable 34 | returns(uint256); 35 | } 36 | 37 | 38 | contract IKyberNetworkProxy { 39 | function trade( 40 | address src, 41 | uint srcAmount, 42 | address dest, 43 | address destAddress, 44 | uint maxDestAmount, 45 | uint minConversionRate, 46 | address walletId 47 | ) 48 | public 49 | payable 50 | returns(uint); 51 | } 52 | 53 | 54 | contract MultiShopper is CanReclaimToken { 55 | using SafeMath for uint256; 56 | using CheckedERC20 for ERC20; 57 | using ExternalCall for address; 58 | 59 | function change(bytes callDatas, uint[] starts) public payable { // starts should include 0 and callDatas.length 60 | for (uint i = 0; i < starts.length - 1; i++) { 61 | require(address(this).externalCall(0, callDatas, starts[i], starts[i + 1] - starts[i])); 62 | } 63 | } 64 | 65 | function sendEthValue(address target, bytes data, uint256 value) external { 66 | // solium-disable-next-line security/no-call-value 67 | require(target.call.value(value)(data)); 68 | } 69 | 70 | function sendEthProportion(address target, bytes data, uint256 mul, uint256 div) external { 71 | uint256 value = address(this).balance.mul(mul).div(div); 72 | // solium-disable-next-line security/no-call-value 73 | require(target.call.value(value)(data)); 74 | } 75 | 76 | function approveTokenAmount(address target, bytes data, ERC20 fromToken, uint256 amount) external { 77 | if (fromToken.allowance(this, target) != 0) { 78 | fromToken.asmApprove(target, 0); 79 | } 80 | fromToken.asmApprove(target, amount); 81 | // solium-disable-next-line security/no-low-level-calls 82 | require(target.call(data)); 83 | } 84 | 85 | function approveTokenProportion(address target, bytes data, ERC20 fromToken, uint256 mul, uint256 div) external { 86 | uint256 amount = fromToken.balanceOf(this).mul(mul).div(div); 87 | if (fromToken.allowance(this, target) != 0) { 88 | fromToken.asmApprove(target, 0); 89 | } 90 | fromToken.asmApprove(target, amount); 91 | // solium-disable-next-line security/no-low-level-calls 92 | require(target.call(data)); 93 | } 94 | 95 | function transferTokenAmount(address target, bytes data, ERC20 fromToken, uint256 amount) external { 96 | require(fromToken.asmTransfer(target, amount)); 97 | if (data.length != 0) { 98 | // solium-disable-next-line security/no-low-level-calls 99 | require(target.call(data)); 100 | } 101 | } 102 | 103 | function transferTokenProportion(address target, bytes data, ERC20 fromToken, uint256 mul, uint256 div) external { 104 | uint256 amount = fromToken.balanceOf(this).mul(mul).div(div); 105 | require(fromToken.asmTransfer(target, amount)); 106 | if (data.length != 0) { 107 | // solium-disable-next-line security/no-low-level-calls 108 | require(target.call(data)); 109 | } 110 | } 111 | 112 | function transferTokenProportionToOrigin(ERC20 token, uint256 mul, uint256 div) external { 113 | uint256 amount = token.balanceOf(this).mul(mul).div(div); 114 | // solium-disable-next-line security/no-tx-origin 115 | require(token.asmTransfer(tx.origin, amount)); 116 | } 117 | 118 | // Multitoken 119 | 120 | function multitokenChangeAmount(IMultiToken mtkn, ERC20 fromToken, ERC20 toToken, uint256 minReturn, uint256 amount) external { 121 | if (fromToken.allowance(this, mtkn) == 0) { 122 | fromToken.asmApprove(mtkn, uint256(-1)); 123 | } 124 | mtkn.change(fromToken, toToken, amount, minReturn); 125 | } 126 | 127 | function multitokenChangeProportion(IMultiToken mtkn, ERC20 fromToken, ERC20 toToken, uint256 minReturn, uint256 mul, uint256 div) external { 128 | uint256 amount = fromToken.balanceOf(this).mul(mul).div(div); 129 | this.multitokenChangeAmount(mtkn, fromToken, toToken, minReturn, amount); 130 | } 131 | 132 | // Ether token 133 | 134 | function withdrawEtherTokenAmount(IEtherToken etherToken, uint256 amount) external { 135 | etherToken.withdraw(amount); 136 | } 137 | 138 | function withdrawEtherTokenProportion(IEtherToken etherToken, uint256 mul, uint256 div) external { 139 | uint256 amount = etherToken.balanceOf(this).mul(mul).div(div); 140 | etherToken.withdraw(amount); 141 | } 142 | 143 | // Bancor Network 144 | 145 | function bancorSendEthValue(IBancorNetwork bancor, address[] path, uint256 value) external { 146 | bancor.convert.value(value)(path, value, 1); 147 | } 148 | 149 | function bancorSendEthProportion(IBancorNetwork bancor, address[] path, uint256 mul, uint256 div) external { 150 | uint256 value = address(this).balance.mul(mul).div(div); 151 | bancor.convert.value(value)(path, value, 1); 152 | } 153 | 154 | function bancorApproveTokenAmount(IBancorNetwork bancor, address[] path, uint256 amount) external { 155 | if (ERC20(path[0]).allowance(this, bancor) == 0) { 156 | ERC20(path[0]).asmApprove(bancor, uint256(-1)); 157 | } 158 | bancor.claimAndConvert(path, amount, 1); 159 | } 160 | 161 | function bancorApproveTokenProportion(IBancorNetwork bancor, address[] path, uint256 mul, uint256 div) external { 162 | uint256 amount = ERC20(path[0]).balanceOf(this).mul(mul).div(div); 163 | if (ERC20(path[0]).allowance(this, bancor) == 0) { 164 | ERC20(path[0]).asmApprove(bancor, uint256(-1)); 165 | } 166 | bancor.claimAndConvert(path, amount, 1); 167 | } 168 | 169 | function bancorTransferTokenAmount(IBancorNetwork bancor, address[] path, uint256 amount) external { 170 | ERC20(path[0]).asmTransfer(bancor, amount); 171 | bancor.convert(path, amount, 1); 172 | } 173 | 174 | function bancorTransferTokenProportion(IBancorNetwork bancor, address[] path, uint256 mul, uint256 div) external { 175 | uint256 amount = ERC20(path[0]).balanceOf(this).mul(mul).div(div); 176 | ERC20(path[0]).asmTransfer(bancor, amount); 177 | bancor.convert(path, amount, 1); 178 | } 179 | 180 | function bancorAlreadyTransferedTokenAmount(IBancorNetwork bancor, address[] path, uint256 amount) external { 181 | bancor.convert(path, amount, 1); 182 | } 183 | 184 | function bancorAlreadyTransferedTokenProportion(IBancorNetwork bancor, address[] path, uint256 mul, uint256 div) external { 185 | uint256 amount = ERC20(path[0]).balanceOf(bancor).mul(mul).div(div); 186 | bancor.convert(path, amount, 1); 187 | } 188 | 189 | // Kyber Network 190 | 191 | function kyberSendEthProportion(IKyberNetworkProxy kyber, ERC20 fromToken, address toToken, uint256 mul, uint256 div) external { 192 | uint256 value = address(this).balance.mul(mul).div(div); 193 | kyber.trade.value(value)( 194 | fromToken, 195 | value, 196 | toToken, 197 | this, 198 | 1 << 255, 199 | 0, 200 | 0 201 | ); 202 | } 203 | 204 | function kyberApproveTokenAmount(IKyberNetworkProxy kyber, ERC20 fromToken, address toToken, uint256 amount) external { 205 | if (fromToken.allowance(this, kyber) == 0) { 206 | fromToken.asmApprove(kyber, uint256(-1)); 207 | } 208 | kyber.trade( 209 | fromToken, 210 | amount, 211 | toToken, 212 | this, 213 | 1 << 255, 214 | 0, 215 | 0 216 | ); 217 | } 218 | 219 | function kyberApproveTokenProportion(IKyberNetworkProxy kyber, ERC20 fromToken, address toToken, uint256 mul, uint256 div) external { 220 | uint256 amount = fromToken.balanceOf(this).mul(mul).div(div); 221 | this.kyberApproveTokenAmount(kyber, fromToken, toToken, amount); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /contracts/network/MultiTokenInfo.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol"; 4 | import "openzeppelin-solidity/contracts/introspection/SupportsInterfaceWithLookup.sol"; 5 | import "../interface/IBasicMultiToken.sol"; 6 | import "../interface/IMultiToken.sol"; 7 | import "../interface/IMultiTokenInfo.sol"; 8 | import "../ext/CheckedERC20.sol"; 9 | 10 | 11 | contract MultiTokenInfo is IMultiTokenInfo, SupportsInterfaceWithLookup { 12 | using CheckedERC20 for DetailedERC20; 13 | 14 | constructor() public { 15 | _registerInterface(InterfaceId_IMultiTokenInfo); 16 | } 17 | 18 | // BasicMultiToken 19 | 20 | function allTokens(IBasicMultiToken mtkn) public view returns(ERC20[] tokens) { 21 | tokens = new ERC20[](mtkn.tokensCount()); 22 | for (uint i = 0; i < tokens.length; i++) { 23 | tokens[i] = mtkn.tokens(i); 24 | } 25 | } 26 | 27 | function allBalances(IBasicMultiToken mtkn) public view returns(uint256[] balances) { 28 | balances = new uint256[](mtkn.tokensCount()); 29 | for (uint i = 0; i < balances.length; i++) { 30 | balances[i] = mtkn.tokens(i).balanceOf(mtkn); 31 | } 32 | } 33 | 34 | function allDecimals(IBasicMultiToken mtkn) public view returns(uint8[] decimals) { 35 | decimals = new uint8[](mtkn.tokensCount()); 36 | for (uint i = 0; i < decimals.length; i++) { 37 | decimals[i] = DetailedERC20(mtkn.tokens(i)).decimals(); 38 | } 39 | } 40 | 41 | function allNames(IBasicMultiToken mtkn) public view returns(bytes32[] names) { 42 | names = new bytes32[](mtkn.tokensCount()); 43 | for (uint i = 0; i < names.length; i++) { 44 | names[i] = DetailedERC20(mtkn.tokens(i)).asmName(); 45 | } 46 | } 47 | 48 | function allSymbols(IBasicMultiToken mtkn) public view returns(bytes32[] symbols) { 49 | symbols = new bytes32[](mtkn.tokensCount()); 50 | for (uint i = 0; i < symbols.length; i++) { 51 | symbols[i] = DetailedERC20(mtkn.tokens(i)).asmSymbol(); 52 | } 53 | } 54 | 55 | function allTokensBalancesDecimalsNamesSymbols(IBasicMultiToken mtkn) public view returns( 56 | ERC20[] tokens, 57 | uint256[] balances, 58 | uint8[] decimals, 59 | bytes32[] names, 60 | bytes32[] symbols 61 | ) { 62 | tokens = allTokens(mtkn); 63 | balances = allBalances(mtkn); 64 | decimals = allDecimals(mtkn); 65 | names = allNames(mtkn); 66 | symbols = allSymbols(mtkn); 67 | } 68 | 69 | // MultiToken 70 | 71 | function allWeights(IMultiToken mtkn) public view returns(uint256[] weights) { 72 | weights = new uint256[](mtkn.tokensCount()); 73 | for (uint i = 0; i < weights.length; i++) { 74 | weights[i] = mtkn.weights(mtkn.tokens(i)); 75 | } 76 | } 77 | 78 | function allTokensBalancesDecimalsNamesSymbolsWeights(IMultiToken mtkn) public view returns( 79 | ERC20[] tokens, 80 | uint256[] balances, 81 | uint8[] decimals, 82 | bytes32[] names, 83 | bytes32[] symbols, 84 | uint256[] weights 85 | ) { 86 | (tokens, balances, decimals, names, symbols) = allTokensBalancesDecimalsNamesSymbols(mtkn); 87 | weights = allWeights(mtkn); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /contracts/network/MultiTokenNetwork.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 4 | import "openzeppelin-solidity/contracts/lifecycle/Pausable.sol"; 5 | import "../AbstractDeployer.sol"; 6 | import "../interface/IMultiToken.sol"; 7 | 8 | 9 | contract MultiTokenNetwork is Pausable { 10 | address[] private _multitokens; 11 | AbstractDeployer[] private _deployers; 12 | 13 | event NewMultitoken(address indexed mtkn); 14 | event NewDeployer(uint256 indexed index, address indexed oldDeployer, address indexed newDeployer); 15 | 16 | function multitokensCount() public view returns(uint256) { 17 | return _multitokens.length; 18 | } 19 | 20 | function multitokens(uint i) public view returns(address) { 21 | return _multitokens[i]; 22 | } 23 | 24 | function allMultitokens() public view returns(address[]) { 25 | return _multitokens; 26 | } 27 | 28 | function deployersCount() public view returns(uint256) { 29 | return _deployers.length; 30 | } 31 | 32 | function deployers(uint i) public view returns(AbstractDeployer) { 33 | return _deployers[i]; 34 | } 35 | 36 | function allWalletBalances(address wallet) public view returns(uint256[]) { 37 | uint256[] memory balances = new uint256[](_multitokens.length); 38 | for (uint i = 0; i < _multitokens.length; i++) { 39 | balances[i] = ERC20(_multitokens[i]).balanceOf(wallet); 40 | } 41 | return balances; 42 | } 43 | 44 | function deleteMultitoken(uint index) public onlyOwner { 45 | require(index < _multitokens.length, "deleteMultitoken: index out of range"); 46 | if (index != _multitokens.length - 1) { 47 | _multitokens[index] = _multitokens[_multitokens.length - 1]; 48 | } 49 | _multitokens.length -= 1; 50 | } 51 | 52 | function deleteDeployer(uint index) public onlyOwner { 53 | require(index < _deployers.length, "deleteDeployer: index out of range"); 54 | if (index != _deployers.length - 1) { 55 | _deployers[index] = _deployers[_deployers.length - 1]; 56 | } 57 | _deployers.length -= 1; 58 | } 59 | 60 | function disableBundlingMultitoken(uint index) public onlyOwner { 61 | IBasicMultiToken(_multitokens[index]).disableBundling(); 62 | } 63 | 64 | function enableBundlingMultitoken(uint index) public onlyOwner { 65 | IBasicMultiToken(_multitokens[index]).enableBundling(); 66 | } 67 | 68 | function disableChangesMultitoken(uint index) public onlyOwner { 69 | IMultiToken(_multitokens[index]).disableChanges(); 70 | } 71 | 72 | function addDeployer(AbstractDeployer deployer) public onlyOwner whenNotPaused { 73 | require(deployer.owner() == address(this), "addDeployer: first set MultiTokenNetwork as owner"); 74 | emit NewDeployer(_deployers.length, address(0), deployer); 75 | _deployers.push(deployer); 76 | } 77 | 78 | function setDeployer(uint256 index, AbstractDeployer deployer) public onlyOwner whenNotPaused { 79 | require(deployer.owner() == address(this), "setDeployer: first set MultiTokenNetwork as owner"); 80 | emit NewDeployer(index, _deployers[index], deployer); 81 | _deployers[index] = deployer; 82 | } 83 | 84 | function deploy(uint256 index, bytes data) public whenNotPaused { 85 | address mtkn = _deployers[index].deploy(data); 86 | _multitokens.push(mtkn); 87 | emit NewMultitoken(mtkn); 88 | } 89 | 90 | function makeCall(address target, uint256 value, bytes data) public onlyOwner { 91 | // solium-disable-next-line security/no-call-value 92 | require(target.call.value(value)(data), "Arbitrary call failed"); 93 | } 94 | } -------------------------------------------------------------------------------- /docs/css/checkbox.css: -------------------------------------------------------------------------------- 1 | /* The container */ 2 | .container { 3 | display: block; 4 | position: relative; 5 | padding-left: 35px; 6 | margin-bottom: 12px; 7 | cursor: pointer; 8 | font-size: 22px; 9 | -webkit-user-select: none; 10 | -moz-user-select: none; 11 | -ms-user-select: none; 12 | user-select: none; 13 | } 14 | 15 | /* Hide the browser's default checkbox */ 16 | .container input { 17 | position: absolute; 18 | opacity: 0; 19 | cursor: pointer; 20 | } 21 | 22 | /* Create a custom checkbox */ 23 | .checkmark { 24 | position: absolute; 25 | top: 0; 26 | left: 0; 27 | height: 25px; 28 | width: 25px; 29 | background-color: #eee; 30 | } 31 | 32 | /* On mouse-over, add a grey background color */ 33 | .container:hover input ~ .checkmark { 34 | background-color: #ccc; 35 | } 36 | 37 | /* When the checkbox is checked, add a blue background */ 38 | .container input:checked ~ .checkmark { 39 | background-color: #2196F3; 40 | } 41 | 42 | /* Create the checkmark/indicator (hidden when not checked) */ 43 | .checkmark:after { 44 | content: ""; 45 | position: absolute; 46 | display: none; 47 | } 48 | 49 | /* Show the checkmark when checked */ 50 | .container input:checked ~ .checkmark:after { 51 | display: block; 52 | } 53 | 54 | /* Style the checkmark/indicator */ 55 | .container .checkmark:after { 56 | left: 9px; 57 | top: 5px; 58 | width: 5px; 59 | height: 10px; 60 | border: solid white; 61 | border-width: 0 3px 3px 0; 62 | -webkit-transform: rotate(45deg); 63 | -ms-transform: rotate(45deg); 64 | transform: rotate(45deg); 65 | } -------------------------------------------------------------------------------- /docs/css/list.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | #myInput { 6 | background-position: 10px 12px; 7 | background-repeat: no-repeat; 8 | width: 100%; 9 | font-size: 16px; 10 | padding: 12px 20px 12px 40px; 11 | border: 1px solid #ddd; 12 | margin-bottom: 12px; 13 | } 14 | 15 | #myUL { 16 | list-style-type: none; 17 | padding: 0; 18 | margin: 0; 19 | } 20 | 21 | #myUL li a { 22 | border: 1px solid #ddd; 23 | margin-top: -1px; /* Prevent double borders */ 24 | background-color: #f6f6f6; 25 | padding: 12px; 26 | text-decoration: none; 27 | font-size: 18px; 28 | color: black; 29 | display: block 30 | } 31 | 32 | #myUL li a:hover:not(.header) { 33 | background-color: #eee; 34 | } -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |# | 103 |Name | 104 |Decimals | 105 |Balance | 106 |Weight | 107 |Address | 108 |
---|