├── .dockerignore ├── .editorconfig ├── .gitbook.yaml ├── .gitignore ├── .prettierrc ├── .protocol ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── docs ├── .gitbook │ └── assets │ │ ├── example-deployment-config.json │ │ └── example-fund-config.json ├── README.md ├── SUMMARY.md ├── building-blocks │ ├── contract.md │ ├── environment │ │ ├── README.md │ │ ├── kovan.md │ │ ├── mainnet.md │ │ └── rinkeby.md │ ├── fund-configuration-json.md │ ├── fund-setup.md │ ├── the-basics.md │ ├── transaction.md │ └── useful-patterns.md ├── deployment-files │ └── untitled.md ├── examples │ ├── deploying-policies │ │ ├── README.md │ │ ├── asset-blacklist.md │ │ ├── asset-whitelist.md │ │ ├── max-concentration.md │ │ ├── max-positions.md │ │ ├── pricetolerance.md │ │ └── user-whitelist.md │ ├── fees.md │ ├── fetching-prices.md │ ├── fund-setup.md │ ├── invest.md │ ├── investmentstatus.md │ ├── querying-fund-data.md │ ├── querying-fund-metrics.md │ ├── redeem.md │ └── trading │ │ ├── 0x.md │ │ ├── README.md │ │ ├── kyber.md │ │ ├── oasisdex.md │ │ └── uniswap.md ├── fund-setup.md ├── the-accounting-and-feemanager-contracts │ ├── fees.md │ └── querying-fund-metrics.md ├── the-participation-contract │ ├── invest.md │ ├── list-investors.md │ └── redeem.md ├── the-policy-contract │ └── overview.md └── the-trading-contract │ ├── executing-a-trade.md │ └── untitled.md ├── jekyll.config.yml ├── package.json ├── rollup.config.js ├── scripts └── codegen.js ├── src ├── Address.ts ├── Contract.ts ├── Deployment.ts ├── Environment.test.ts ├── Environment.ts ├── Transaction.ts ├── contracts │ ├── dependencies │ │ ├── authorization │ │ │ ├── DSAuth.ts │ │ │ ├── DSAuthority.ts │ │ │ ├── DSGuard.ts │ │ │ └── PermissiveAuthority.ts │ │ └── token │ │ │ ├── BurnableToken.test.ts │ │ │ ├── BurnableToken.ts │ │ │ ├── ERC20WithFields.ts │ │ │ ├── MaliciousToken.test.ts │ │ │ ├── MaliciousToken.ts │ │ │ ├── PreminedToken.test.ts │ │ │ ├── PreminedToken.ts │ │ │ ├── StandardToken.test.ts │ │ │ ├── StandardToken.ts │ │ │ ├── Weth.test.ts │ │ │ └── Weth.ts │ ├── engine │ │ ├── AmguConsumer.ts │ │ ├── Engine.test.ts │ │ └── Engine.ts │ ├── exchanges │ │ ├── ExchangeAdapter.test.ts │ │ ├── ExchangeAdapter.ts │ │ └── third-party │ │ │ ├── kyber │ │ │ └── KyberNetworkProxy.ts │ │ │ ├── oasisdex │ │ │ ├── OasisDexAccessor.ts │ │ │ ├── OasisDexExchange.test.ts │ │ │ └── OasisDexExchange.ts │ │ │ ├── uniswap │ │ │ ├── UniswapExchange.ts │ │ │ ├── UniswapFactory.test.ts │ │ │ └── UniswapFactory.ts │ │ │ └── zeroex │ │ │ ├── ZeroExV2Exchange.ts │ │ │ └── ZeroExV3Exchange.ts │ ├── factory │ │ ├── Factory.test.ts │ │ ├── Factory.ts │ │ ├── FundFactory.error.ts │ │ ├── FundFactory.test.ts │ │ └── FundFactory.ts │ ├── fund │ │ ├── accounting │ │ │ ├── Accounting.test.ts │ │ │ ├── Accounting.ts │ │ │ ├── AccountingFactory.test.ts │ │ │ └── AccountingFactory.ts │ │ ├── fees │ │ │ ├── FeeManager.test.ts │ │ │ ├── FeeManager.ts │ │ │ ├── FeeManagerFactory.test.ts │ │ │ ├── FeeManagerFactory.ts │ │ │ ├── IFee.test.ts │ │ │ ├── IFee.ts │ │ │ ├── ManagementFee.test.ts │ │ │ ├── ManagementFee.ts │ │ │ ├── PerformanceFee.test.ts │ │ │ └── PerformanceFee.ts │ │ ├── hub │ │ │ ├── Hub.test.ts │ │ │ ├── Hub.ts │ │ │ ├── Spoke.test.ts │ │ │ └── Spoke.ts │ │ ├── participation │ │ │ ├── Participation.errors.ts │ │ │ ├── Participation.test.ts │ │ │ ├── Participation.ts │ │ │ ├── ParticipationFactory.test.ts │ │ │ └── ParticipationFactory.ts │ │ ├── policies │ │ │ ├── AddressList.test.ts │ │ │ ├── AddressList.ts │ │ │ ├── AssetBlacklist.test.ts │ │ │ ├── AssetBlacklist.ts │ │ │ ├── AssetWhitelist.test.ts │ │ │ ├── AssetWhitelist.ts │ │ │ ├── IPolicy.test.ts │ │ │ ├── IPolicy.ts │ │ │ ├── MaxConcentration.test.ts │ │ │ ├── MaxConcentration.ts │ │ │ ├── MaxPositions.test.ts │ │ │ ├── MaxPositions.ts │ │ │ ├── PolicyManager.test.ts │ │ │ ├── PolicyManager.ts │ │ │ ├── PolicyManagerFactory.test.ts │ │ │ ├── PolicyManagerFactory.ts │ │ │ ├── PriceTolerance.test.ts │ │ │ ├── PriceTolerance.ts │ │ │ ├── UserWhitelist.test.ts │ │ │ └── UserWhitelist.ts │ │ ├── shares │ │ │ ├── Shares.test.ts │ │ │ ├── Shares.ts │ │ │ ├── SharesFactory.test.ts │ │ │ └── SharesFactory.ts │ │ ├── trading │ │ │ ├── Trading.errors.ts │ │ │ ├── Trading.test.ts │ │ │ ├── Trading.ts │ │ │ ├── TradingFactory.test.ts │ │ │ ├── TradingFactory.ts │ │ │ ├── exchanges │ │ │ │ ├── BaseTradingAdapter.ts │ │ │ │ ├── KyberTradingAdapter.ts │ │ │ │ ├── MelonEngineTradingAdapter.ts │ │ │ │ ├── OasisDexTradingAdapter.ts │ │ │ │ ├── UniswapTradingAdapter.ts │ │ │ │ ├── ZeroExV2TradingAdapter.ts │ │ │ │ └── ZeroExV3TradingAdapter.ts │ │ │ └── utils │ │ │ │ ├── checkCooldownReached.ts │ │ │ │ ├── checkExistingOpenMakeOrder.ts │ │ │ │ ├── checkFundIsNotShutdown.ts │ │ │ │ ├── checkSenderIsFundManager.ts │ │ │ │ └── checkSufficientBalance.ts │ │ └── vault │ │ │ ├── Vault.test.ts │ │ │ ├── Vault.ts │ │ │ ├── VaultFactory.test.ts │ │ │ └── VaultFactory.ts │ ├── prices │ │ ├── IPriceSource.ts │ │ ├── KyberPriceFeed.test.ts │ │ ├── KyberPriceFeed.ts │ │ └── TestingPriceFeed.ts │ └── version │ │ ├── Registry.error.ts │ │ ├── Registry.test.ts │ │ ├── Registry.ts │ │ ├── Version.test.ts │ │ └── Version.ts ├── errors │ ├── CallError.ts │ ├── HubIsShutdownError.ts │ ├── InsufficientAllowanceError.ts │ ├── OutOfBalanceError.ts │ ├── SpokeNotInitializedError.ts │ ├── ValidationError.ts │ └── ZeroAddressError.ts ├── index.ts └── utils │ ├── applyMixins.ts │ ├── availableExchanges.ts │ ├── availablePolicies.ts │ ├── availableTokens.ts │ ├── encodeFunctionSignature.ts │ ├── functionSignature.ts │ ├── hexToString.ts │ ├── includesAddress.ts │ ├── isZeroAddress.ts │ ├── range.ts │ ├── sameAddress.ts │ ├── stringToBytes.ts │ ├── tests │ ├── createTestEnvironment.ts │ ├── deployAccounting.ts │ ├── deployEngine.ts │ ├── deployFeeManager.ts │ ├── deployHub.ts │ ├── deployManagementFee.ts │ ├── deployParticipation.ts │ ├── deployPerformanceFee.ts │ ├── deployRegistry.ts │ ├── deployShares.ts │ ├── deploySpoke.ts │ ├── deployTrading.ts │ ├── deployVault.ts │ ├── deployVersion.ts │ ├── deployWeth.ts │ └── randomAddress.ts │ ├── toBigNumber.ts │ ├── toDate.ts │ ├── tradingSignatureName.ts │ ├── zeroAddress.ts │ └── zeroBigNumber.ts ├── tests ├── jest.config.js ├── jest.setup.js ├── jest.teardown.js └── jest.timeout.js ├── tsconfig.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | 3 | !src 4 | !scripts 5 | !package.json 6 | !yarn.lock 7 | !tsconfig.json 8 | !.protocol -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_size = 2 6 | end_of_line = lf 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitbook.yaml: -------------------------------------------------------------------------------- 1 | root: ./docs 2 | 3 | structure: 4 | readme: README.md 5 | summary: SUMMARY.md 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node dependencies. 2 | node_modules/ 3 | 4 | # Ignore all generated files. 5 | package-lock.json 6 | coverage/ 7 | dist/ 8 | typedocs/ 9 | 10 | # The abis are pulled from the protocol release using `yarn codegen`. 11 | src/abis/ 12 | 13 | # Ignore all dot files. 14 | .* 15 | 16 | # Ignore all log files. 17 | *.log 18 | 19 | # Allowed dot files 20 | !.gitignore 21 | !.prettierrc 22 | !.prettierignore 23 | !.dockerignore 24 | !.travis.yml 25 | !.editorconfig 26 | !.protocol 27 | 28 | # ABI tarball. 29 | melon-protocol-*.tar.gz 30 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": true, 4 | "trailingComma": "all", 5 | "singleQuote": true, 6 | "bracketSpacing": true 7 | } 8 | -------------------------------------------------------------------------------- /.protocol: -------------------------------------------------------------------------------- 1 | v1.1.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | dist: bionic 3 | branches: 4 | only: 5 | - master 6 | 7 | node_js: 8 | - '8' 9 | - '10' 10 | 11 | before_install: 12 | - curl -o- -L https://yarnpkg.com/install.sh | bash 13 | - export PATH="$HOME/.yarn/bin:$PATH" 14 | 15 | install: yarn --frozen-lockfile 16 | 17 | script: 18 | - node --version 19 | - yarn --version 20 | - yarn codegen 21 | - yarn typecheck 22 | - yarn build 23 | - yarn test 24 | - yarn lint 25 | 26 | notifications: 27 | email: 28 | on_failure: change 29 | 30 | cache: 31 | yarn: true 32 | directories: 33 | - node_modules 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Avantgarde Finance. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MelonJS 2 | 3 | > A convenient JavaScript interface to the Melon protocol Ethereum smart contracts. 4 | 5 | [![Build status](https://img.shields.io/travis/melonproject/melonjs)](https://travis-ci.org/melonproject/melonjs) 6 | [![Package version](https://img.shields.io/npm/v/@melonproject/melonjs)](https://www.npmjs.com/package/@melonproject/melonjs) 7 | ![License](https://img.shields.io/npm/l/@melonproject/melonjs) 8 | 9 | ## Installation 10 | 11 | This library is published as an [npm package][npm]. You can install it through npm or yarn. 12 | 13 | ```bash 14 | # Using yarn 15 | yarn add @melonproject/melonjs 16 | 17 | # Using npm 18 | npm install @melonproject/melonjs 19 | ``` 20 | 21 | ## Documentation 22 | 23 | - Extensive documentation (including examples): https://melonjs.melonprotocol.com 24 | - API: https://melonproject.github.io/melonjs/ 25 | - Melon Protocol: https://melonprotocol.com 26 | 27 | ## Development 28 | 29 | Before you can start developing, you'll need to have [Node.js][node] and [Yarn][yarn] installed. 30 | 31 | Installing only takes two commands and you're ready to roll: 32 | 33 | ```bash 34 | # Install all library dependencies. 35 | yarn install 36 | 37 | # Generate the abi and bytecode files for the smart contracts. 38 | yarn codegen 39 | ``` 40 | 41 | You are now ready to start development. Documentation is available [here](https://melonjs.melonprotocol.com/). There are also several helpful scripts in package.json for testing, test coverage, building and watch mode. 42 | 43 | ## Testing 44 | 45 | The tests contained in this repository use an in-memory ganache test chain. 46 | 47 | In order to execute the tests, simply run: 48 | 49 | ```bash 50 | yarn test 51 | ``` 52 | 53 | ## Contributing 54 | 55 | Third party contributions to this project are welcome and encouraged. If you want to contribute, please open an issue before submtting a pull requests so we can discuss the proposed changes and/or additions. 56 | 57 | Please note that all repositories hosted under this organization follow our [Code of Conduct][coc], make sure to review and follow it. 58 | 59 | [yarn]: https://yarnpkg.com 60 | [node]: https://nodejs.org 61 | [npm]: https://www.npmjs.com/package/@melonproject/melonjs 62 | [coc]: https://github.com/melonproject/melonjs/blob/master/CODE_OF_CONDUCT.md 63 | -------------------------------------------------------------------------------- /docs/.gitbook/assets/example-fund-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "FundName": "THE Fund", 3 | "QuoteToken": "WETH", 4 | "AllowedTokens": ["WETH", "MLN"], 5 | "ManagementFee": "10", 6 | "PerformanceFee": "10", 7 | "Exchanges": [ 8 | "OasisDex", 9 | "Uniswap" 10 | ] 11 | } -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | **MelonJS** is a convenient JavaScript interface for interacting with the Melon Protocol smart contracts on the Ethereum blockchain. It allows users to call functions on the contracts at every point of a fund's life cycle. We envision this functionality enabling a wide variety of apps, from fund creation and investor relations management to trading bots and automatic portfolio rebalancing. 4 | 5 | ## Installation and Development 6 | 7 | This library is published as an [npm package](https://www.npmjs.com/package/@melonproject/melonjs). You can install it through npm or yarn. 8 | 9 | ```bash 10 | # Using yarn 11 | yarn add @melonproject/melonjs 12 | 13 | # Using npm 14 | npm install @melonproject/melonjs 15 | ``` 16 | 17 | ## Links 18 | 19 | * [API documentation](https://melonproject.github.io/melonjs/): technical documentation of the MelonJS API. 20 | * [Melon Protocol](https://melonprotocol.com): the main Melon protocol website. 21 | 22 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [Introduction](README.md) 4 | 5 | ## Building Blocks 6 | 7 | * [The Basics](building-blocks/the-basics.md) 8 | * [Creating the Environment](building-blocks/environment/README.md) 9 | * [Mainnet](building-blocks/environment/mainnet.md) 10 | * [Rinkeby](building-blocks/environment/rinkeby.md) 11 | * [Kovan](building-blocks/environment/kovan.md) 12 | * [Interacting with Contracts](building-blocks/contract.md) 13 | * [Useful Patterns](building-blocks/useful-patterns.md) 14 | 15 | ## Examples 16 | 17 | * [Setting Up a Melon Fund](examples/fund-setup.md) 18 | * [Querying Fund Data](examples/querying-fund-data.md) 19 | * [Investing in a Fund](examples/invest.md) 20 | * [Redeeming Shares From a Fund](examples/redeem.md) 21 | * [Getting Investment Data](examples/investmentstatus.md) 22 | * [Fetching Prices](examples/fetching-prices.md) 23 | * [Managing Fees](examples/fees.md) 24 | * [Querying Fund Metrics](examples/querying-fund-metrics.md) 25 | * [Deploying Policies](examples/deploying-policies/README.md) 26 | * [User Whitelist](examples/deploying-policies/user-whitelist.md) 27 | * [PriceTolerance](examples/deploying-policies/pricetolerance.md) 28 | * [Max Concentration](examples/deploying-policies/max-concentration.md) 29 | * [Asset Blacklist](examples/deploying-policies/asset-blacklist.md) 30 | * [Asset Whitelist](examples/deploying-policies/asset-whitelist.md) 31 | * [Max Positions](examples/deploying-policies/max-positions.md) 32 | * [Trading](examples/trading/README.md) 33 | * [0x](examples/trading/0x.md) 34 | * [OasisDex](examples/trading/oasisdex.md) 35 | * [Uniswap](examples/trading/uniswap.md) 36 | * [Kyber Network](examples/trading/kyber.md) 37 | 38 | -------------------------------------------------------------------------------- /docs/building-blocks/environment/README.md: -------------------------------------------------------------------------------- 1 | # Creating the Environment 2 | 3 | The `Environment` class provides the context for all the functions you'll call. Its constructor function requires the network on which you're working \(this will be a testnet of some sort or the Ethereum mainnet if you're cooking with gas\) and a deployment `config` file that specifies the addresses of the current Melon Protocol contracts, the ERC-20 tokens that are tradeable on Melon, and the exchanges upon which they're traded. There are example deployment JSON files at the end of this Building Blocks section. 4 | 5 | ```javascript 6 | const { Eth } = require('web3-eth'); 7 | const { HttpProvider } = require('web3-providers'); 8 | const { DeployedEnvironment } = require('@melonproject/melonjs'); 9 | 10 | // Instantiate the environment where you'll create your fund 11 | const eth = new Eth(new HttpProvider('https://mainnet.infura.io/v3/9136e09ace01493b86fed528cb6a87a5', { 12 | confirmTransactionBlocks: 1, 13 | }})); 14 | const deployment = fs.readFileSync('./deployment.json'); 15 | 16 | // pass eth, the networkID, and the deployment to the DeployedEnvironment constructor 17 | // for networkID: Mainnet 1, rinkeby 4, kovan 42, local 4447 18 | const environment = new DeployedEnvironment(eth, 1, deployment); 19 | ``` 20 | 21 | -------------------------------------------------------------------------------- /docs/building-blocks/fund-configuration-json.md: -------------------------------------------------------------------------------- 1 | # Fund Configuration JSON 2 | 3 | This config JSON contains the information necessary to instantiate a Melon Fund. Our fund set up example references this JSON, but it should be noted that any way you provide the setup functions with these data points is fine. 4 | 5 | ```javascript 6 | { 7 | "FundName": "THE Fund", 8 | "QuoteToken": "WETH", // the fund's NAV, GAV, etc will be denominated in WETH 9 | "AllowedTokens": ["WETH", "MLN"], // an array of tokens investors can use to buy shares of the fund 10 | "ManagementFee": "10", // the fee, in percent, the manager charges to run the fund 11 | "PerformanceFee": "10", // the fee, in percent, the manager deducts from any profits 12 | "Exchanges": [ 13 | "OasisDex", 14 | "Uniswap", 15 | "KyberNetwork", 16 | "ZeroExV2", 17 | "ZeroExV3", 18 | "MelonEngine" 19 | ] // an array of exchanges your fund will use 20 | } 21 | ``` 22 | 23 | -------------------------------------------------------------------------------- /docs/building-blocks/fund-setup.md: -------------------------------------------------------------------------------- 1 | # Setting Up a Melon Fund 2 | 3 | Given an `Environment` and a config file, you can spin up your own Melon fund. The first step is to define a few variables that your fund will depend on. Some of these will come from the `Environment` you've defined already. I've defined others by pointing at `config.json` \(there's an example in the /examples folder in this directory\) . 4 | 5 | ```javascript 6 | const { Version } = require('@melonproject/melonjs') 7 | 8 | const managerAddress = string // the fund manager's address. 9 | 10 | // instantiate the current version of the Melon Protocol 11 | const version = new Version(environmnet, config, managerAddress, gasPrice) 12 | 13 | const denominationAddress = environment.deployment.tokens.addr[config.QuoteToken] 14 | const defaultAssets = config.AllowedTokens.map(t => environment.deployment.tokens.addr[t]) // an array of token addresses 15 | const managementFeeRate = new BigNumber(config.ManagementFee).times('1000000000000000000'); // the management fee 16 | const performanceFeeRate = new BigNumber(config.PerformanceFee).times('1000000000000000000'); // the performance fee 17 | 18 | let exchanges = string[]; // an array of decentralized exchange addresses 19 | let adapters = string[]; // an array of those exchanges' corresponding adapter contract addresses 20 | 21 | ``` 22 | 23 | Next, you'll pass those variables to version's `beginSetup()` method to start the process. 24 | 25 | ```javascript 26 | // pass your newly-defined variables to the `beginSetup` function to create the transaction and you're on your way. 27 | 28 | const transaction = version.beginSetup(sender, { 29 | name: config.FundName, 30 | fees: [environment.deployment.melon.addr.ManagementFee, environment.deployment.melon.addr.PerformanceFee], 31 | feeRates: [managementFeeRate, performanceFeeRate], 32 | feePeriods: [new BigNumber(0), new BigNumber(90 * 60 * 60 * 24)], 33 | exchanges: exchanges, 34 | adapters: adapters, 35 | denominationAsset: denominationAssetAddress, 36 | defaultAssets: defaultAssets, 37 | }); 38 | 39 | 40 | const receipt = ... // pass the transaction through the flow described above 41 | 42 | ``` 43 | 44 | At this point, you'll need to call the `createSpokeContract` methods individually as such: 45 | 46 | ```javascript 47 | const accountingTx = version.createAccounting(sender); 48 | const accountingReceipt = ... // pass the transaction through the flow above to generate the receipt 49 | 50 | console.log(accountingReceipt); 51 | ``` 52 | 53 | These contract creation methods need to be called in the following order: 54 | 55 | * createAccounting 56 | * createFeeManager 57 | * createParticipation 58 | * createPolicyManager 59 | * createShares 60 | * createTrading 61 | 62 | Once the spokes are all instantiated, call `Version.completeSetup(sender)` and pass it through the normal transaction flow to tie everything together. 63 | 64 | -------------------------------------------------------------------------------- /docs/building-blocks/the-basics.md: -------------------------------------------------------------------------------- 1 | # The Basics 2 | 3 | If you're unfamiliar with the Melon Protocol, there are many resources [here](https://melonprotocol.com/) that can get you started. 4 | 5 | This library is based on[ web3.js 2x](https://github.com/ethereum/web3.js/tree/2.x) \(link to repo\). Those imports are noted at the top of each example code block. We also use [BigNumber.js](https://github.com/MikeMcl/bignumber.js/) to describe tokens numerically. In all cases where we use those libraries, the imports have been noted. 6 | 7 | Finally, it's worth defining some primitives that will be used frequently when interacting with both the Ethereum blockchain in general and the Melon Protocol in particular. Understanding both`Environment` and `Contract` is vital to make use of MelonJS; they're both defined and illustrated in the following sections. 8 | 9 | -------------------------------------------------------------------------------- /docs/building-blocks/transaction.md: -------------------------------------------------------------------------------- 1 | # Transaction 2 | 3 | A `Transaction` is how a deployed contract interacts with the blockchain, and can be thought of as having four steps from start to finish. 4 | 5 | **Instantiation** - A contract must create a new transaction of a given type. 6 | 7 | **Validation** - this step is optional, though it provides named error messages which can be helpful for debugging or to show on any sort of front end application. 8 | 9 | **Preparation** - `Transaction.prepare()` is an asynchronous function that returns an object denoting the fees a transaction is projected to require shaped like this: 10 | 11 | ```text 12 | { 13 | gas: number, 14 | gasPrice?: number, 15 | amgu?: number, 16 | incentive?: number, 17 | value?: number, 18 | from?: string, 19 | } 20 | ``` 21 | 22 | If no arguments are passed to `Transaction.prepare()`, it will check the [ethgasstation.info](https://ethgasstation.info/) for estimated network gas prices, and the Melon Protocol for any applicable AMGU or incentive fees. However, we recommend passing 23 | 24 | ```text 25 | { gasPrice: number } 26 | ``` 27 | 28 | to `Transaction.prepare()` with a number you believe will guarantee transaction success. 29 | 30 | **Completion** - Pass the resolution of `Transaction.prepare()` to `Transaction.send()`, which returns a Web3 `PromiEvent`. Check [their docs](https://web3js.readthedocs.io/en/v1.2.6/callbacks-promises-events.html) for details, but these are promises with multiple stages of asyncronicity, each of which can be listened for to trigger UI interactions. 31 | 32 | These steps can be chained together into something like this: 33 | 34 | ```javascript 35 | const trading = new Trading(environment, address); 36 | const transaction = trading.addExchange(fromAddress, exchangeAddress, adapterAddress); 37 | 38 | function executeTransaction(transaction){ 39 | await new Promise( (resolve, reject) => { 40 | transaction.validate() 41 | .then(() => transaction.prepare({ gasPrice: GAS_PRICE })) // of your choosing, or omit this 42 | .then((options) => { 43 | const tx = transaction.send(options); 44 | 45 | tx.once('transactionHash', hash => console.log(`Pending: ${hash}`)); 46 | tx.once('receipt', receipt => resolve(receipt)); 47 | tx.once('error', error => reject(error)); 48 | }) 49 | .catch(error) => reject(error)); 50 | }); 51 | } 52 | 53 | 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /docs/building-blocks/useful-patterns.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Helper functions to abstract away some complexity 3 | --- 4 | 5 | # Useful Patterns 6 | 7 | ### Transactions 8 | 9 | The steps to validate, prepare, and execute a contract are used quite frequently. In the code examples throughout this documentation we explicitly call each function to. However, in the real world, we've found it helpful to abstract those steps into a single function, as such: 10 | 11 | ```javascript 12 | function executeTransaction(transaction, options) { 13 | return new Promise(async (resolve, reject) => { 14 | try { 15 | await transaction.validate(); 16 | 17 | const opts = await transaction.prepare(options); 18 | const tx = transaction.send(opts); 19 | tx.once('transactionHash', hash => console.log(`Pending: ${hash}`)); 20 | tx.once('receipt', receipt => resolve(receipt)); 21 | tx.once('error', error => reject(error)); 22 | } catch (e) { 23 | reject(e); 24 | } 25 | }); 26 | } 27 | ``` 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/deployment-files/untitled.md: -------------------------------------------------------------------------------- 1 | # Untitled 2 | 3 | -------------------------------------------------------------------------------- /docs/examples/deploying-policies/README.md: -------------------------------------------------------------------------------- 1 | # Deploying Policies 2 | 3 | -------------------------------------------------------------------------------- /docs/examples/deploying-policies/max-concentration.md: -------------------------------------------------------------------------------- 1 | # Max Concentration 2 | 3 | A `MaxConcentration` policy restricts a fund from trading if the trade would increase the size of a fund's position in one asset over a given threshold as a percentage of the fund's portfolio. 4 | 5 | {% hint style="info" %} 6 | This example requires an [environment](../../building-blocks/environment/) instance as described [here](../../building-blocks/environment/). 7 | {% endhint %} 8 | 9 | {% hint style="info" %} 10 | One thing to note: to deploy specific policy contracts, you must pass the corresponding byte code to the that contract's deployment method. 11 | {% endhint %} 12 | 13 | ```javascript 14 | import { Hub, MaxConcentration, PolicyManager } from '@melonproject/melonjs'; 15 | import { MaxConcentrationBytecode } from '@melonproject/melonjs/abis/MaxConcentration.bin'; 16 | 17 | // your hub address 18 | const hubAddress = '0x05263237f43190ce0e93b48afb25dd60a03ad3c5'; 19 | 20 | // the address of the fund's manager 21 | const fundManager = '0x0b64bf0fae1b9ffa80cd880f5b82d467ee34c28e'; 22 | 23 | // declare an instance of the fund's hub to access the spoke contract addresses 24 | const hub = new Hub(environment, hubAddress); 25 | 26 | // the address of the fund's PolicyManger contract 27 | const policyManagerAddress = hub.getRoutes().policyManager; 28 | 29 | // the number, in percent, you'd like to set for MaxConcentration 30 | const concentration = 10; 31 | 32 | // specify the gas price (refer to http://ethgasstation.info/) 33 | const gasPrice = 30000000000; 34 | 35 | // declare the instance of the fund's PolicyManager contract 36 | const manager = new PolicyManager(environment, policyManagerAddress); 37 | 38 | // execute the deployment transaction 39 | const deploymentTx = MaxConcentration.deploy( 40 | environment, 41 | MaxConcentrationByteCode, 42 | fundManager, 43 | concentration 44 | ); 45 | const deploymentOpts = await deploymentTx.prepare({gasPrice}); 46 | const deploymentReceipt = await deploymentTx.send(deploymentOpts); 47 | 48 | // assign the proper address and signature to pass to the registration transaction 49 | const maxConcSig = deploymentReceipt.signature; 50 | const maxConcAddr = deploymentReceipt.address; 51 | 52 | // execute the registration transaction 53 | const registerTx = manager.registerPolicy(fundManager, maxConcSig, maxConcAddr); 54 | const registerOpts = await registerTx.prepare({ gasPrice }); 55 | const registerReceipt = await registerTx.send(registerOpts); 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /docs/examples/deploying-policies/max-positions.md: -------------------------------------------------------------------------------- 1 | # Max Positions 2 | 3 | The `MaxPositions` policy's purpose is fairly self-explanatory: to limit the number of token positions that a fund can hold at any given time. 4 | 5 | {% hint style="info" %} 6 | This example requires an [environment](../../building-blocks/environment/) instance as described [here](../../building-blocks/environment/). 7 | {% endhint %} 8 | 9 | {% hint style="info" %} 10 | One thing to note: to deploy specific policy contracts, you must pass the corresponding byte code to the that contract's deployment method. 11 | {% endhint %} 12 | 13 | ```javascript 14 | import { Hub, MaxPositions, PolicyManager } from '@melonproject/melonjs'; 15 | import { MaxPositionsBytecode } from '@melonproject/melonjs/abis/MaxPositions.bin'; 16 | 17 | // your hub address 18 | const hubAddress = '0x05263237f43190ce0e93b48afb25dd60a03ad3c5'; 19 | 20 | // the address of the fund's manager 21 | const fundManager = '0x0b64bf0fae1b9ffa80cd880f5b82d467ee34c28e'; 22 | 23 | // declare an instance of the fund's hub to access the spoke contract addresses 24 | const hub = new Hub(environment, hubAddress); 25 | 26 | // the address of the fund's PolicyManger contract 27 | const policyManagerAddress = hub.getRoutes().policyManager; 28 | 29 | // specify the max number of positions as a Big Number 30 | const numberOfPositions = new BigNumber(5); 31 | 32 | // specify the gas price (refer to http://ethgasstation.info/) 33 | const gasPrice = 30000000000; 34 | 35 | // declare the instance of the fund's PolicyManager contract 36 | const manager = new PolicyManager(environment, policyManagerAddress); 37 | 38 | // execute the deployment transaction 39 | const deploymentTx = MaxPositions.deploy( 40 | environment, 41 | MaxPositionsByteCode, 42 | managerAddress, 43 | numberOfPositions 44 | ); 45 | const deploymentOpts = await deplomentTx.prepare({gasPrice}); 46 | const deploymentReceipt = await deploymentTx.send(deploymentOpts); 47 | 48 | // assign the proper address and signature to pass to the registration transaction 49 | const maxPosSig = deploymentReceipt.signature; 50 | const maxPosAddr = deploymentReceipt.address; 51 | 52 | // execute the registration transaction 53 | const registerTx = manager.registerPolicy(fundAddress, maxPosSig, maxPosAddr); 54 | const registerOpts = await registerTx.prepare({ gasPrice }); 55 | const registerReceipt = await transaction.send(registerOpts); 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /docs/examples/deploying-policies/pricetolerance.md: -------------------------------------------------------------------------------- 1 | # PriceTolerance 2 | 3 | A `PriceTolerance` policy restricts a fund from trading when the price of an asset has varied too much from the last protocol-wide price update. 4 | 5 | {% hint style="info" %} 6 | This example requires an [environment](../../building-blocks/environment/) instance as described [here](../../building-blocks/environment/). 7 | {% endhint %} 8 | 9 | {% hint style="info" %} 10 | One thing to note: to deploy specific policy contracts, you must pass the corresponding byte code to the that contract's deployment method. 11 | {% endhint %} 12 | 13 | ```javascript 14 | import { Hub, PriceTolerance, PolicyManager } from '@melonproject/melonjs'; 15 | import { PriceToleranceBytecode } from '@melonproject/melonjs/abis/MaxPositions.bin'; 16 | 17 | // your hub address 18 | const hubAddress = '0x05263237f43190ce0e93b48afb25dd60a03ad3c5'; 19 | 20 | // the address of the fund's manager 21 | const fundManager = '0x0b64bf0fae1b9ffa80cd880f5b82d467ee34c28e'; 22 | 23 | // declare an instance of the fund's hub to access the spoke contract addresses 24 | const hub = new Hub(environment, hubAddress); 25 | 26 | // the address of the fund's PolicyManger contract 27 | const policyManagerAddress = hub.getRoutes().policyManager; 28 | 29 | // the number, in percent, you'd like to set for priceTolerance 30 | const tolerance = 10; 31 | 32 | // specify the gas price (refer to http://ethgasstation.info/). 33 | const gasPrice = 30000000000; 34 | 35 | // declare the instance of the fund's PolicyManager contract 36 | const manager = new PolicyManager(environment, policyManagerAddress); 37 | 38 | // execute the deployment transaction 39 | const deploymentTx = PriceTolerance.deploy( 40 | environment, 41 | PriceToleranceByteCode, 42 | fundManager, 43 | tolerance 44 | ); 45 | const deploymentOpts = await deploymentTx.prepare({ gasPrice }); 46 | const deploymentReceipt = await deploymentTransaction.send(deploymentOpts); 47 | 48 | // assign the proper address and signature to pass to the registration transaction 49 | const priceToleranceSignature = receipt.signature; 50 | const priceToleranceAddress = receipt.address; 51 | 52 | // execute the registration transaction 53 | const registerTx = manager.registerPolicy(fundManager, priceToleranceSignature, priceToleranceAddress) 54 | const registerOpts = await registerTx.send({ gasPrice }); 55 | const registerReceipt = await registerTx.send(registerOpts); 56 | ``` 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /docs/examples/fees.md: -------------------------------------------------------------------------------- 1 | # Managing Fees 2 | 3 | A fund's `FeeManager` and `Accounting` contract work in tandem to manage and distribute management and performance fees. 4 | 5 | In general, management fees are continuously available for distribution. In other words, as soon as fees accrue, the can be claimed by the manager. However, performance fees are only available at intervals which are set at the fund's creation. Below, we'll walk through examples of both transactions. 6 | 7 | {% hint style="info" %} 8 | This example requires an [environment](../building-blocks/environment/) instance as described [here](../building-blocks/environment/). 9 | {% endhint %} 10 | 11 | ```javascript 12 | import { Accounting, FeeManager, Hub } from '@melonproject/melonjs'; 13 | 14 | // your hub address 15 | const hubAddress = '0x05263237f43190ce0e93b48afb25dd60a03ad3c5'; 16 | 17 | // declare an instance of the fund's hub to access the spoke contract addresses 18 | const hub = new Hub(environment, hubAddress); 19 | 20 | // the address of the fund's FeeManager contract 21 | const feeManagerAddress = hub.getRoutes().feeManager; 22 | 23 | // the address of the fund's Accounting contract 24 | const accountingAddress = hub.getRoutes().accounting; 25 | 26 | // the address of the fund manager 27 | const fundManagerAddress = '0x33b2f147a2526ac8abccccf38b71c0467673bffd'; 28 | 29 | // specify the gas price (refer to http://ethgasstation.info/) 30 | const gasPrice = 30000000000; 31 | 32 | // declare instances of the fund's FeeManager and Accounting contracts 33 | const feeManager = new FeeManager(environment, feeManagerAddress); 34 | const accounting = new Accounting(environmnet, accountingAddress); 35 | ``` 36 | 37 | Once the contract instances are declared, you can go one of two ways - either reward **just** the management fees with the `FeeManager` contract instance: 38 | 39 | ```javascript 40 | const transaction = feeManager.rewardManagementFees(fundManagerAddress); 41 | const opts = transaction.prepare({ gasPrice }); 42 | const receipt = transaction.send(opts); 43 | ``` 44 | 45 | Or reward both the management and performance fees with the `Accounting` contract instance: 46 | 47 | ```javascript 48 | const transaction = accounting.triggerRewardAllFees(fundManagerAddress); 49 | const opts = await transaction.prepare({ gasPrice }); 50 | const receipt = await transaction.send(opts); 51 | ``` 52 | 53 | -------------------------------------------------------------------------------- /docs/examples/fetching-prices.md: -------------------------------------------------------------------------------- 1 | # Fetching Prices 2 | 3 | For various purposes, including calculating fund return metrics and share prices, the Melon Protocol fetches on-chain prices for all available assets once a day and saves them in state. These prices are accessible via MelonJS using query methods exposed by the `KyberPriceSource` contract on the mainnet and the `TestingPriceFeed` contract on the rinkeby or kovan testnets. 4 | 5 | {% hint style="info" %} 6 | This example requires an [environment](../building-blocks/environment/) instance as described [here](../building-blocks/environment/). 7 | {% endhint %} 8 | 9 | ```javascript 10 | import { KyberPriceSource } from '@melonproject/melonjs'; 11 | 12 | // the priceSourceAddress 13 | const priceSourceAddress = environment.melon.addr.KyberPriceFeed; 14 | 15 | // declare an instance of the KyberPriceSource contract 16 | const priceSource = new KyberPriceSource(environment, priceSourceAddress); 17 | 18 | // There are a many methods exposed on this contract instance. 19 | // We'll lay them out below 20 | 21 | // the MLN token object 22 | const mln = environment.getToken('MLN'); 23 | 24 | // the BAT token object 25 | const bat = environment.getToken('BAT'); 26 | 27 | // the WETH address 28 | const quoteAssetAddress = await priceSource.getQuoteAsset(); 29 | 30 | // returns the unix timestamp of the last pricefeed update 31 | const lastUpdate = await priceSource.getLastUpdate(); 32 | 33 | // returns a boolean indicating whether the last price feed update was successful 34 | const valid = await priceSource.hasValidPrice(mln.address); 35 | 36 | // makes the same call as above on an array of tokens 37 | const multiValid = await priceSource.hasValidPrices([mln.address, bat.address]); 38 | 39 | // returns {price: bigNumber, timestamp: date} 40 | const mlnPrice = await priceSource.getPrice(mln.address); 41 | 42 | // returns an array of price objects like the method directly above 43 | const variousPrices = await priceSource.getPrice([mln.address, bat.address]); 44 | 45 | // returns a boolean if this asset pair exists in the price feed 46 | const weirdPriceExists = await priceSource.existsPriceOnAssetPair(mln.address, bat.address); 47 | 48 | // returns the { price: BigNumber, decimals: number } relative to the pair passed to the method 49 | const mlnBatRate = await priceSource.getReferencePriceInfo( 50 | mln.address, 51 | bat.address 52 | ); 53 | 54 | // returns the number of BAT you'd get for 10 MLN as a BigNumber 55 | const mlnInBat = await priceSource.convertQuantity( 56 | new BigNumber(10).multipliedBy('1e8'), 57 | mln.address, 58 | bat.address 59 | ); 60 | ``` 61 | 62 | -------------------------------------------------------------------------------- /docs/examples/investmentstatus.md: -------------------------------------------------------------------------------- 1 | # Getting Investment Data 2 | 3 | Various query methods are exposed on the `Participation` contract that allow one to investigate the fund's current and historical investors at the transaction level. 4 | 5 | {% hint style="info" %} 6 | This example requires an [environment](../building-blocks/environment/) instance as described [here](../building-blocks/environment/). 7 | {% endhint %} 8 | 9 | ```javascript 10 | import { Hub, Participation } from '@melonproject/melonjs'): 11 | 12 | // your hub address 13 | const hubAddress = '0x05263237f43190ce0e93b48afb25dd60a03ad3c5'; 14 | 15 | // declare an instance of the fund's hub to access the spoke contract addresses 16 | const hub = new Hub(environment, hubAddress); 17 | 18 | // the address of the fund's participation contract 19 | const participationAddress = hub.getRoutes().participation; 20 | 21 | // the Melon token's address 22 | const melon = enviroment.getToken('MLN'); 23 | 24 | // not actually my ETH address 25 | const myETHAddress = '0xfbf4e3511bbb80f335988e7482efe2f6e1ef387e'; 26 | 27 | // declare an instance of the fund's Participation contract 28 | const participation = new Participation(environment, participationAddress); 29 | 30 | // returns an array of addresses of investors in the fund 31 | const investors = await participation.getHistoricalInvestors(); 32 | 33 | // returns a boolean that describes whether the fund allows investment with a given token 34 | const melonAllowed = await participation.canInvestWithAsset(melon.address); 35 | 36 | // returns a boolean describing whether a given address has already invested in the fund 37 | const alreadyLong = await participation.hasInvested(myETHAddress); 38 | 39 | // returns a boolean describing whether a given address has requested an investment in the fund 40 | const hasRequested = await participation.hasRequest(myETHAddress); 41 | 42 | // returns a boolean describing whether a given address has executed an investment request that expired 43 | const hasExpired = await participation.hasExpiredRequest(myETHAddress); 44 | 45 | // returns a boolean describing whether a given address has an outstanding investment request that has not expired 46 | const hasValidRequest = await participation.hasValidRequest(myETHAddress); 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /docs/examples/redeem.md: -------------------------------------------------------------------------------- 1 | # Redeeming Shares From a Fund 2 | 3 | Redeeming one's shares from a Melon fund is fairly simple. The first step is to create an instance of the `Participation` contract. 4 | 5 | {% hint style="info" %} 6 | This example requires an [environment](../building-blocks/environment/) instance as described [here](../building-blocks/environment/). 7 | {% endhint %} 8 | 9 | ```javascript 10 | import { Hub, Participation } from '@melonproject/melonjs'; 11 | 12 | // your hub address 13 | const hubAddress = '0x05263237f43190ce0e93b48afb25dd60a03ad3c5'; 14 | 15 | // declare an instance of the fund's hub to access the spoke contract addresses 16 | const hub = new Hub(environment, hubAddress); 17 | 18 | // the address of the fund's participation contract 19 | const participationAddress = hub.getRoutes().participation; 20 | 21 | // the address of the user making the redemption request 22 | const userAddress = '0xf039e6893ffa43196fd9d1d7038b74bf39dda4a5'; 23 | 24 | // specify the gas price (refer to http://ethgasstation.info/). 25 | const gasPrice = 30000000000; 26 | 27 | // declare the instance of the fund's Participation contract 28 | const participation = new Participation(environment, participationAddress); 29 | ``` 30 | 31 | From there, you can either redeem all shares: 32 | 33 | ```javascript 34 | const transaction = participation.redeem(userAddress); 35 | const opts = transaction.prepare({gasPrice}); 36 | const receipt = transaction.send(opts); 37 | ``` 38 | 39 | Or redeem a specific amount of shares to redeem: 40 | 41 | ```javascript 42 | // a bignumber representing the number of shares to redeem 43 | const shareQuantity = new BigNumber(329); 44 | const transaction = participation.redeemQuantity(userAddress, shareQuantity); 45 | const opts = transaction.prepare({gasPrice}); 46 | const receipt = transaction.send(opts); 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /docs/examples/trading/README.md: -------------------------------------------------------------------------------- 1 | # Trading 2 | 3 | -------------------------------------------------------------------------------- /docs/examples/trading/kyber.md: -------------------------------------------------------------------------------- 1 | # Kyber Network 2 | 3 | 4 | 5 | In the example below, we'll fetch a rate and then trade `WETH` for `MLN`on the [Kyber Network](https://kyber.network/). 6 | 7 | {% hint style="info" %} 8 | This example requires an [environment](../../building-blocks/environment/) instance as described [here](../../building-blocks/environment/). 9 | {% endhint %} 10 | 11 | ```javascript 12 | import { 13 | Hub, 14 | KyberNetworkProxy 15 | KyberTradingAdapter, 16 | Trading 17 | } from '@melonproject/melonjs'; 18 | 19 | // your hub address 20 | const hubAddress = '0x05263237f43190ce0e93b48afb25dd60a03ad3c5'; 21 | 22 | // the address of the fund's manager 23 | const fundManager = '0x0b64bf0fae1b9ffa80cd880f5b82d467ee34c28e'; 24 | 25 | // declare an instance of the fund's hub to access the spoke contract addresses 26 | const hub = new Hub(environment, hubAddress); 27 | 28 | // the address of the fund's trading contract 29 | const tradingAddress = hub.getRoutes().trading; 30 | 31 | // specify the gas price (refer to http://ethgasstation.info/). 32 | const gasPrice = 30000000000; 33 | 34 | // the address of WETH, 35 | const makerAddress = environment.getToken('WETH').address; 36 | 37 | // which we'll trade for MLN 38 | const takerAddress = environment.getToken('MLN').address; 39 | 40 | // amount of the makerToken that you'd like to swap for the takerToken with 18 decmials 41 | const makerQty = new BigNumber(100).multipliedBy('1e18'); 42 | 43 | // declare an instance of the KyberNetworkProxy contract 44 | const priceChecking = new KyberNetworkProxy( 45 | environment, 46 | environment.deployment.kyber.addr.KyberNetworkProxy 47 | ); 48 | 49 | // an object shaped like: { expectedRate: BigNumber, slippageRate: BigNumber } 50 | const rate = await priceChecking.getExpectedRate(makerToken, takerToken, makerQty); 51 | 52 | // We'll next submit an order by preparing a transaction and pushing it through the normal pattern 53 | 54 | // declare an instance of the fund's Trading contract 55 | const trading = new Trading(environment, tradingAddress); 56 | 57 | // the amount of the taker asset as implied by the rate returned above 58 | const takerQty = makerQty.multipliedBy(rate.expectedRate); 59 | 60 | // assemble an orderArgs object to pass to the takeOrderFunction 61 | const orderArgs = { 62 | makerAsset: makerAddress; // address of the makerToken 63 | takerAsset: takerAddress; // address of the takerToken 64 | makerQuantity: makerQty; // amount of the makerToken 65 | takerQuantity: takerQty; // amount of the takerToken 66 | }; 67 | 68 | // Create the adapter instance 69 | const adapter = await KyberTradingAdapter.create(environment, exchange, trading); 70 | 71 | // Create and execute the transaction 72 | const transaction = adapter.takeOrder(fundManager, orderArgs); 73 | const opts = await transaction.prepare({ gasPrice }); 74 | const receipt = await transaction.send(opts); 75 | ``` 76 | 77 | -------------------------------------------------------------------------------- /docs/examples/trading/oasisdex.md: -------------------------------------------------------------------------------- 1 | # OasisDex 2 | 3 | Oasis is a liquidity pool on the Ethereum blockchain. You can read more about it [here](https://developer.makerdao.com/oasis/). In the example below, we'll trade on an OasisDex order. 4 | 5 | {% hint style="info" %} 6 | This example requires an [environment](../../building-blocks/environment/) instance as described [here](../../building-blocks/environment/). 7 | {% endhint %} 8 | 9 | ```javascript 10 | import { 11 | ExchangeIdentifier, 12 | OasisDexExchange, 13 | OasisDexTradingAdapter, 14 | Trading 15 | } from '@melonproject/melonjs'; 16 | 17 | // your hub address 18 | const hubAddress = '0x05263237f43190ce0e93b48afb25dd60a03ad3c5'; 19 | 20 | // the address of the fund's manager 21 | const fundManager = '0x0b64bf0fae1b9ffa80cd880f5b82d467ee34c28e'; 22 | 23 | // declare an instance of the fund's hub to access the spoke contract addresses 24 | const hub = new Hub(environment, hubAddress); 25 | 26 | // the address of the fund's trading contract 27 | const tradingAddress = hub.getRoutes().trading; // declare an exchange object, shape noted in ~/buildingblocks/environment 28 | const exchange = environment.getExchange(ExchangeIdentifier.OasisDex); 29 | 30 | // the maker token object 31 | const maker = environment.getToken('WETH'); 32 | 33 | // the taker token object 34 | const taker = environment.getToken('MLN'); 35 | 36 | 37 | // using an OasisDex order retrieved from their API and shaped as such: 38 | const order = { 39 | id: new BigNumber(112), 40 | sellQuantity: new BigNumer(120), 41 | buyQuantity: new BigNumber(110) 42 | }; 43 | 44 | 45 | // declare an instance of the Trading contract 46 | const trading = new Trading(environment, tradingAddress); 47 | 48 | // create an instance of the OasisDexExchange contract 49 | const market = new OasisDexExchange(environment, exchange.exchange); 50 | 51 | 52 | // create an instance of the OasisDexTradingAdapter contract 53 | const adapter = await OasisDexTradingAdapter.create( 54 | environment, 55 | exchange.exchange, 56 | trading 57 | ); 58 | 59 | // use the exchange to get the specific offer 60 | const offer = await market.getOffer(order.id); 61 | 62 | // the amount of the takerAsset to buy with the makerAsset 63 | const quantity = new BigNumber(100); 64 | 65 | // specify the gas price (refer to http://ethgasstation.info/). 66 | const gasPrice = 30000000000; 67 | 68 | // create and execute the transaction 69 | const transaction = adapter.takeOrder( 70 | fundManager, 71 | order.id, 72 | offer, 73 | quantity 74 | ); 75 | const opts = await transaction.prepare({ gasPrice }); 76 | const receipt = await transaction.send(opts); 77 | ``` 78 | 79 | -------------------------------------------------------------------------------- /docs/the-accounting-and-feemanager-contracts/fees.md: -------------------------------------------------------------------------------- 1 | # Fees 2 | 3 | 4 | 5 | ### The Accounting and FeeManager Contracts 6 | 7 | The Accounting contract provides several methods to check the performance of a fund. It also works in tandem with FeeManager to manage and distribute management and performance fees. 8 | 9 | The fund's management fee is redeemable at any time, via the FeeManager contract. 10 | 11 | ```javascript 12 | const { FeeManager } = require('@melonproject/melonjs') 13 | 14 | const feeManagerAddress = string; // address of the fund's trading contract 15 | const fundAddress = string; // address of the fund manager 16 | 17 | const feeManager = new FeeManager(environment, feeManagerAddress); 18 | 19 | const transaction = feeManager.rewardManagementFees(fundAddress); 20 | 21 | const receipt = etc ... // the usual transaction confirmation flow detailed in the first section of this usage guide. 22 | ``` 23 | 24 | To reward both the accrued management and performance fees, call the Accounting contract. 25 | 26 | ```javascript 27 | const { Accounting } = require('@melonproject/melonjs') 28 | 29 | const accountingAddress = string; // address of the fund's Accounting contract 30 | const fundAddress = string; // address of the fund 31 | 32 | const accounting = new Account(environment, accountingAddress); 33 | 34 | const transaction = accounting.triggerRewardAllFees(fundAddress) 35 | 36 | const receipt = etc ... // the usual transaction confirmation flow detailed in the first section of this usage guide 37 | ``` 38 | 39 | You can also use the Accounting contract to query the fund's holdings. 40 | 41 | ```javascript 42 | const fundHoldings = accounting.getFundHoldings() 43 | // fundHoldings will be an array of objects shaped like this: 44 | // [{address: string, amount: BigNumber}...] 45 | ``` 46 | 47 | You'll have to do the work to put an asset's name to the address. 48 | 49 | The Accounting contract will also do some handy calculation work for you, providing a description of the Fund across various metrics. 50 | 51 | ```javascript 52 | const fundStats = accounting.getCalculationResults() 53 | 54 | // fundStats will be an object shaped like this: 55 | 56 | { 57 | sharePrice: BigNumber, 58 | gav: BigNumber 59 | nav: BigNumber 60 | feesInDenominationAsset: BigNumber 61 | feesInShares: BigNumber 62 | gavPerShareNetManagementFee: BigNumber 63 | } 64 | 65 | ``` 66 | 67 | -------------------------------------------------------------------------------- /docs/the-accounting-and-feemanager-contracts/querying-fund-metrics.md: -------------------------------------------------------------------------------- 1 | # Querying Fund Metrics 2 | 3 | You can also use the `Accounting` contract to query the fund's holdings. 4 | 5 | ```javascript 6 | const fundHoldings = accounting.getFundHoldings() 7 | // fundHoldings will be an array of objects shaped like this: 8 | // [{address: string, amount: BigNumber}...] 9 | ``` 10 | 11 | You'll have to do the work to put an asset's name to the address. 12 | 13 | The Accounting contract will also do some handy calculation work for you, providing a description of the Fund across various metrics. 14 | 15 | ```javascript 16 | const fundStats = accounting.getCalculationResults() 17 | 18 | // fundStats will be an object shaped like this: 19 | { 20 | sharePrice: BigNumber, 21 | gav: BigNumber 22 | nav: BigNumber 23 | feesInDenominationAsset: BigNumber 24 | feesInShares: BigNumber 25 | gavPerShareNetManagementFee: BigNumber 26 | } 27 | 28 | ``` 29 | 30 | -------------------------------------------------------------------------------- /docs/the-participation-contract/list-investors.md: -------------------------------------------------------------------------------- 1 | # List Investors in a Fund 2 | 3 | We can also use the `Participation` contract to check a fund's current investors. 4 | 5 | ```javascript 6 | const { Participation } = require('@melonproject/melonjs'); 7 | const participationAddress = string; // the address of the fund's participation contract 8 | const participation = new Participation(environment, participationAddress) 9 | const investors = await participation.getHistoricalInvestors() 10 | 11 | // investors will be an array of addresses of investors in the fund 12 | ``` 13 | 14 | -------------------------------------------------------------------------------- /docs/the-participation-contract/redeem.md: -------------------------------------------------------------------------------- 1 | # Redeeming From a Fund 2 | 3 | Redeeming one's shares from a Melon fund is fairly simple and requires only one step. 4 | 5 | ```javascript 6 | const { Participation } = require('@melonproject/melonjs'); 7 | 8 | const participationAddress = string; // the address of the fund's participation contract 9 | const userAddress = string; // the address of the user making the redemption request 10 | 11 | const participation = new Participation(environment, participationAddress) 12 | 13 | 14 | // the redemption transaction will either be .redeem(), hwich redeems all shares, or .redeemQuantity, which redeems a partial balance of the user's shares. Note that the shareQuantity must be a BigNumber with an appropriate number of decimal places. 15 | const transaction = participation.redeem(userAddress) || contract.redeemQuantity(userAddress, shareQuantity) 16 | 17 | const receipt = etc ... // the usual transaction confirmation flow detailed in the first section of this usage guide. 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /docs/the-policy-contract/overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Policies are rules to which the fund manager must adhere. They come in a few different flavors. Each specific policy has its own contract \(`AssetWhitelist`, `MaxConcentration`, etc...\). The arguments passed to these contracts' deployment methods vary based on what the policy is enforcing and are fairly intuitive. For instance, `AssetWhiteList`'s deployment method accepts an array of addresses, `MaxConcentration` accepts a BigNumber, and so on. A full discussion of policies and their enforcement dynamics can be found [here](https://docs.melonport.com/chapters/fund.html#policies). 4 | 5 | **One thing to note: to deploy specific policy contracts, you must pass the corresponding byte code to the that contract's deployment method.** Luckily, MelonJS provides that for you as well. 6 | 7 | Let's create a `MaxPositions` policy, whose purpose is fairly self-explanatory. 8 | 9 | ```javascript 10 | const { MaxPositions } = require('@melonproject/melonjs'); 11 | const { MaxPositionsBytecode } = require('@melonproject/melonjs/abis/MaxPositions.bin'); 12 | 13 | const fundAddress = string // the address of the fund 14 | const manager = new PolicyManager(environment, policyManagerAddress) 15 | 16 | const numberOfPositions = new BigNumber(5); 17 | const transaction = MaxPositions.deploy(environment, MaxPositionsByteCode, fundAddress, numberOfPositions); 18 | 19 | transaction.validate() 20 | .then(() => transaction.prepare({ gasPrice: GAS_PRICE })) // of your choosing, or omit this 21 | .then((options) => { 22 | const tx = transaction.send(options); 23 | 24 | tx.once('transactionHash', hash => console.log(`Pending: ${hash}`)); 25 | tx.once('receipt', receipt => resolve(receipt)); 26 | tx.once('error', error => reject(error)); 27 | }) 28 | .catch(error) => reject(error)); 29 | }); 30 | ``` 31 | 32 | Once you've deployed the Policy contract, you need to register it with your fund using the PolicyManager contract that you created when you set up the fund. 33 | 34 | ```javascript 35 | const {PolicyManager } = require('@melonproject/melonjs'); 36 | 37 | const maxPositionsAddress = receipt.contractAddress // the address of the contract you just deployed 38 | const maxPositionsSignature = string // the signature of the max positions policy 39 | const policyManagerAddress = string // the address of the fund's policy manager contract 40 | const fundAddress = string // the address of the fund 41 | 42 | 43 | const manager = new PolicyManager(environment, policyManagerAddress) 44 | const transaction = manager.registerPolicy(fundAddress, maxPositionsSignature, maxPositionsAddress 45 | ``` 46 | 47 | -------------------------------------------------------------------------------- /docs/the-trading-contract/executing-a-trade.md: -------------------------------------------------------------------------------- 1 | # Executing a Trade 2 | 3 | 4 | 5 | Once you've got that rate, you can move on to the fun part. 6 | 7 | ```javascript 8 | const { Trading, KyberTradingAdapter } = require('@melonproject/melonjs'); 9 | const tradingAddress = string; // address of the fund's trading contract 10 | const trading = new Trading(environment, tradingAddress); // tradingContract is the string address of the trading contract belonging to the fund in question. This will define a Trading class specific to the fund. 11 | 12 | const orderArgs = { 13 | makerAsset: Address; // address of the makerToken 14 | takerAsset: Address; // address of the takerToken 15 | makerQuantity: BigNumber; // amount of the makerToken with the appropriate number of decimal places ***Note that if you're calling these functions sequentially, makerQuantity shoudld be equal to the user -specified takerQuantity multiplied by the expectedRate that was fetched from the KyberNetworkProxy 16 | takerQuantity: BigNumber; // amount of the takerToken with the appropriate number of decimal places 17 | } 18 | 19 | const adapter = await KyberTradingAdapter.create(environment, exchange, trading); 20 | 21 | const tx = adapter.takeOrder(from, orderArgs) // The from argument is the wallet address that's initiating the transaction, and kyberTakeOrderArgs an object in the shape noted above. takeOrder() returns a Transaction, which you can pass through the flow we described in the Transaction primitive section above. 22 | 23 | const receipt = etc ... // the usual transaction confirmation flow detailed in the first section of this usage guide. 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /docs/the-trading-contract/untitled.md: -------------------------------------------------------------------------------- 1 | # Querying Prices 2 | 3 | The Melon Protocol is directly integrated with the smart contracts of various trading venues. MelonJS provides an avenue to interact with those exchanges to query prices and trade tokens. These two concerns are handled by separate classes. 4 | 5 | ```javascript 6 | const { KyberNetworkProxy } = require('@melonproject/melonjs'); 7 | const makerToken = string; // address of the token you own 8 | const takerToken = string; // address of the token you want to own 9 | const makerQty = BigNumber; // amount of the makerToken that you'd like to swap for the destToken with the appropriate number of decimal places 10 | const priceChecking = new KyberNetworkProxy(environment, environment.deployment.kyber.addr.KyberNetworkProxy); 11 | const expectedRate = await priceChecking.getExpectedRate(makerToken, takerToken, makerQty); 12 | ``` 13 | 14 | expectedRate is an object shaped like so: 15 | 16 | ```text 17 | { 18 | expectedRate: new BigNumber(expectedRate), 19 | slippageRate: new BigNumber(slippageRate), 20 | } 21 | ``` 22 | 23 | -------------------------------------------------------------------------------- /jekyll.config.yml: -------------------------------------------------------------------------------- 1 | title: '@melonproject/melonjs' 2 | description: A convenient JavaScript interface to the Melon protocol Ethereum smart contracts. 3 | 4 | include: 5 | - '*.html' # Needed to include files that start with underscore 6 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import sourcemaps from 'rollup-plugin-sourcemaps'; 2 | import node from 'rollup-plugin-node-resolve'; 3 | 4 | export default [ 5 | { 6 | input: 'dist/index.js', 7 | output: { 8 | file: 'dist/bundle.umd.js', 9 | format: 'umd', 10 | name: 'melonJs', 11 | sourcemap: true, 12 | exports: 'named', 13 | }, 14 | plugins: [node(), sourcemaps()], 15 | onwarn, 16 | }, 17 | { 18 | input: 'dist/index.js', 19 | output: { 20 | file: 'dist/bundle.esm.js', 21 | format: 'esm', 22 | sourcemap: true, 23 | }, 24 | plugins: [node(), sourcemaps()], 25 | onwarn, 26 | }, 27 | { 28 | input: 'dist/bundle.esm.js', 29 | output: { 30 | file: 'dist/bundle.cjs.js', 31 | format: 'cjs', 32 | sourcemap: true, 33 | }, 34 | onwarn, 35 | }, 36 | ]; 37 | 38 | export function onwarn(message) { 39 | const suppressed = ['UNRESOLVED_IMPORT', 'THIS_IS_UNDEFINED']; 40 | 41 | if (!suppressed.find(code => message.code === code)) { 42 | return console.warn(message.message); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Address.ts: -------------------------------------------------------------------------------- 1 | export type Address = string; 2 | -------------------------------------------------------------------------------- /src/contracts/dependencies/authorization/DSAuth.ts: -------------------------------------------------------------------------------- 1 | import { Environment } from '../../../Environment'; 2 | import { Address } from '../../../Address'; 3 | import { Contract } from '../../../Contract'; 4 | import { DSAuthAbi } from '../../../abis/DSAuth.abi'; 5 | 6 | export class DSAuth extends Contract { 7 | public static readonly abi = DSAuthAbi; 8 | 9 | public static deploy(environment: Environment, bytecode: string, from: Address) { 10 | return super.createDeployment(environment, bytecode, from); 11 | } 12 | 13 | /** 14 | * Gets the address of the owner 15 | * 16 | * @param block The block number to execute the call on. 17 | */ 18 | public getOwner(block?: number) { 19 | return this.makeCall
('owner', [undefined], block); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/contracts/dependencies/authorization/DSAuthority.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../../Address'; 2 | import { Contract } from '../../../Contract'; 3 | import { DSAuthorityAbi } from '../../../abis/DSAuthority.abi'; 4 | 5 | export class DSAuthority extends Contract { 6 | public static readonly abi = DSAuthorityAbi; 7 | 8 | /** 9 | * Checks whether source (account/contract) can call a function with 10 | * a certain signature on the destination contract 11 | * 12 | * @param source The address of the source account/contract 13 | * @param destination The address of the contract 14 | * @param signature The signature of the function to be called 15 | * @param block The block number to execute the call on. 16 | */ 17 | public canCall(source: Address, destination: Address, signature: string, block?: number) { 18 | return this.makeCall('canCall', [source, destination, signature], block); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/contracts/dependencies/authorization/DSGuard.ts: -------------------------------------------------------------------------------- 1 | import { Environment } from '../../../Environment'; 2 | import { Address } from '../../../Address'; 3 | import { Contract } from '../../../Contract'; 4 | import { DSGuardAbi } from '../../../abis/DSGuard.abi'; 5 | import { applyMixins } from '../../../utils/applyMixins'; 6 | import { DSAuth } from './DSAuth'; 7 | import { DSAuthority } from './DSAuthority'; 8 | 9 | export class DSGuard extends Contract { 10 | public static readonly abi = DSGuardAbi; 11 | 12 | public static deploy(environment: Environment, bytecode: string, from: Address) { 13 | return super.createDeployment(environment, bytecode, from); 14 | } 15 | } 16 | 17 | export interface DSGuard extends DSAuth, DSAuthority {} 18 | applyMixins(DSGuard, [DSAuth, DSAuthority]); 19 | -------------------------------------------------------------------------------- /src/contracts/dependencies/authorization/PermissiveAuthority.ts: -------------------------------------------------------------------------------- 1 | import { Environment } from '../../../Environment'; 2 | import { Address } from '../../../Address'; 3 | import { Contract } from '../../../Contract'; 4 | import { PermissiveAuthorityAbi } from '../../../abis/PermissiveAuthority.abi'; 5 | import { applyMixins } from '../../../utils/applyMixins'; 6 | import { DSAuthority } from './DSAuthority'; 7 | 8 | export class PermissiveAuthority extends Contract { 9 | public static readonly abi = PermissiveAuthorityAbi; 10 | 11 | public static deploy(environment: Environment, bytecode: string, from: Address) { 12 | return super.createDeployment(environment, bytecode, from); 13 | } 14 | } 15 | 16 | export interface PermissiveAuthority extends DSAuthority {} 17 | applyMixins(PermissiveAuthority, [DSAuthority]); 18 | -------------------------------------------------------------------------------- /src/contracts/dependencies/token/BurnableToken.test.ts: -------------------------------------------------------------------------------- 1 | import { TestEnvironment, createTestEnvironment } from '../../../utils/tests/createTestEnvironment'; 2 | import { BurnableTokenBytecode } from '../../../abis/BurnableToken.bin'; 3 | import { BurnableToken } from './BurnableToken'; 4 | 5 | describe('BurnableToken', () => { 6 | let environment: TestEnvironment; 7 | let burnableToken: BurnableToken; 8 | 9 | beforeAll(async () => { 10 | environment = await createTestEnvironment(); 11 | const deploy = BurnableToken.deploy(environment, BurnableTokenBytecode, environment.accounts[0], { 12 | symbol: 'BBB', 13 | decimals: 9, 14 | name: 'Burn baby burn', 15 | }); 16 | burnableToken = await deploy.send(await deploy.prepare()); 17 | }); 18 | 19 | it('should return balance of the token', async () => { 20 | const result = await burnableToken.getTotalSupply(); 21 | expect(result.isGreaterThanOrEqualTo(0)).toBe(true); 22 | }); 23 | 24 | it('should return the symbol of the token', async () => { 25 | const result = await burnableToken.getSymbol(); 26 | expect(result).toBe('BBB'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/contracts/dependencies/token/BurnableToken.ts: -------------------------------------------------------------------------------- 1 | import { Environment } from '../../../Environment'; 2 | import { Address } from '../../../Address'; 3 | import { Contract } from '../../../Contract'; 4 | import { applyMixins } from '../../../utils/applyMixins'; 5 | import { PreminedToken, PreminedTokenDeployArguments } from './PreminedToken'; 6 | import { BigNumber } from 'bignumber.js'; 7 | import { BurnableTokenAbi } from '../../../abis/BurnableToken.abi'; 8 | import { OutOfBalanceError } from '../../../errors/OutOfBalanceError'; 9 | import { isZeroAddress } from '../../../utils/isZeroAddress'; 10 | import { ZeroAddressError } from '../../../errors/ZeroAddressError'; 11 | 12 | export class BurnableToken extends Contract { 13 | public static readonly abi = BurnableTokenAbi; 14 | 15 | public static deploy(environment: Environment, bytecode: string, from: Address, args: PreminedTokenDeployArguments) { 16 | return super.createDeployment(environment, bytecode, from, [args.symbol, args.decimals, args.name]); 17 | } 18 | 19 | /** 20 | * Burn tokens 21 | * 22 | * @param from The address to burn 23 | * @param amount The amount to transfer 24 | */ 25 | public burn(from: Address, amount: BigNumber) { 26 | const args = [amount.toFixed(0)]; 27 | 28 | const validate = async () => { 29 | if (isZeroAddress(from)) throw new ZeroAddressError(); 30 | 31 | const balance = await this.getBalanceOf(from); 32 | if (balance.isLessThan(amount)) { 33 | throw new OutOfBalanceError(amount, balance); 34 | } 35 | }; 36 | 37 | return this.createTransaction({ from, method: 'burn', args, validate }); 38 | } 39 | } 40 | 41 | export interface BurnableToken extends PreminedToken {} 42 | applyMixins(BurnableToken, [PreminedToken]); 43 | -------------------------------------------------------------------------------- /src/contracts/dependencies/token/ERC20WithFields.ts: -------------------------------------------------------------------------------- 1 | import { ERC20WithFieldsAbi } from '../../../abis/ERC20WithFields.abi'; 2 | import { Environment } from '../../../Environment'; 3 | import { Address } from '../../../Address'; 4 | import { toBigNumber } from '../../../utils/toBigNumber'; 5 | import { Contract } from '../../../Contract'; 6 | 7 | export class ERC20WithFields extends Contract { 8 | public static readonly abi = ERC20WithFieldsAbi; 9 | 10 | public static deploy(environment: Environment, bytecode: string, from: Address) { 11 | return super.createDeployment(environment, bytecode, from); 12 | } 13 | 14 | /** 15 | * Gets the balance of an owner. 16 | * 17 | * @param who The address of the one we want to check the balance of. 18 | * @param block The block number to execute the call on. 19 | */ 20 | public async getBalanceOf(who: Address, block?: number) { 21 | const result = await this.makeCall('balanceOf', [who], block); 22 | return toBigNumber(result); 23 | } 24 | 25 | /** 26 | * Gets the ammount of token that an owner allows a spender to use. 27 | * 28 | * @param owner The owner address. 29 | * @param spender The spender address. 30 | * @param block The block number to execute the call on. 31 | */ 32 | public async getAllowance(owner: Address, spender: Address, block?: number) { 33 | const result = await this.makeCall('allowance', [owner, spender], block); 34 | return toBigNumber(result); 35 | } 36 | 37 | /** 38 | * Gets the total supply of the token. 39 | * 40 | * @param block The block number to execute the call on. 41 | */ 42 | public async getTotalSupply(block?: number) { 43 | const result = await this.makeCall('totalSupply', undefined, block); 44 | return toBigNumber(result); 45 | } 46 | 47 | /** 48 | * Gets the name of the token. 49 | * 50 | * @param block The block number to execute the call on. 51 | */ 52 | public getName(block?: number) { 53 | return this.makeCall('name', undefined, block); 54 | } 55 | 56 | /** 57 | * Gets the symbol of the token. 58 | * 59 | * @param block The block number to execute the call on. 60 | */ 61 | public getSymbol(block?: number) { 62 | return this.makeCall('symbol', undefined, block); 63 | } 64 | 65 | /** 66 | * Gets the decimals of the token. 67 | * 68 | * @param block The block number to execute the call on. 69 | */ 70 | public getDecimals(block?: number) { 71 | return this.makeCall('decimals', undefined, block); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/contracts/dependencies/token/MaliciousToken.test.ts: -------------------------------------------------------------------------------- 1 | import { TestEnvironment, createTestEnvironment } from '../../../utils/tests/createTestEnvironment'; 2 | import { PreminedToken } from './PreminedToken'; 3 | import { MaliciousToken } from './MaliciousToken'; 4 | import { MaliciousTokenBytecode } from '../../../abis/MaliciousToken.bin'; 5 | 6 | describe('MaliciousToken', () => { 7 | let environment: TestEnvironment; 8 | let maliciousToken: MaliciousToken; 9 | 10 | beforeAll(async () => { 11 | environment = await createTestEnvironment(); 12 | const deploy = PreminedToken.deploy(environment, MaliciousTokenBytecode, environment.accounts[0], { 13 | symbol: 'MMM', 14 | decimals: 15, 15 | name: 'Malicous Monster Money', 16 | }); 17 | maliciousToken = await deploy.send(await deploy.prepare()); 18 | }); 19 | 20 | it('should return balance of the token', async () => { 21 | const result = await maliciousToken.getTotalSupply(); 22 | expect(result.isGreaterThanOrEqualTo(0)).toBe(true); 23 | }); 24 | 25 | it('should return the symbol of the token', async () => { 26 | const result = await maliciousToken.getSymbol(); 27 | expect(result).toBe('MMM'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/contracts/dependencies/token/MaliciousToken.ts: -------------------------------------------------------------------------------- 1 | import { Environment } from '../../../Environment'; 2 | import { Address } from '../../../Address'; 3 | import { Contract } from '../../../Contract'; 4 | import { PreminedTokenAbi } from '../../../abis/PreminedToken.abi'; 5 | import { applyMixins } from '../../../utils/applyMixins'; 6 | import { PreminedToken } from './PreminedToken'; 7 | 8 | export interface PreminedTokenDeployArguments { 9 | symbol: string; 10 | decimals: number; 11 | name: string; 12 | } 13 | 14 | export class MaliciousToken extends Contract { 15 | public static readonly abi = PreminedTokenAbi; 16 | 17 | public static deploy(environment: Environment, bytecode: string, from: Address, args: PreminedTokenDeployArguments) { 18 | return super.createDeployment(environment, bytecode, from, [args.symbol, args.decimals, args.name]); 19 | } 20 | } 21 | 22 | export interface MaliciousToken extends PreminedToken {} 23 | applyMixins(PreminedToken, [PreminedToken]); 24 | -------------------------------------------------------------------------------- /src/contracts/dependencies/token/PreminedToken.test.ts: -------------------------------------------------------------------------------- 1 | import { TestEnvironment, createTestEnvironment } from '../../../utils/tests/createTestEnvironment'; 2 | import { PreminedToken } from './PreminedToken'; 3 | import { PreminedTokenBytecode } from '../../../abis/PreminedToken.bin'; 4 | 5 | describe('PreminedToken', () => { 6 | let environment: TestEnvironment; 7 | let preminedToken: PreminedToken; 8 | 9 | beforeAll(async () => { 10 | environment = await createTestEnvironment(); 11 | const deploy = PreminedToken.deploy(environment, PreminedTokenBytecode, environment.accounts[0], { 12 | symbol: 'TTT', 13 | decimals: 12, 14 | name: 'Turbo Test Token', 15 | }); 16 | preminedToken = await deploy.send(await deploy.prepare()); 17 | }); 18 | 19 | it('should return balance of the token', async () => { 20 | const result = await preminedToken.getTotalSupply(); 21 | expect(result.isGreaterThanOrEqualTo(0)).toBe(true); 22 | }); 23 | 24 | it('should return the symbol of the token', async () => { 25 | const result = await preminedToken.getSymbol(); 26 | expect(result).toBe('TTT'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/contracts/dependencies/token/PreminedToken.ts: -------------------------------------------------------------------------------- 1 | import { Environment } from '../../../Environment'; 2 | import { Address } from '../../../Address'; 3 | import { Contract } from '../../../Contract'; 4 | import { PreminedTokenAbi } from '../../../abis/PreminedToken.abi'; 5 | import { StandardToken } from './StandardToken'; 6 | import { applyMixins } from '../../../utils/applyMixins'; 7 | 8 | export interface PreminedTokenDeployArguments { 9 | symbol: string; 10 | decimals: number; 11 | name: string; 12 | } 13 | 14 | export class PreminedToken extends Contract { 15 | public static readonly abi = PreminedTokenAbi; 16 | 17 | public static deploy(environment: Environment, bytecode: string, from: Address, args: PreminedTokenDeployArguments) { 18 | return super.createDeployment(environment, bytecode, from, [args.symbol, args.decimals, args.name]); 19 | } 20 | } 21 | 22 | export interface PreminedToken extends StandardToken {} 23 | applyMixins(PreminedToken, [StandardToken]); 24 | -------------------------------------------------------------------------------- /src/contracts/dependencies/token/StandardToken.test.ts: -------------------------------------------------------------------------------- 1 | import { TestEnvironment, createTestEnvironment } from '../../../utils/tests/createTestEnvironment'; 2 | import { StandardToken } from './StandardToken'; 3 | import { StandardTokenBytecode } from '../../../abis/StandardToken.bin'; 4 | import BigNumber from 'bignumber.js'; 5 | import { randomAddress } from '../../../utils/tests/randomAddress'; 6 | import { OutOfBalanceError } from '../../../errors/OutOfBalanceError'; 7 | import { ZeroAddressError } from '../../../errors/ZeroAddressError'; 8 | 9 | describe('StandardToken', () => { 10 | let environment: TestEnvironment; 11 | let standardToken: StandardToken; 12 | 13 | beforeAll(async () => { 14 | environment = await createTestEnvironment(); 15 | const deploy = StandardToken.deploy(environment, StandardTokenBytecode, environment.accounts[0]); 16 | standardToken = await deploy.send(await deploy.prepare()); 17 | }); 18 | 19 | it('should return balance of the token', async () => { 20 | const result = await standardToken.getTotalSupply(); 21 | expect(result.isGreaterThanOrEqualTo(0)).toBe(true); 22 | }); 23 | 24 | it('should throw OutOfBalanceError', async () => { 25 | const amount = new BigNumber(2); 26 | const tx = standardToken.transfer(randomAddress(), randomAddress(), amount); 27 | 28 | jest.spyOn(standardToken, 'getBalanceOf').mockReturnValue(new Promise((resolve) => resolve(new BigNumber(1)))); 29 | 30 | const rejects = expect(tx.validate()).rejects; 31 | await rejects.toThrowError(OutOfBalanceError); 32 | await rejects.toMatchObject({ 33 | amount: expect.any(BigNumber), 34 | balance: expect.any(BigNumber), 35 | }); 36 | }); 37 | 38 | it('should throw ZeroAddressError', async () => { 39 | const amount = new BigNumber(1); 40 | const from = randomAddress(); 41 | const to = '0x0000000000000000000000000000000000000000'; 42 | const tx = standardToken.transfer(from, to, amount); 43 | 44 | jest.spyOn(standardToken, 'getBalanceOf').mockReturnValue(new Promise((resolve) => resolve(new BigNumber(2)))); 45 | 46 | await expect(tx.validate()).rejects.toThrowError(ZeroAddressError); 47 | }); 48 | 49 | it('should increase the approval for an account', async () => { 50 | const amount = new BigNumber(1); 51 | const spender = randomAddress(); 52 | 53 | const tx = standardToken.increaseApproval(environment.accounts[0], spender, amount); 54 | const result = await tx.send(await tx.prepare()); 55 | expect(result.status).toBe(true); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /src/contracts/dependencies/token/Weth.test.ts: -------------------------------------------------------------------------------- 1 | import { fromWei, toWei } from 'web3-utils'; 2 | import { Weth } from './Weth'; 3 | import { WETHBytecode } from '../../../abis/WETH.bin'; 4 | import { createTestEnvironment, TestEnvironment } from '../../../utils/tests/createTestEnvironment'; 5 | import { OutOfBalanceError } from '../../../errors/OutOfBalanceError'; 6 | import { randomAddress } from '../../../utils/tests/randomAddress'; 7 | import BigNumber from 'bignumber.js'; 8 | import { toBigNumber } from '../../../utils/toBigNumber'; 9 | 10 | describe('Weth', () => { 11 | let environment: TestEnvironment; 12 | let weth: Weth; 13 | 14 | beforeAll(async () => { 15 | environment = await createTestEnvironment(); 16 | const contract = Weth.deploy(environment, WETHBytecode, environment.accounts[0]); 17 | weth = await contract.send(await contract.prepare()); 18 | }); 19 | 20 | it('should allow deposit and withdrawal', async () => { 21 | const before = await weth.getBalanceOf(environment.accounts[1]); 22 | expect(fromWei(before.toFixed(0))).toBe('0'); 23 | 24 | { 25 | const tx = weth.deposit(environment.accounts[1], toBigNumber(toWei('1'))); 26 | await tx.send(await tx.prepare()); 27 | } 28 | 29 | const after = await weth.getBalanceOf(environment.accounts[1]); 30 | expect(fromWei(after.toFixed(0))).toBe('1'); 31 | 32 | { 33 | const tx = weth.withdraw(environment.accounts[1], toBigNumber(toWei('1'))); 34 | await tx.send(await tx.prepare()); 35 | } 36 | 37 | const final = await weth.getBalanceOf(environment.accounts[1]); 38 | expect(fromWei(final.toFixed(0))).toBe('0'); 39 | }); 40 | 41 | xit('should return the total supply of the token', async () => { 42 | const result = await weth.getTotalSupply(); 43 | expect(fromWei(result.toFixed(0))).toBe('0'); 44 | }); 45 | 46 | it('should throw OutOfBalanceError', async () => { 47 | const tx = weth.withdraw(randomAddress(), new BigNumber(2)); 48 | 49 | jest.spyOn(weth, 'getBalanceOf').mockReturnValue(new Promise((resolve) => resolve(new BigNumber(1)))); 50 | 51 | const rejects = expect(tx.validate()).rejects; 52 | await rejects.toThrowError(OutOfBalanceError); 53 | await rejects.toMatchObject({ 54 | amount: expect.any(BigNumber), 55 | balance: expect.any(BigNumber), 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/contracts/dependencies/token/Weth.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { WETHAbi } from '../../../abis/WETH.abi'; 3 | import { Environment } from '../../../Environment'; 4 | import { Address } from '../../../Address'; 5 | import { ERC20WithFields } from './ERC20WithFields'; 6 | import { Contract } from '../../../Contract'; 7 | import { applyMixins } from '../../../utils/applyMixins'; 8 | import { OutOfBalanceError } from '../../../errors/OutOfBalanceError'; 9 | 10 | export class Weth extends Contract { 11 | public static readonly abi = WETHAbi; 12 | 13 | public static deploy(environment: Environment, bytecode: string, from: Address) { 14 | return super.createDeployment(environment, bytecode, from); 15 | } 16 | 17 | /** 18 | * Deposit WETH in the senders account 19 | * 20 | * @param from The address of the sender 21 | * @param amount The amount to deposit 22 | */ 23 | public deposit(from: Address, amount: BigNumber) { 24 | return this.createTransaction({ from, method: 'deposit', args: undefined, value: amount }); 25 | } 26 | 27 | /** 28 | * Withdraw WETH from the senders account 29 | * 30 | * @param from The address of the sender 31 | * @param amount The amount to withdraw 32 | */ 33 | public withdraw(from: Address, amount: BigNumber) { 34 | const args = [amount.toFixed(0)]; 35 | 36 | const validate = async () => { 37 | const balance = await this.getBalanceOf(from); 38 | if (balance.isLessThan(amount)) { 39 | throw new OutOfBalanceError(amount, balance); 40 | } 41 | }; 42 | 43 | return this.createTransaction({ from, method: 'withdraw', args, validate }); 44 | } 45 | } 46 | 47 | export interface Weth extends ERC20WithFields {} 48 | applyMixins(Weth, [ERC20WithFields]); 49 | -------------------------------------------------------------------------------- /src/contracts/engine/AmguConsumer.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../Contract'; 2 | import { Address } from '../../Address'; 3 | import { AmguConsumerAbi } from '../../abis/AmguConsumer.abi'; 4 | import { Engine } from './Engine'; 5 | import { IPriceSource } from '../prices/IPriceSource'; 6 | import { ERC20WithFields } from '../dependencies/token/ERC20WithFields'; 7 | import BigNumber from 'bignumber.js'; 8 | 9 | export class AmguConsumer extends Contract { 10 | public static readonly abi = AmguConsumerAbi; 11 | 12 | /** 13 | * Gets the amgu token. 14 | * 15 | * @param block The block number to execute the call on. 16 | */ 17 | public getAmguToken(block?: number) { 18 | return this.makeCall
('mlnToken', undefined, block); 19 | } 20 | 21 | /** 22 | * Gets the engine address. 23 | * 24 | * @param block The block number to execute the call on. 25 | */ 26 | public getEngine(block?: number) { 27 | return this.makeCall
('engine', undefined, block); 28 | } 29 | 30 | /** 31 | * Gets the price source address. 32 | * 33 | * @param block The block number to execute the call on. 34 | */ 35 | public getPriceSource(block?: number) { 36 | return this.makeCall
('priceSource', undefined, block); 37 | } 38 | 39 | /** 40 | * Gets the version address. 41 | * 42 | * @param block The block number to execute the call on. 43 | */ 44 | public getVersion(block?: number) { 45 | return this.makeCall
('version', undefined, block); 46 | } 47 | 48 | /** 49 | * Calculates the required AMGU value for a transaction. 50 | * 51 | * @param gasEstimation The gas estimation value. 52 | * @param block The block number to execute the call on. 53 | */ 54 | protected async calculateAmgu(gasEstimation: number, block?: number) { 55 | const [amguTokenAddress, engineAddress, priceSourceAddress] = await Promise.all([ 56 | this.getAmguToken(block), 57 | this.getEngine(block), 58 | this.getPriceSource(block), 59 | ]); 60 | 61 | // TODO: Can we derive the price feed type somehow or create a lightweight, common denominator just based 62 | // on the interface for cases like this? 63 | const prices = new IPriceSource(this.environment, priceSourceAddress); 64 | const engine = new Engine(this.environment, engineAddress); 65 | const amgu = new ERC20WithFields(this.environment, amguTokenAddress); 66 | 67 | const [amguDecimals, mlnPerAmgu, ethPerMln] = await Promise.all([ 68 | amgu.getDecimals(block), 69 | engine.getAmguPrice(block), 70 | prices.getPrice(amguTokenAddress, block), 71 | ]); 72 | 73 | const result = new BigNumber(1) 74 | .multipliedBy(mlnPerAmgu) 75 | .multipliedBy(ethPerMln.price) 76 | .multipliedBy(gasEstimation) 77 | .dividedBy(new BigNumber(10).exponentiatedBy(amguDecimals)) 78 | .decimalPlaces(0, BigNumber.ROUND_CEIL); 79 | 80 | return result; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/contracts/engine/Engine.test.ts: -------------------------------------------------------------------------------- 1 | import { Engine } from './Engine'; 2 | import { sameAddress } from '../../utils/sameAddress'; 3 | import { TestEnvironment, createTestEnvironment } from '../../utils/tests/createTestEnvironment'; 4 | import { deployRegistry } from '../../utils/tests/deployRegistry'; 5 | import { deployEngine } from '../../utils/tests/deployEngine'; 6 | import BigNumber from 'bignumber.js'; 7 | import { Registry } from '../version/Registry'; 8 | 9 | describe('Engine', () => { 10 | let environment: TestEnvironment; 11 | let engine: Engine; 12 | let registry: Registry; 13 | 14 | beforeAll(async () => { 15 | environment = await createTestEnvironment(); 16 | registry = await deployRegistry(environment, environment.accounts[0], environment.accounts[0]); 17 | engine = await deployEngine(environment, environment.accounts[0], { 18 | delay: new BigNumber(1000000), 19 | registry: registry.contract.address, 20 | }); 21 | }); 22 | 23 | it('should return the amgu price', async () => { 24 | const result = await engine.getAmguPrice(); 25 | expect(result.isGreaterThanOrEqualTo(0)).toBe(true); 26 | }); 27 | 28 | // needs price source, so skipping for the moment 29 | 30 | // it('should set and return the engine price', async () => { 31 | // const result = await engine.getEnginePrice(); 32 | // console.log(result); 33 | // expect(result.isGreaterThanOrEqualTo(0)).toBe(true); 34 | // }); 35 | 36 | it('should return the frozen ether', async () => { 37 | const result = await engine.getFrozenEther(); 38 | expect(result.isGreaterThanOrEqualTo(0)).toBe(true); 39 | }); 40 | 41 | it('should return the liquid ether', async () => { 42 | const result = await engine.getLiquidEther(); 43 | expect(result.isGreaterThanOrEqualTo(0)).toBe(true); 44 | }); 45 | 46 | it('should return the percentage premium', async () => { 47 | const result = await engine.getPremiumPercent(); 48 | expect(result.isGreaterThanOrEqualTo(0)).toBe(true); 49 | }); 50 | 51 | it('should return the address of the registry', async () => { 52 | const result = await engine.getRegistry(); 53 | expect(sameAddress(result, registry.contract.address)).toBe(true); 54 | }); 55 | 56 | it('should return the total ether consumed', async () => { 57 | const result = await engine.getTotalEtherConsumed(); 58 | expect(result.isGreaterThanOrEqualTo(0)).toBe(true); 59 | }); 60 | 61 | it('should return the total amgu consumed', async () => { 62 | const result = await engine.getTotalAmguConsumed(); 63 | expect(result.isGreaterThanOrEqualTo(0)).toBe(true); 64 | }); 65 | 66 | it('should return the total MLN burned', async () => { 67 | const result = await engine.getTotalMlnBurned(); 68 | expect(result.isGreaterThanOrEqualTo(0)).toBe(true); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /src/contracts/exchanges/ExchangeAdapter.test.ts: -------------------------------------------------------------------------------- 1 | import { createTestEnvironment, TestEnvironment } from '../../utils/tests/createTestEnvironment'; 2 | 3 | import { ExchangeAdapter } from './ExchangeAdapter'; 4 | import { ExchangeAdapterBytecode } from '../../abis/ExchangeAdapter.bin'; 5 | 6 | describe('ExchangeAdapter', () => { 7 | let environment: TestEnvironment; 8 | let exchangeAdapter: ExchangeAdapter; 9 | 10 | beforeAll(async () => { 11 | environment = await createTestEnvironment(); 12 | 13 | const deploy = ExchangeAdapter.deploy(environment, ExchangeAdapterBytecode, environment.accounts[0]); 14 | exchangeAdapter = await deploy.send(await deploy.prepare()); 15 | }); 16 | 17 | it('should check if the exchange adapter has an address', async () => { 18 | const result = exchangeAdapter.contract.address; 19 | expect(result.startsWith('0x')).toBe(true); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/contracts/exchanges/ExchangeAdapter.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../Contract'; 2 | import { Address } from '../../Address'; 3 | import { Environment } from '../../Environment'; 4 | import { ExchangeAdapterAbi } from '../../abis/ExchangeAdapter.abi'; 5 | 6 | export class ExchangeAdapter extends Contract { 7 | public static readonly abi = ExchangeAdapterAbi; 8 | 9 | public static deploy(environment: Environment, bytecode: string, from: Address) { 10 | return super.createDeployment(environment, bytecode, from); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/contracts/exchanges/third-party/kyber/KyberNetworkProxy.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { Contract } from '../../../../Contract'; 3 | import { Environment } from '../../../../Environment'; 4 | import { Address } from '../../../../Address'; 5 | import { KyberNetworkProxyAbi } from '../../../../abis/KyberNetworkProxy.abi'; 6 | 7 | export interface KyberExpectedRate { 8 | expectedRate: BigNumber; 9 | slippageRate: BigNumber; 10 | } 11 | 12 | export class KyberNetworkProxy extends Contract { 13 | public static readonly abi = KyberNetworkProxyAbi; 14 | 15 | public static deploy(environment: Environment, bytecode: string, from: Address) { 16 | return super.createDeployment(environment, bytecode, from); 17 | } 18 | 19 | /** 20 | * Gets the expected rate for a given asset pair. 21 | * 22 | * @param srcToken The maker asset address. 23 | * @param destToken The taker asset address. 24 | * @param srcQty The maker asset amount. 25 | * @param block The block number to execute the call on. 26 | */ 27 | public async getExpectedRate( 28 | srcToken: Address, 29 | destToken: Address, 30 | srcQty: BigNumber, 31 | block?: number, 32 | ): Promise { 33 | const { '0': expectedRate, '1': slippageRate } = await this.makeCall<{ 34 | '0': string; 35 | '1': string; 36 | }>('getExpectedRate', [srcToken, destToken, srcQty.toFixed(0)], block); 37 | 38 | return { 39 | expectedRate: new BigNumber(expectedRate), 40 | slippageRate: new BigNumber(slippageRate), 41 | } as KyberExpectedRate; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/contracts/exchanges/third-party/oasisdex/OasisDexAccessor.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../../Contract'; 2 | import { Environment } from '../../../../Environment'; 3 | import { Address } from '../../../../Address'; 4 | import { OasisDexAccessorAbi } from '../../../../abis/OasisDexAccessor.abi'; 5 | import { toBigNumber } from '../../../../utils/toBigNumber'; 6 | import BigNumber from 'bignumber.js'; 7 | 8 | export interface OasisDexOrder { 9 | id: BigNumber; 10 | sellQuantity: BigNumber; 11 | buyQuantity: BigNumber; 12 | } 13 | 14 | export class OasisDexAccessor extends Contract { 15 | public static readonly abi = OasisDexAccessorAbi; 16 | 17 | public static deploy(environment: Environment, bytecode: string, from: Address) { 18 | return super.createDeployment(environment, bytecode, from); 19 | } 20 | 21 | /** 22 | * Gets current market orders for a given asset pair. 23 | * 24 | * @param targetExchange The address of the exchange. 25 | * @param sellAsset The sell asset address. 26 | * @param buyAsset The buy asset address. 27 | * @param block The block number to execute the call on. 28 | */ 29 | public async getOrders( 30 | targetExchange: Address, 31 | sellAsset: Address, 32 | buyAsset: Address, 33 | block?: number, 34 | ): Promise { 35 | const { '0': ids, '1': sellQtys, '2': buyQtys } = await this.makeCall<{ 36 | '0': string[]; 37 | '1': string[]; 38 | '2': string[]; 39 | }>('getOrders', [targetExchange, sellAsset, buyAsset], block); 40 | 41 | return (ids || []).map((id, index) => ({ 42 | id: toBigNumber(id), 43 | sellQuantity: toBigNumber(sellQtys[index]), 44 | buyQuantity: toBigNumber(buyQtys[index]), 45 | })); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/contracts/exchanges/third-party/oasisdex/OasisDexExchange.test.ts: -------------------------------------------------------------------------------- 1 | import { createTestEnvironment, TestEnvironment } from '../../../../utils/tests/createTestEnvironment'; 2 | import BigNumber from 'bignumber.js'; 3 | 4 | import { OasisDexExchange } from './OasisDexExchange'; 5 | import { OasisDexExchangeBytecode } from '../../../../abis/OasisDexExchange.bin'; 6 | 7 | describe('OasisDex', () => { 8 | let environment: TestEnvironment; 9 | let oasisDex: OasisDexExchange; 10 | 11 | beforeAll(async () => { 12 | environment = await createTestEnvironment(); 13 | 14 | const deploy = OasisDexExchange.deploy( 15 | environment, 16 | OasisDexExchangeBytecode, 17 | environment.accounts[0], 18 | new BigNumber('999999999999'), 19 | ); 20 | 21 | oasisDex = await deploy.send(await deploy.prepare()); 22 | }); 23 | 24 | it('should check if the exchange adapter has an address', async () => { 25 | const result = oasisDex.contract.address; 26 | expect(result.startsWith('0x')).toBe(true); 27 | }); 28 | 29 | fit('should get an order', async () => { 30 | const result = await oasisDex.getOffer(new BigNumber(0)); 31 | expect(result).toMatchObject({ 32 | makerQuantity: expect.any(BigNumber), 33 | makerAsset: expect.any(String), 34 | takerQuantity: expect.any(BigNumber), 35 | takerAsset: expect.any(String), 36 | owner: expect.any(String), 37 | timestamp: expect.any(Date), 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/contracts/exchanges/third-party/oasisdex/OasisDexExchange.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../../Contract'; 2 | import { Environment } from '../../../../Environment'; 3 | import { Address } from '../../../../Address'; 4 | import { toBigNumber } from '../../../../utils/toBigNumber'; 5 | import { toDate } from '../../../../utils/toDate'; 6 | import { OasisDexExchangeAbi } from '../../../../abis/OasisDexExchange.abi'; 7 | import BigNumber from 'bignumber.js'; 8 | 9 | export interface OasisDexOffer { 10 | makerQuantity: BigNumber; 11 | makerAsset: Address; 12 | takerQuantity: BigNumber; 13 | takerAsset: Address; 14 | owner: Address; 15 | timestamp: Date; 16 | } 17 | 18 | export class OasisDexExchange extends Contract { 19 | public static readonly abi = OasisDexExchangeAbi; 20 | 21 | public static deploy(environment: Environment, bytecode: string, from: Address, closetime: BigNumber) { 22 | return super.createDeployment(environment, bytecode, from, [closetime.toFixed(0)]); 23 | } 24 | 25 | /** 26 | * Gets the details of an offer 27 | * 28 | * @param id The id of the offer 29 | * @param block The block number to execute the call on. 30 | */ 31 | public async getOffer(id: BigNumber, block?: number): Promise { 32 | const { 33 | '0': makerQuantity, 34 | '1': makerAsset, 35 | '2': takerQuantity, 36 | '3': takerAsset, 37 | '4': owner, 38 | '5': timestamp, 39 | } = await this.makeCall<{ 40 | '0': string; 41 | '1': string; 42 | '2': string; 43 | '3': string; 44 | '4': string; 45 | '5': string; 46 | }>('offers', [id.toFixed(0)], block); 47 | 48 | return { 49 | makerQuantity: toBigNumber(makerQuantity), 50 | makerAsset, 51 | takerQuantity: toBigNumber(takerQuantity), 52 | takerAsset, 53 | owner, 54 | timestamp: toDate(timestamp), 55 | }; 56 | } 57 | 58 | /** 59 | * Checks whether an offer is active 60 | * 61 | * @param id The id of the offer 62 | * @param block The block number to execute the call on. 63 | */ 64 | public async isActive(id: BigNumber, block?: number) { 65 | return this.makeCall('isActive', [id.toFixed(0)], block); 66 | } 67 | 68 | /** 69 | * Gets the owner of an offer 70 | * 71 | * @param id The id of the offer 72 | * @param block The block number to execute the call on. 73 | */ 74 | public async getOwner(id: BigNumber, block?: number) { 75 | return this.makeCall
('getOwner', [id.toFixed(0)], block); 76 | } 77 | 78 | /** 79 | * Checks whether an offer is closed 80 | * 81 | * @param block The block number to execute the call on. 82 | */ 83 | public async isClosed(block?: number) { 84 | return this.makeCall('isClosed', undefined, block); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/contracts/exchanges/third-party/uniswap/UniswapExchange.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../../Contract'; 2 | import { Environment } from '../../../../Environment'; 3 | import { Address } from '../../../../Address'; 4 | import { UniswapExchangeAbi } from '../../../../abis/UniswapExchange.abi'; 5 | import BigNumber from 'bignumber.js'; 6 | import { toBigNumber } from '../../../../utils/toBigNumber'; 7 | 8 | export class UniswapExchange extends Contract { 9 | public static readonly abi = UniswapExchangeAbi; 10 | 11 | public static deploy(environment: Environment, bytecode: string, from: Address) { 12 | return super.createDeployment(environment, bytecode, from); 13 | } 14 | 15 | /** 16 | * Gets the input price for a swap from ETH to token 17 | * 18 | * @param ethSold The amount of ETH sold (in wei) 19 | * @param block The block number to execute the call on. 20 | */ 21 | public async getEthToTokenInputPrice(ethSold: BigNumber, block?: number) { 22 | const result = await this.makeCall('getEthToTokenInputPrice', [ethSold.toFixed(0)], block); 23 | return toBigNumber(result); 24 | } 25 | 26 | /** 27 | * Gets the input price for a swap from token to ETH 28 | * 29 | * @param tokensSold The amount of token sold (in base unit) 30 | * @param block The block number to execute the call on. 31 | */ 32 | public async getTokenToEthInputPrice(tokensBought: BigNumber, block?: number) { 33 | const result = await this.makeCall('getTokenToEthInputPrice', [tokensBought.toFixed(0)], block); 34 | return toBigNumber(result); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/contracts/exchanges/third-party/uniswap/UniswapFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { TestEnvironment, createTestEnvironment } from '../../../../utils/tests/createTestEnvironment'; 2 | import { UniswapFactory } from './UniswapFactory'; 3 | import { randomAddress } from '../../../../utils/tests/randomAddress'; 4 | 5 | describe('UniswapFactory', () => { 6 | let environment: TestEnvironment; 7 | let uniswapFactory: UniswapFactory; 8 | 9 | beforeAll(async () => { 10 | environment = await createTestEnvironment(); 11 | 12 | uniswapFactory = new UniswapFactory(environment, randomAddress()); 13 | }); 14 | 15 | it('should check if the uniswap factory has an address', async () => { 16 | const result = uniswapFactory.contract.address; 17 | expect(result.startsWith('0x')).toBe(true); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/contracts/exchanges/third-party/uniswap/UniswapFactory.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../../Contract'; 2 | import { Environment } from '../../../../Environment'; 3 | import { Address } from '../../../../Address'; 4 | import { UniswapFactoryAbi } from '../../../../abis/UniswapFactory.abi'; 5 | 6 | export class UniswapFactory extends Contract { 7 | public static readonly abi = UniswapFactoryAbi; 8 | 9 | public static deploy(environment: Environment, bytecode: string, from: Address) { 10 | return super.createDeployment(environment, bytecode, from); 11 | } 12 | 13 | /** 14 | * Gets the exchange address 15 | * 16 | * @param token The maker asset address. 17 | * @param block The block number to execute the call on. 18 | */ 19 | public getExchange(token: Address, block?: number) { 20 | return this.makeCall
('getExchange', [token], block); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/contracts/exchanges/third-party/zeroex/ZeroExV2Exchange.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { Contract } from '../../../../Contract'; 3 | import { Environment } from '../../../../Environment'; 4 | import { Address } from '../../../../Address'; 5 | import { ZeroExV2ExchangeAbi } from '../../../../abis/ZeroExV2Exchange.abi'; 6 | import { Order } from '@0x/order-utils-v2'; 7 | 8 | export interface ZeroExV2Order extends Order {} 9 | 10 | export enum ZeroExV2OrderStatus { 11 | INVALID, 12 | INVALID_MAKER_ASSET_AMOUNT, 13 | INVALID_TAKER_ASSET_AMOUNT, 14 | FILLABLE, 15 | EXPIRED, 16 | FULLY_FILLED, 17 | CANCELLED, 18 | } 19 | 20 | export interface ZeroExV2OrderInfo { 21 | orderStatus: ZeroExV2OrderStatus; 22 | orderHash: string; 23 | orderTakerAssetFilledAmount: BigNumber; 24 | } 25 | 26 | export class ZeroExV2Exchange extends Contract { 27 | public static readonly abi = ZeroExV2ExchangeAbi; 28 | 29 | public static deploy(environment: Environment, bytecode: string, from: Address, closetime: BigNumber) { 30 | return super.createDeployment(environment, bytecode, from, [closetime.toFixed(0)]); 31 | } 32 | 33 | /** 34 | * Gets the order info. 35 | * 36 | * @param order The order object. 37 | * @param block The block number to execute the call on. 38 | */ 39 | public async getOrderInfo(order: ZeroExV2Order, block?: number) { 40 | return await this.makeCall('getOrderInfo', [order], block); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/contracts/exchanges/third-party/zeroex/ZeroExV3Exchange.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { Contract } from '../../../../Contract'; 3 | import { Environment } from '../../../../Environment'; 4 | import { Address } from '../../../../Address'; 5 | import { Order } from '@0x/order-utils'; 6 | import { ZeroExV3ExchangeAbi } from '../../../../abis/ZeroExV3Exchange.abi'; 7 | 8 | export interface ZeroExV3Order extends Order {} 9 | 10 | export enum ZeroExV3OrderStatus { 11 | INVALID, 12 | INVALID_MAKER_ASSET_AMOUNT, 13 | INVALID_TAKER_ASSET_AMOUNT, 14 | FILLABLE, 15 | EXPIRED, 16 | FULLY_FILLED, 17 | CANCELLED, 18 | } 19 | 20 | export interface ZeroExV3OrderInfo { 21 | orderStatus: ZeroExV3OrderStatus; 22 | orderHash: string; 23 | orderTakerAssetFilledAmount: BigNumber; 24 | } 25 | 26 | export class ZeroExV3Exchange extends Contract { 27 | public static readonly abi = ZeroExV3ExchangeAbi; 28 | 29 | public static deploy(environment: Environment, bytecode: string, from: Address, closetime: BigNumber) { 30 | return super.createDeployment(environment, bytecode, from, [closetime.toFixed(0)]); 31 | } 32 | 33 | /** 34 | * Gets the order info. 35 | * 36 | * @param order The order object. 37 | * @param block The block number to execute the call on. 38 | */ 39 | public async getOrderInfo(order: ZeroExV3Order, block?: number) { 40 | return await this.makeCall('getOrderInfo', [order], block); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/contracts/factory/Factory.test.ts: -------------------------------------------------------------------------------- 1 | import { createTestEnvironment, TestEnvironment } from '../../utils/tests/createTestEnvironment'; 2 | import { randomAddress } from '../../utils/tests/randomAddress'; 3 | import { Factory } from './Factory'; 4 | import { FactoryBytecode } from '../../abis/Factory.bin'; 5 | 6 | describe('Factory', () => { 7 | let environment: TestEnvironment; 8 | let factory: Factory; 9 | 10 | beforeAll(async () => { 11 | environment = await createTestEnvironment(); 12 | 13 | const deploy = Factory.deploy(environment, FactoryBytecode, environment.accounts[0]); 14 | factory = await deploy.send(await deploy.prepare()); 15 | }); 16 | 17 | it('should check if a contract is an instance of a factory', async () => { 18 | const result = await factory.isInstance(randomAddress()); 19 | expect(typeof result).toBe('boolean'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/contracts/factory/Factory.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../Contract'; 2 | import { Address } from '../../Address'; 3 | import { FactoryAbi } from '../../abis/Factory.abi'; 4 | import { Environment } from '../../Environment'; 5 | 6 | export class Factory extends Contract { 7 | public static readonly abi = FactoryAbi; 8 | 9 | public static deploy(environment: Environment, bytecode: string, from: Address) { 10 | return super.createDeployment(environment, bytecode, from); 11 | } 12 | 13 | /** 14 | * Checks if an address is an instance of the factory 15 | * 16 | * @param address The address of the contract to check 17 | * @param block The block number to execute the call on. 18 | */ 19 | public isInstance(address: Address, block?: number) { 20 | return this.makeCall('isInstance', [address], block); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/contracts/factory/FundFactory.error.ts: -------------------------------------------------------------------------------- 1 | import { ValidationError } from '../../errors/ValidationError'; 2 | import { Address } from '../../Address'; 3 | 4 | export class ComponentNotSetError extends ValidationError { 5 | public readonly name = 'ComponentNotSetError'; 6 | constructor(message: string = 'Component has not been set yet.') { 7 | super(message); 8 | } 9 | } 10 | 11 | export class ComponentAlreadySetError extends ValidationError { 12 | public readonly name = 'ComponentAlreadySetError'; 13 | constructor(public readonly address: Address, message: string = 'Component has already been set.') { 14 | super(message); 15 | } 16 | } 17 | 18 | export class DenominationAssetNotRegisteredError extends ValidationError { 19 | public readonly name = 'DenominationAssetNotRegisteredError'; 20 | constructor(message: string = 'Denomination asset must be registered.') { 21 | super(message); 22 | } 23 | } 24 | export class FundSetupAlreadyCompleteError extends ValidationError { 25 | public readonly name = 'FundSetupAlreadyCompleteError'; 26 | constructor(message: string = 'Setup already complete.') { 27 | super(message); 28 | } 29 | } 30 | 31 | export class FundSetupAlreadyStartedError extends ValidationError { 32 | public readonly name = 'FundSetupAlreadyStartedError'; 33 | constructor(message: string = 'Fund setup has already been started.') { 34 | super(message); 35 | } 36 | } 37 | 38 | export class DifferentNumberOfExchangesAndAdaptersError extends ValidationError { 39 | public readonly name = 'DifferentNumberOfExchangesAndAdaptersError'; 40 | constructor(message: string = 'Route has already been created.') { 41 | super(message); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/contracts/factory/FundFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { createTestEnvironment, TestEnvironment } from '../../utils/tests/createTestEnvironment'; 2 | 3 | import { randomAddress } from '../../utils/tests/randomAddress'; 4 | import { FundFactoryBytecode } from '../../abis/FundFactory.bin'; 5 | import { FundFactory } from './FundFactory'; 6 | 7 | describe('FundFactory', () => { 8 | let environment: TestEnvironment; 9 | let fundFactory: FundFactory; 10 | 11 | beforeAll(async () => { 12 | environment = await createTestEnvironment(); 13 | 14 | const deploy = FundFactory.deploy(environment, FundFactoryBytecode, environment.accounts[0], { 15 | accountingFactory: randomAddress(), 16 | feeManagerFactory: randomAddress(), 17 | participationFactory: randomAddress(), 18 | policyManagerFactory: randomAddress(), 19 | sharesFactory: randomAddress(), 20 | tradingFactory: randomAddress(), 21 | vaultFactory: randomAddress(), 22 | version: randomAddress(), 23 | }); 24 | fundFactory = await deploy.send(await deploy.prepare()); 25 | }); 26 | 27 | it('should check if a contract is an instance of a factory', async () => { 28 | const result = fundFactory.contract.address; 29 | expect(result.startsWith('0x')).toBe(true); 30 | }); 31 | 32 | it('should return the address of the version', async () => { 33 | const result = await fundFactory.getVersion(); 34 | expect(result.startsWith('0x')).toBe(true); 35 | }); 36 | 37 | it('should return the address of the registry', async () => { 38 | const result = await fundFactory.getRegistry(); 39 | expect(result.startsWith('0x')).toBe(true); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/contracts/fund/accounting/AccountingFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { createTestEnvironment, TestEnvironment } from '../../../utils/tests/createTestEnvironment'; 2 | 3 | import { AccountingFactory } from './AccountingFactory'; 4 | import { randomAddress } from '../../../utils/tests/randomAddress'; 5 | import { AccountingFactoryBytecode } from '../../../abis/AccountingFactory.bin'; 6 | import { deployHub } from '../../../utils/tests/deployHub'; 7 | import { deployWeth } from '../../../utils/tests/deployWeth'; 8 | 9 | describe('AccountingFactory', () => { 10 | let environment: TestEnvironment; 11 | let accountingFactory: AccountingFactory; 12 | 13 | beforeAll(async () => { 14 | environment = await createTestEnvironment(); 15 | 16 | const deploy = AccountingFactory.deploy(environment, AccountingFactoryBytecode, environment.accounts[0]); 17 | 18 | accountingFactory = await deploy.send(await deploy.prepare()); 19 | }); 20 | 21 | it('should check if a contract is an instance of AccountingFactory', async () => { 22 | const result = await accountingFactory.isInstance(randomAddress()); 23 | expect(typeof result).toBe('boolean'); 24 | }); 25 | 26 | it('should create an instance of an accounting contract', async () => { 27 | const hub = await deployHub(environment, environment.accounts[0], { 28 | manager: environment.accounts[1], 29 | name: 'accounting-test-fund', 30 | }); 31 | 32 | const weth = await deployWeth(environment, environment.accounts[0]); 33 | const tx = accountingFactory.createInstance(environment.accounts[0], { 34 | hub: hub.contract.address, 35 | denominationAsset: weth.contract.address, 36 | nativeAsset: weth.contract.address, 37 | }); 38 | 39 | const txResult = await tx.send(await tx.prepare()); 40 | expect(txResult.gasUsed).toBeGreaterThanOrEqual(0); 41 | expect(txResult.status).toBe(true); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/contracts/fund/accounting/AccountingFactory.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../Contract'; 2 | import { Environment } from '../../../Environment'; 3 | import { Address } from '../../../Address'; 4 | import { applyMixins } from '../../../utils/applyMixins'; 5 | import { AccountingFactoryAbi } from '../../../abis/AccountingFactory.abi'; 6 | import { Factory } from '../../factory/Factory'; 7 | import { AccountingDeployArguments } from './Accounting'; 8 | 9 | export class AccountingFactory extends Contract { 10 | public static readonly abi = AccountingFactoryAbi; 11 | 12 | public static deploy(environment: Environment, bytecode: string, from: Address) { 13 | return super.createDeployment(environment, bytecode, from); 14 | } 15 | 16 | /** 17 | * Creates an Accounting instance. 18 | * 19 | * @param from The sender address. 20 | * @param args The accounting deploy arguments as [[AccountingDeployArguments]]. 21 | */ 22 | public createInstance(from: Address, deployArgs: AccountingDeployArguments) { 23 | const args = [deployArgs.hub, deployArgs.denominationAsset, deployArgs.nativeAsset]; 24 | return this.createTransaction({ from, method: 'createInstance', args }); 25 | } 26 | } 27 | 28 | export interface AccountingFactory extends Factory {} 29 | applyMixins(AccountingFactory, [Factory]); 30 | -------------------------------------------------------------------------------- /src/contracts/fund/fees/FeeManagerFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { createTestEnvironment, TestEnvironment } from '../../../utils/tests/createTestEnvironment'; 2 | 3 | import { randomAddress } from '../../../utils/tests/randomAddress'; 4 | import { deployHub } from '../../../utils/tests/deployHub'; 5 | import { deployWeth } from '../../../utils/tests/deployWeth'; 6 | import { FeeManagerFactory } from './FeeManagerFactory'; 7 | import { FeeManagerFactoryBytecode } from '../../../abis/FeeManagerFactory.bin'; 8 | import { deployManagementFee } from '../../../utils/tests/deployManagementFee'; 9 | import { deployPerformanceFee } from '../../../utils/tests/deployPerformanceFee'; 10 | import { deployRegistry } from '../../../utils/tests/deployRegistry'; 11 | import BigNumber from 'bignumber.js'; 12 | 13 | describe('FeeManagerFactory', () => { 14 | let environment: TestEnvironment; 15 | let feeManagerFactory: FeeManagerFactory; 16 | 17 | beforeAll(async () => { 18 | environment = await createTestEnvironment(); 19 | 20 | const deploy = FeeManagerFactory.deploy(environment, FeeManagerFactoryBytecode, environment.accounts[0]); 21 | feeManagerFactory = await deploy.send(await deploy.prepare()); 22 | }); 23 | 24 | it('should check if a contract is an instance of the FeeManagerFactory', async () => { 25 | const result = await feeManagerFactory.isInstance(randomAddress()); 26 | expect(typeof result).toBe('boolean'); 27 | }); 28 | 29 | it('should create an instance of a FeeManager contract', async () => { 30 | const hub = await deployHub(environment, environment.accounts[0], { 31 | name: 'feemanager-test-fund', 32 | manager: environment.accounts[0], 33 | }); 34 | const weth = await deployWeth(environment, environment.accounts[0]); 35 | const managementFee = await deployManagementFee(environment, environment.accounts[0]); 36 | const performanceFee = await deployPerformanceFee(environment, environment.accounts[0]); 37 | 38 | const registry = await deployRegistry(environment, environment.accounts[0], environment.accounts[0]); 39 | 40 | const txRegisterFees = registry.registerFees(environment.accounts[0], [ 41 | managementFee.contract.address, 42 | performanceFee.contract.address, 43 | ]); 44 | await txRegisterFees.send(await txRegisterFees.prepare()); 45 | 46 | const tx = feeManagerFactory.createInstance(environment.accounts[0], { 47 | hub: hub.contract.address, 48 | denominationAsset: weth.contract.address, 49 | fees: [managementFee.contract.address, performanceFee.contract.address], 50 | rates: [new BigNumber('100000'), new BigNumber('2000000')], 51 | periods: [1000, 2000], 52 | registry: registry.contract.address, 53 | }); 54 | 55 | const txResult = await tx.send(await tx.prepare()); 56 | expect(txResult.gasUsed).toBeGreaterThanOrEqual(0); 57 | expect(txResult.status).toBe(true); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /src/contracts/fund/fees/FeeManagerFactory.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../Contract'; 2 | import { Environment } from '../../../Environment'; 3 | import { Address } from '../../../Address'; 4 | import { applyMixins } from '../../../utils/applyMixins'; 5 | import { Factory } from '../../factory/Factory'; 6 | import { FeeManagerFactoryAbi } from '../../../abis/FeeManagerFactory.abi'; 7 | import { FeeManagerDeployArguments } from './FeeManager'; 8 | 9 | export class FeeManagerFactory extends Contract { 10 | public static readonly abi = FeeManagerFactoryAbi; 11 | 12 | public static deploy(environment: Environment, bytecode: string, from: Address) { 13 | return super.createDeployment(environment, bytecode, from); 14 | } 15 | 16 | /** 17 | * Creates a FeeManager instance. 18 | * 19 | * @param from The sender address. 20 | * @param args The FeeManager deploy arguments as [[FeeManagerDeployArguments]]. 21 | */ 22 | public createInstance(from: Address, deployArgs: FeeManagerDeployArguments) { 23 | const args = [ 24 | deployArgs.hub, 25 | deployArgs.denominationAsset, 26 | deployArgs.fees, 27 | deployArgs.rates.map((fee) => fee.toFixed(0)), 28 | deployArgs.periods, 29 | deployArgs.registry, 30 | ]; 31 | 32 | return this.createTransaction({ from, method: 'createInstance', args }); 33 | } 34 | } 35 | 36 | export interface FeeManagerFactory extends Factory {} 37 | applyMixins(FeeManagerFactory, [Factory]); 38 | -------------------------------------------------------------------------------- /src/contracts/fund/fees/IFee.test.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { TestEnvironment, createTestEnvironment } from '../../../utils/tests/createTestEnvironment'; 3 | import { deployManagementFee } from '../../../utils/tests/deployManagementFee'; 4 | import { randomAddress } from '../../../utils/tests/randomAddress'; 5 | import { IFee, FeeAlreadyInitializedError } from './IFee'; 6 | 7 | describe('FeeManager', () => { 8 | let environment: TestEnvironment; 9 | let fee: IFee; 10 | 11 | beforeAll(async () => { 12 | environment = await createTestEnvironment(); 13 | 14 | fee = await deployManagementFee(environment, environment.accounts[0]); 15 | }); 16 | 17 | it('should return the correct identifier', async () => { 18 | const result = await fee.identifier(); 19 | expect(result).toBeGreaterThanOrEqual(0); 20 | }); 21 | 22 | it('should initialize the fees for a user', async () => { 23 | { 24 | const tx = fee.initializeForUser(environment.accounts[0], { 25 | feeRate: new BigNumber(1000), 26 | feePeriod: 1000, 27 | denominationAsset: randomAddress(), 28 | }); 29 | const txResult = await tx.send(await tx.prepare()); 30 | expect(txResult.gasUsed).toBeGreaterThan(0); 31 | } 32 | 33 | { 34 | const tx = fee.updateState(environment.accounts[0]); 35 | const txResult = await tx.send(await tx.prepare()); 36 | expect(txResult.gasUsed).toBeGreaterThan(0); 37 | } 38 | }); 39 | 40 | it('should throw FeeAlreadyInitializedError', async () => { 41 | const tx = fee.initializeForUser('', { 42 | feeRate: new BigNumber(0), 43 | feePeriod: 0, 44 | denominationAsset: '', 45 | }); 46 | 47 | jest.spyOn(fee, 'getLastPayoutTime').mockReturnValue(new Promise((resolve) => resolve(new Date(1)))); 48 | 49 | await expect(tx.validate()).rejects.toThrowError(FeeAlreadyInitializedError); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /src/contracts/fund/fees/ManagementFee.test.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { ManagementFee } from './ManagementFee'; 3 | import { FeeAlreadyInitializedError } from './IFee'; 4 | import { TestEnvironment, createTestEnvironment } from '../../../utils/tests/createTestEnvironment'; 5 | import { deployManagementFee } from '../../../utils/tests/deployManagementFee'; 6 | import { randomAddress } from '../../../utils/tests/randomAddress'; 7 | 8 | describe('FeeManager', () => { 9 | let environment: TestEnvironment; 10 | let managementFee: ManagementFee; 11 | 12 | beforeAll(async () => { 13 | environment = await createTestEnvironment(); 14 | managementFee = await deployManagementFee(environment, environment.accounts[0]); 15 | }); 16 | 17 | it('should set the management fee parameters', async () => { 18 | const tx = managementFee.initializeForUser(environment.accounts[0], { 19 | feeRate: new BigNumber('100000000'), 20 | feePeriod: 1000, 21 | denominationAsset: randomAddress(), 22 | }); 23 | const txResult = await tx.send(await tx.prepare()); 24 | expect(txResult.gasUsed).toBeGreaterThan(0); 25 | }); 26 | 27 | it('should throw FeeAlreadyInitializedError', async () => { 28 | const tx = managementFee.initializeForUser('', { 29 | feeRate: new BigNumber(0), 30 | feePeriod: 0, 31 | denominationAsset: '', 32 | }); 33 | 34 | jest.spyOn(managementFee, 'getLastPayoutTime').mockReturnValue(new Promise((resolve) => resolve(new Date(1)))); 35 | 36 | await expect(tx.validate()).rejects.toThrowError(FeeAlreadyInitializedError); 37 | }); 38 | 39 | it('should get the management fee rate', async () => { 40 | const result = await managementFee.getManagementFeeRate(environment.accounts[0]); 41 | expect(result.isGreaterThanOrEqualTo(0)).toBe(true); 42 | }); 43 | 44 | it('should get the last payout time', async () => { 45 | const result = await managementFee.getLastPayoutTime(environment.accounts[0]); 46 | expect(result.getTime() >= 0).toBe(true); 47 | }); 48 | 49 | it('should return the correct identifier', async () => { 50 | const result = await managementFee.identifier(); 51 | expect(result).toBe(0); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/contracts/fund/fees/ManagementFee.ts: -------------------------------------------------------------------------------- 1 | import { IFee } from './IFee'; 2 | import { Environment } from '../../../Environment'; 3 | import { Address } from '../../../Address'; 4 | import { ManagementFeeAbi } from '../../../abis/ManagementFee.abi'; 5 | import { toBigNumber } from '../../../utils/toBigNumber'; 6 | 7 | export class ManagementFee extends IFee { 8 | public static readonly abi = ManagementFeeAbi; 9 | 10 | public static deploy(environment: Environment, bytecode: string, from: Address) { 11 | return super.createDeployment(environment, bytecode, from); 12 | } 13 | 14 | /** 15 | * Gets the management fee rate. 16 | * 17 | * @param feeManagerAddress The address of the fee manager contract 18 | * @param block The block number to execute the call on. 19 | */ 20 | public async getManagementFeeRate(feeManagerAddress: Address, block?: number) { 21 | const result = await this.makeCall('managementFeeRate', [feeManagerAddress], block); 22 | return toBigNumber(result); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/contracts/fund/fees/PerformanceFee.test.ts: -------------------------------------------------------------------------------- 1 | import { PerformanceFee } from './PerformanceFee'; 2 | import { FeeAlreadyInitializedError } from './IFee'; 3 | import { createTestEnvironment, TestEnvironment } from '../../../utils/tests/createTestEnvironment'; 4 | import { deployPerformanceFee } from '../../../utils/tests/deployPerformanceFee'; 5 | import { BigNumber } from 'bignumber.js'; 6 | import { deployWeth } from '../../../utils/tests/deployWeth'; 7 | import { randomAddress } from '../../../utils/tests/randomAddress'; 8 | 9 | describe('FeeManager', () => { 10 | let environment: TestEnvironment; 11 | let performanceFee: PerformanceFee; 12 | 13 | beforeAll(async () => { 14 | environment = await createTestEnvironment(); 15 | performanceFee = await deployPerformanceFee(environment, environment.accounts[0]); 16 | }); 17 | 18 | it('should set the performance fee parameters', async () => { 19 | const weth = await deployWeth(environment, environment.accounts[0]); 20 | 21 | const tx = performanceFee.initializeForUser(environment.accounts[0], { 22 | feeRate: new BigNumber('100000000'), 23 | feePeriod: 1000, 24 | denominationAsset: weth.contract.address, 25 | }); 26 | const txResult = await tx.send(await tx.prepare()); 27 | expect(txResult.gasUsed).toBeGreaterThan(0); 28 | }); 29 | 30 | it('should throw FeeAlreadyInitializedError', async () => { 31 | const tx = performanceFee.initializeForUser('', { 32 | feeRate: new BigNumber(0), 33 | feePeriod: 0, 34 | denominationAsset: '', 35 | }); 36 | 37 | jest.spyOn(performanceFee, 'getLastPayoutTime').mockReturnValue(new Promise((resolve) => resolve(new Date(1)))); 38 | 39 | await expect(tx.validate()).rejects.toThrowError(FeeAlreadyInitializedError); 40 | }); 41 | 42 | it('should get the performance fee rate', async () => { 43 | const result = await performanceFee.getPerformanceFeeRate(environment.accounts[0]); 44 | expect(result.isGreaterThanOrEqualTo(0)).toBe(true); 45 | }); 46 | 47 | it('should get the last payout time', async () => { 48 | jest.spyOn(performanceFee, 'getLastPayoutTime').mockReturnValue(new Promise((resolve) => resolve(new Date(1)))); 49 | 50 | const result = await performanceFee.getLastPayoutTime(randomAddress()); 51 | expect(result.getTime() === 1).toBe(true); 52 | }); 53 | 54 | it('should return the correct identifier', async () => { 55 | const result = await performanceFee.identifier(); 56 | expect(result).toBe(1); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/contracts/fund/fees/PerformanceFee.ts: -------------------------------------------------------------------------------- 1 | import { Environment } from '../../../Environment'; 2 | import { Address } from '../../../Address'; 3 | import { PerformanceFeeAbi } from '../../../abis/PerformanceFee.abi'; 4 | import { toBigNumber } from '../../../utils/toBigNumber'; 5 | import { IFee } from './IFee'; 6 | import { toDate } from '../../../utils/toDate'; 7 | 8 | export class PerformanceFee extends IFee { 9 | public static readonly abi = PerformanceFeeAbi; 10 | 11 | public static deploy(environment: Environment, bytecode: string, from: Address) { 12 | return super.createDeployment(environment, bytecode, from); 13 | } 14 | 15 | /** 16 | * Gets the performance fee rate. 17 | * 18 | * @param address The address of the fee manager contract 19 | * @param block The block number to execute the call on. 20 | */ 21 | public async getPerformanceFeeRate(address: Address, block?: number) { 22 | const result = await this.makeCall('performanceFeeRate', [address], block); 23 | return toBigNumber(result); 24 | } 25 | 26 | /** 27 | * Gets the performance fee period. 28 | * 29 | * @param address The address of the fee manager contract 30 | * @param block The block number to execute the call on. 31 | */ 32 | public async getPerformanceFeePeriod(address: Address, block?: number) { 33 | const result = await this.makeCall('performanceFeePeriod', [address], block); 34 | return parseInt(result, 10); 35 | } 36 | 37 | /** 38 | * Gets the current high water mark 39 | * 40 | * @param address The address of the fee manager contract 41 | * @param block The block number to execute the call on. 42 | */ 43 | public async getHighWaterMark(address: Address, block?: number) { 44 | const result = await this.makeCall('highWaterMark', [address], block); 45 | return toBigNumber(result); 46 | } 47 | 48 | /** 49 | * Gets the initialize time 50 | * 51 | * @param address The address of the fee manager contract 52 | * @param block The block number to execute the call on. 53 | */ 54 | public async getInitializeTime(address: Address, block?: number) { 55 | const result = await this.makeCall('initializeTime', [address], block); 56 | return toDate(result); 57 | } 58 | 59 | /** 60 | * Checks wether the performance fee can be updated 61 | * 62 | * @param address The address of the fee manager contract 63 | * @param block The block number to execute the call on. 64 | */ 65 | public canUpdate(address: Address, block?: number) { 66 | return this.makeCall('canUpdate', [address], block); 67 | } 68 | 69 | /** 70 | * Gets the divisor. 71 | * 72 | * @param block The block number to execute the call on. 73 | */ 74 | public async getDivisor(block?: number) { 75 | const result = await this.makeCall('DIVISOR', undefined, block); 76 | return toBigNumber(result); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/contracts/fund/hub/Hub.test.ts: -------------------------------------------------------------------------------- 1 | import { Hub } from './Hub'; 2 | import { sameAddress } from '../../../utils/sameAddress'; 3 | import { createTestEnvironment, TestEnvironment } from '../../../utils/tests/createTestEnvironment'; 4 | import { deployHub } from '../../../utils/tests/deployHub'; 5 | 6 | describe('Hub', () => { 7 | let hub: Hub; 8 | let environment: TestEnvironment; 9 | 10 | beforeAll(async () => { 11 | environment = await createTestEnvironment(); 12 | hub = await deployHub(environment, environment.accounts[0], { 13 | manager: environment.accounts[1], 14 | name: 'hub-test-fund', 15 | }); 16 | }); 17 | 18 | it('should return the correct fund name', async () => { 19 | const result = await hub.getName(); 20 | expect(result).toBe('hub-test-fund'); 21 | }); 22 | 23 | it('should return the correct manager address', async () => { 24 | const result = await hub.getManager(); 25 | expect(sameAddress(result, environment.accounts[1])).toBe(true); 26 | }); 27 | 28 | it('should return the creation time', async () => { 29 | const result = await hub.getCreationTime(); 30 | expect(result).toBeInstanceOf(Date); 31 | expect(result.getTime()).toBeLessThan(Date.now()); 32 | }); 33 | 34 | it('should return the address of the creator', async () => { 35 | const result = await hub.getCreator(); 36 | expect(sameAddress(result, environment.accounts[0])).toBe(true); 37 | }); 38 | 39 | it('should return whether or not a fund is shutdown', async () => { 40 | const result = await hub.isShutDown(); 41 | expect(result === true || result === false).toBe(true); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/contracts/fund/hub/Spoke.test.ts: -------------------------------------------------------------------------------- 1 | import { Spoke } from './Spoke'; 2 | import { TestEnvironment, createTestEnvironment } from '../../../utils/tests/createTestEnvironment'; 3 | import { deploySpoke } from '../../../utils/tests/deploySpoke'; 4 | import { deployHub } from '../../../utils/tests/deployHub'; 5 | import { Hub } from './Hub'; 6 | import { sameAddress } from '../../../utils/sameAddress'; 7 | 8 | describe('Spoke', () => { 9 | let environment: TestEnvironment; 10 | let spoke: Spoke; 11 | let hub: Hub; 12 | 13 | beforeAll(async () => { 14 | environment = await createTestEnvironment(); 15 | hub = await deployHub(environment, environment.accounts[0], { 16 | name: 'spoke-test-fund', 17 | manager: environment.accounts[1], 18 | }); 19 | spoke = await deploySpoke(environment, environment.accounts[0], hub.contract.address); 20 | }); 21 | 22 | it('should return the address of the engine', async () => { 23 | const result = await spoke.getEngine(); 24 | expect(result.startsWith('0x')).toBe(true); 25 | }); 26 | 27 | it('should return the address of the MLN token', async () => { 28 | const result = await spoke.getMlnToken(); 29 | expect(result.startsWith('0x')).toBe(true); 30 | }); 31 | 32 | it('should return the address of the version', async () => { 33 | const result = await spoke.getVersion(); 34 | expect(result.startsWith('0x')).toBe(true); 35 | }); 36 | 37 | it('should return the address of the registry', async () => { 38 | const result = await spoke.getRegistry(); 39 | expect(result.startsWith('0x')).toBe(true); 40 | }); 41 | 42 | it('should return the address of the hub', async () => { 43 | const result = await spoke.getHub(); 44 | expect(sameAddress(result, hub.contract.address)).toBe(true); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/contracts/fund/participation/ParticipationFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { createTestEnvironment, TestEnvironment } from '../../../utils/tests/createTestEnvironment'; 2 | 3 | import { randomAddress } from '../../../utils/tests/randomAddress'; 4 | import { deployHub } from '../../../utils/tests/deployHub'; 5 | import { deployWeth } from '../../../utils/tests/deployWeth'; 6 | import { deployRegistry } from '../../../utils/tests/deployRegistry'; 7 | import BigNumber from 'bignumber.js'; 8 | import { ParticipationFactory } from './ParticipationFactory'; 9 | import { ParticipationFactoryBytecode } from '../../../abis/ParticipationFactory.bin'; 10 | 11 | describe('ParticipationFactory', () => { 12 | let environment: TestEnvironment; 13 | let participationFactory: ParticipationFactory; 14 | 15 | beforeAll(async () => { 16 | environment = await createTestEnvironment(); 17 | 18 | const deploy = ParticipationFactory.deploy(environment, ParticipationFactoryBytecode, environment.accounts[0]); 19 | participationFactory = await deploy.send(await deploy.prepare()); 20 | }); 21 | 22 | it('should check if a contract is an instance of the ParticipationFactory', async () => { 23 | const result = await participationFactory.isInstance(randomAddress()); 24 | expect(typeof result).toBe('boolean'); 25 | }); 26 | 27 | it('should create an instance of a Participation contract', async () => { 28 | const hub = await deployHub(environment, environment.accounts[0], { 29 | manager: environment.accounts[1], 30 | name: 'accounting-test-fund', 31 | }); 32 | 33 | const weth = await deployWeth(environment, environment.accounts[0]); 34 | 35 | const registry = await deployRegistry(environment, environment.accounts[0], environment.accounts[0]); 36 | 37 | const txRegisterAsset = await registry.registerAsset(environment.accounts[0], { 38 | address: weth.contract.address, 39 | name: 'Test Asset', 40 | symbol: 'TAT', 41 | url: 'https://tat.tat/', 42 | reserveMin: new BigNumber(100000), 43 | standards: [1, 2, 3], 44 | sigs: ['0x30303030'], 45 | }); 46 | await txRegisterAsset.send(await txRegisterAsset.prepare()); 47 | 48 | const tx = participationFactory.createInstance(environment.accounts[0], { 49 | hub: hub.contract.address, 50 | defaultAssets: [weth.contract.address], 51 | registry: registry.contract.address, 52 | }); 53 | 54 | const txResult = await tx.send(await tx.prepare()); 55 | expect(txResult.gasUsed).toBeGreaterThanOrEqual(0); 56 | expect(txResult.status).toBe(true); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/contracts/fund/participation/ParticipationFactory.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../Contract'; 2 | import { Environment } from '../../../Environment'; 3 | import { Address } from '../../../Address'; 4 | import { applyMixins } from '../../../utils/applyMixins'; 5 | import { Factory } from '../../factory/Factory'; 6 | import { ParticipationFactoryAbi } from '../../../abis/ParticipationFactory.abi'; 7 | import { ParticipationDeployArguments } from './Participation'; 8 | 9 | export class ParticipationFactory extends Contract { 10 | public static readonly abi = ParticipationFactoryAbi; 11 | 12 | public static deploy(environment: Environment, bytecode: string, from: Address) { 13 | return super.createDeployment(environment, bytecode, from); 14 | } 15 | 16 | /** 17 | * Creates a ParticipationFactory instance. 18 | * 19 | * @param from The sender address. 20 | * @param args The participation deploy arguments as [[ParticipationDeployArguments]]. 21 | */ 22 | public createInstance(from: Address, deployArgs: ParticipationDeployArguments) { 23 | const args = [deployArgs.hub, deployArgs.defaultAssets, deployArgs.registry]; 24 | return this.createTransaction({ from, method: 'createInstance', args }); 25 | } 26 | } 27 | 28 | export interface ParticipationFactory extends Factory {} 29 | applyMixins(ParticipationFactory, [Factory]); 30 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/AddressList.test.ts: -------------------------------------------------------------------------------- 1 | import { AddressList } from './AddressList'; 2 | import { TestEnvironment, createTestEnvironment } from '../../../utils/tests/createTestEnvironment'; 3 | import { AddressListBytecode } from '../../../abis/AddressList.bin'; 4 | import { randomAddress } from '../../../utils/tests/randomAddress'; 5 | import { sameAddress } from '../../../utils/sameAddress'; 6 | import { range } from '../../../utils/range'; 7 | 8 | describe('AddressList', () => { 9 | let environment: TestEnvironment; 10 | let addressList: AddressList; 11 | const addresses = range(5).map(() => randomAddress()); 12 | 13 | beforeAll(async () => { 14 | environment = await createTestEnvironment(); 15 | 16 | const deploy = AddressList.deploy(environment, AddressListBytecode, environment.accounts[0], addresses); 17 | addressList = await deploy.send(await deploy.prepare()); 18 | }); 19 | 20 | it('should check if an address is member of an address list', async () => { 21 | const result = await addressList.isMember(addresses[1]); 22 | expect(result === true || result === false).toBe(true); 23 | }); 24 | 25 | it('should return the correct number of members', async () => { 26 | const result = await addressList.getMemberCount(); 27 | expect(result.isEqualTo(addresses.length)).toBe(true); 28 | }); 29 | 30 | it('should return a list of members', async () => { 31 | const result = await addressList.getMembers(); 32 | result.map((address) => { 33 | expect(addresses.some((a) => sameAddress(a, address))).toBe(true); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/AddressList.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../Contract'; 2 | import { Address } from '../../../Address'; 3 | import { AddressListAbi } from '../../../abis/AddressList.abi'; 4 | import { Environment } from '../../../Environment'; 5 | import { toBigNumber } from '../../../utils/toBigNumber'; 6 | import { ValidationError } from '../../../errors/ValidationError'; 7 | 8 | export class IsNotMemberError extends ValidationError { 9 | public readonly name = 'IsNotMemberError'; 10 | 11 | constructor(public readonly address: Address, message: string = 'Address is not member of AddressList') { 12 | super(message); 13 | } 14 | } 15 | 16 | export class IsAlreadyMemberError extends ValidationError { 17 | public readonly name = 'IsAlreadyMemberError'; 18 | 19 | constructor(public readonly address: Address, message: string = 'Address is already member of AddressList') { 20 | super(message); 21 | } 22 | } 23 | 24 | export class AddressList extends Contract { 25 | public static readonly abi = AddressListAbi; 26 | 27 | public static deploy(environment: Environment, bytecode: string, from: Address, members: Address[]) { 28 | return super.createDeployment(environment, bytecode, from, [members]); 29 | } 30 | 31 | /** 32 | * Checks if an address is part of an address list. 33 | * 34 | * @param address The address to check 35 | * @param block The block number to execute the call on. 36 | */ 37 | public isMember(address: Address, block?: number) { 38 | return this.makeCall('isMember', [address], block); 39 | } 40 | 41 | /** 42 | * Gets the number of members of an address list. 43 | * 44 | * @param address The address to check 45 | * @param block The block number to execute the call on. 46 | */ 47 | public async getMemberCount(block?: number) { 48 | const result = await this.makeCall('getMemberCount', undefined, block); 49 | return toBigNumber(result); 50 | } 51 | 52 | /** 53 | * Gets all members of an address list. 54 | * 55 | * @param address The address to check 56 | * @param block The block number to execute the call on. 57 | */ 58 | public getMembers(block?: number) { 59 | return this.makeCall('getMembers', undefined, block); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/AssetBlacklist.test.ts: -------------------------------------------------------------------------------- 1 | import { AssetBlacklist } from './AssetBlacklist'; 2 | import { TestEnvironment, createTestEnvironment } from '../../../utils/tests/createTestEnvironment'; 3 | import { randomAddress } from '../../../utils/tests/randomAddress'; 4 | import { AssetBlacklistBytecode } from '../../../abis/AssetBlacklist.bin'; 5 | import { range } from '../../../utils/range'; 6 | 7 | describe('AssetBlacklist', () => { 8 | let environment: TestEnvironment; 9 | let assetBlacklist: AssetBlacklist; 10 | const addresses = range(5).map(() => randomAddress()); 11 | 12 | beforeAll(async () => { 13 | environment = await createTestEnvironment(); 14 | 15 | const deploy = AssetBlacklist.deploy(environment, AssetBlacklistBytecode, environment.accounts[0], addresses); 16 | assetBlacklist = await deploy.send(await deploy.prepare()); 17 | }); 18 | 19 | it('should return the correct position', async () => { 20 | const result = await assetBlacklist.getPosition(); 21 | expect(result).toBe(0); 22 | }); 23 | 24 | it('should return the correct identifier', async () => { 25 | const result = await assetBlacklist.getIdentifier(); 26 | expect(result).toBe('AssetBlacklist'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/AssetBlacklist.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../Contract'; 2 | import { applyMixins } from '../../../utils/applyMixins'; 3 | import { IPolicy } from './IPolicy'; 4 | import { AddressList, IsAlreadyMemberError } from './AddressList'; 5 | import { AssetBlacklistAbi } from '../../../abis/AssetBlacklist.abi'; 6 | import { Environment } from '../../../Environment'; 7 | import { Address } from '../../../Address'; 8 | 9 | export class AssetBlacklist extends Contract { 10 | public static readonly abi = AssetBlacklistAbi; 11 | 12 | public static deploy(environment: Environment, bytecode: string, from: Address, members: Address[]) { 13 | return super.createDeployment(environment, bytecode, from, [members]); 14 | } 15 | 16 | /** 17 | * Adds a list of assets to an AssetBlacklist. 18 | * 19 | * @param from The address of the sender 20 | * @param asset The address of the asset to be added to the AssetBlacklist 21 | */ 22 | public addToBlacklist(from: Address, asset: Address) { 23 | const validate = async () => { 24 | if (await this.isMember(asset)) { 25 | throw new IsAlreadyMemberError(asset); 26 | } 27 | }; 28 | 29 | return this.createTransaction({ from, method: 'addToBlacklist', args: [asset], validate }); 30 | } 31 | } 32 | 33 | export interface AssetBlacklist extends IPolicy, AddressList {} 34 | applyMixins(AssetBlacklist, [IPolicy, AddressList]); 35 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/AssetWhitelist.test.ts: -------------------------------------------------------------------------------- 1 | import { AssetWhitelist } from './AssetWhitelist'; 2 | import { TestEnvironment, createTestEnvironment } from '../../../utils/tests/createTestEnvironment'; 3 | import { randomAddress } from '../../../utils/tests/randomAddress'; 4 | import { AssetWhitelistBytecode } from '../../../abis/AssetWhitelist.bin'; 5 | import { range } from '../../../utils/range'; 6 | 7 | describe('AssetWhitelist', () => { 8 | let environment: TestEnvironment; 9 | let assetWhitelist: AssetWhitelist; 10 | const addresses = range(5).map(() => randomAddress()); 11 | 12 | beforeAll(async () => { 13 | environment = await createTestEnvironment(); 14 | 15 | const deploy = AssetWhitelist.deploy(environment, AssetWhitelistBytecode, environment.accounts[0], addresses); 16 | assetWhitelist = await deploy.send(await deploy.prepare()); 17 | }); 18 | 19 | it('should return the correct position', async () => { 20 | const result = await assetWhitelist.getPosition(); 21 | expect(result).toBe(0); 22 | }); 23 | 24 | it('should return the correct identifier', async () => { 25 | const result = await assetWhitelist.getIdentifier(); 26 | expect(result).toBe('AssetWhitelist'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/AssetWhitelist.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../Contract'; 2 | import { applyMixins } from '../../../utils/applyMixins'; 3 | import { IPolicy } from './IPolicy'; 4 | import { AddressList, IsNotMemberError } from './AddressList'; 5 | import { AssetWhitelistAbi } from '../../../abis/AssetWhitelist.abi'; 6 | import { Environment } from '../../../Environment'; 7 | import { Address } from '../../../Address'; 8 | 9 | export class AssetWhitelist extends Contract { 10 | public static readonly abi = AssetWhitelistAbi; 11 | 12 | public static deploy(environment: Environment, bytecode: string, from: Address, members: Address[]) { 13 | return super.createDeployment(environment, bytecode, from, [members]); 14 | } 15 | 16 | /** 17 | * Removes an asset from an AssetWhitelist. 18 | * 19 | * @param from The address of the sender 20 | * @param asset The asset address to be removed from the AssetWhitelist 21 | */ 22 | public removeFromWhitelist(from: Address, asset: Address) { 23 | const validate = async () => { 24 | if (!(await this.isMember(asset))) { 25 | throw new IsNotMemberError(asset); 26 | } 27 | }; 28 | 29 | return this.createTransaction({ from, method: 'removeFromWhitelist', args: [asset], validate }); 30 | } 31 | 32 | /** 33 | * Gets the index of an asset. 34 | * 35 | * @param asset The address of the asset 36 | * @param block The block number to execute the call on. 37 | */ 38 | public async getAssetIndex(asset: Address, block?: number) { 39 | const result = await this.makeCall('getAssetIndex', [asset], block); 40 | return parseInt(result, 10); 41 | } 42 | } 43 | 44 | export interface AssetWhitelist extends IPolicy, AddressList {} 45 | applyMixins(AssetWhitelist, [IPolicy, AddressList]); 46 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/IPolicy.test.ts: -------------------------------------------------------------------------------- 1 | import { TestEnvironment, createTestEnvironment } from '../../../utils/tests/createTestEnvironment'; 2 | import { MaxPositions } from './MaxPositions'; 3 | import { MaxPositionsBytecode } from '../../../abis/MaxPositions.bin'; 4 | 5 | describe('Policy', () => { 6 | let environment: TestEnvironment; 7 | let maxPositions: MaxPositions; 8 | const max: number = 10; 9 | 10 | beforeAll(async () => { 11 | environment = await createTestEnvironment(); 12 | 13 | // Policy itself cannot be deployed (empty bytecode, it's really just an interface) 14 | const deploy = MaxPositions.deploy(environment, MaxPositionsBytecode, environment.accounts[0], max); 15 | maxPositions = await deploy.send(await deploy.prepare()); 16 | }); 17 | 18 | it('should return the identifier of a policy', async () => { 19 | const result = await maxPositions.getIdentifier(); 20 | expect(result).toBe('MaxPositions'); 21 | }); 22 | 23 | it('should return the position of a policy', async () => { 24 | const result = await maxPositions.getPosition(); 25 | expect(result).toBe(1); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/IPolicy.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../Contract'; 2 | import { IPolicyAbi } from '../../../abis/IPolicy.abi'; 3 | import BigNumber from 'bignumber.js'; 4 | import { Address } from '../../../Address'; 5 | import { hexToBytes } from 'web3-utils'; 6 | 7 | export interface PolicyArgs { 8 | signature: string; 9 | addresses: [Address, Address, Address, Address, Address]; 10 | values: [BigNumber, BigNumber, BigNumber]; 11 | identifier: string; 12 | } 13 | 14 | export class IPolicy extends Contract { 15 | public static readonly abi = IPolicyAbi; 16 | 17 | /** 18 | * Gets the identifier of a policy. 19 | * 20 | * @param block The block number to execute the call on. 21 | */ 22 | public getIdentifier(block?: number) { 23 | return this.makeCall('identifier', undefined, block); 24 | } 25 | 26 | /** 27 | * Gets the position of a policy (0: pre, 1: post). 28 | * 29 | * @param block The block number to execute the call on. 30 | */ 31 | public getPosition(block?: number) { 32 | return this.makeCall('position', undefined, block); 33 | } 34 | 35 | /** 36 | * Checks wheter a rule is true or false 37 | * 38 | * @param args The arguments for the rule 39 | * @param block The block number to execute the call on. 40 | */ 41 | public rule(args: PolicyArgs, block?: number) { 42 | return this.makeCall( 43 | 'rule', 44 | [ 45 | hexToBytes(args.signature), 46 | args.addresses, 47 | args.values.map((value) => value.toFixed(0)), 48 | hexToBytes(args.identifier), 49 | ], 50 | block, 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/MaxConcentration.test.ts: -------------------------------------------------------------------------------- 1 | import { MaxConcentration } from './MaxConcentration'; 2 | import { TestEnvironment, createTestEnvironment } from '../../../utils/tests/createTestEnvironment'; 3 | import { MaxConcentrationBytecode } from '../../../abis/MaxConcentration.bin'; 4 | import BigNumber from 'bignumber.js'; 5 | 6 | describe('MaxConcentration', () => { 7 | let environment: TestEnvironment; 8 | let maxConcentration: MaxConcentration; 9 | const max = new BigNumber('100000000000000000'); 10 | 11 | beforeAll(async () => { 12 | environment = await createTestEnvironment(); 13 | 14 | const deploy = MaxConcentration.deploy(environment, MaxConcentrationBytecode, environment.accounts[0], max); 15 | maxConcentration = await deploy.send(await deploy.prepare()); 16 | }); 17 | 18 | it('should return the price tolerance', async () => { 19 | const result = await maxConcentration.getMaxConcentration(); 20 | expect(result.isEqualTo(max)).toBe(true); 21 | }); 22 | 23 | it('should return the correct identifier', async () => { 24 | const result = await maxConcentration.getIdentifier(); 25 | expect(result).toBe('MaxConcentration'); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/MaxConcentration.ts: -------------------------------------------------------------------------------- 1 | import { MaxConcentrationAbi } from '../../../abis/MaxConcentration.abi'; 2 | import { IPolicy } from './IPolicy'; 3 | import { toBigNumber } from '../../../utils/toBigNumber'; 4 | import { Environment } from '../../../Environment'; 5 | import { Address } from '../../../Address'; 6 | import BigNumber from 'bignumber.js'; 7 | 8 | export class MaxConcentration extends IPolicy { 9 | public static readonly abi = MaxConcentrationAbi; 10 | 11 | public static deploy(environment: Environment, bytecode: string, from: Address, maxConcentration: BigNumber) { 12 | return super.createDeployment(environment, bytecode, from, [maxConcentration.toFixed(0)]); 13 | } 14 | 15 | /** 16 | * Gets the maximum concentration of an asset in the portfolio. 17 | * 18 | * @param block The block number to execute the call on. 19 | */ 20 | public async getMaxConcentration(block?: number) { 21 | const result = await this.makeCall('maxConcentration', undefined, block); 22 | return toBigNumber(result); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/MaxPositions.test.ts: -------------------------------------------------------------------------------- 1 | import { MaxPositions } from './MaxPositions'; 2 | import { TestEnvironment, createTestEnvironment } from '../../../utils/tests/createTestEnvironment'; 3 | import { MaxPositionsBytecode } from '../../../abis/MaxPositions.bin'; 4 | 5 | describe('MaxPositions', () => { 6 | let environment: TestEnvironment; 7 | let maxPositions: MaxPositions; 8 | const max = 10; 9 | 10 | beforeAll(async () => { 11 | environment = await createTestEnvironment(); 12 | 13 | const deploy = MaxPositions.deploy(environment, MaxPositionsBytecode, environment.accounts[0], max); 14 | maxPositions = await deploy.send(await deploy.prepare()); 15 | }); 16 | 17 | it('should return the max number of positions', async () => { 18 | const result = await maxPositions.getMaxPositions(); 19 | expect(result).toEqual(max); 20 | }); 21 | 22 | it('should return the correct identifier', async () => { 23 | const result = await maxPositions.getIdentifier(); 24 | expect(result).toBe('MaxPositions'); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/MaxPositions.ts: -------------------------------------------------------------------------------- 1 | import { MaxPositionsAbi } from '../../../abis/MaxPositions.abi'; 2 | import { IPolicy } from './IPolicy'; 3 | import { Environment } from '../../../Environment'; 4 | import { Address } from '../../../Address'; 5 | import { hexToNumber } from 'web3-utils'; 6 | 7 | export class MaxPositions extends IPolicy { 8 | public static readonly abi = MaxPositionsAbi; 9 | 10 | public static deploy(environment: Environment, bytecode: string, from: Address, maxPositions: number) { 11 | return super.createDeployment(environment, bytecode, from, [maxPositions]); 12 | } 13 | 14 | /** 15 | * Gets the maximum number of positions. 16 | * 17 | * @param block The block number to execute the call on. 18 | */ 19 | public async getMaxPositions(block?: number) { 20 | const result = await this.makeCall('maxPositions', undefined, block); 21 | return hexToNumber(result); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/PolicyManagerFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { createTestEnvironment, TestEnvironment } from '../../../utils/tests/createTestEnvironment'; 2 | import { randomAddress } from '../../../utils/tests/randomAddress'; 3 | import { deployHub } from '../../../utils/tests/deployHub'; 4 | import { PolicyManagerFactory } from './PolicyManagerFactory'; 5 | import { PolicyManagerFactoryBytecode } from '../../../abis/PolicyManagerFactory.bin'; 6 | 7 | describe('PolicyManagerFactory', () => { 8 | let environment: TestEnvironment; 9 | let policyManagerFactory: PolicyManagerFactory; 10 | 11 | beforeAll(async () => { 12 | environment = await createTestEnvironment(); 13 | 14 | const deploy = PolicyManagerFactory.deploy(environment, PolicyManagerFactoryBytecode, environment.accounts[0]); 15 | policyManagerFactory = await deploy.send(await deploy.prepare()); 16 | }); 17 | 18 | it('should check if a contract is an instance of the PolicyManagerFactory', async () => { 19 | const result = await policyManagerFactory.isInstance(randomAddress()); 20 | expect(typeof result).toBe('boolean'); 21 | }); 22 | 23 | it('should create an instance of a PolicyManager contract', async () => { 24 | const hub = await deployHub(environment, environment.accounts[0], { 25 | name: 'policymanager-test-fund', 26 | manager: environment.accounts[0], 27 | }); 28 | 29 | const tx = policyManagerFactory.createInstance(environment.accounts[0], hub.contract.address); 30 | 31 | const txResult = await tx.send(await tx.prepare()); 32 | expect(txResult.gasUsed).toBeGreaterThanOrEqual(0); 33 | expect(txResult.status).toBe(true); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/PolicyManagerFactory.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../Contract'; 2 | import { Environment } from '../../../Environment'; 3 | import { Address } from '../../../Address'; 4 | import { applyMixins } from '../../../utils/applyMixins'; 5 | import { Factory } from '../../factory/Factory'; 6 | import { PolicyManagerFactoryAbi } from '../../../abis/PolicyManagerFactory.abi'; 7 | 8 | export class PolicyManagerFactory extends Contract { 9 | public static readonly abi = PolicyManagerFactoryAbi; 10 | 11 | public static deploy(environment: Environment, bytecode: string, from: Address) { 12 | return super.createDeployment(environment, bytecode, from); 13 | } 14 | 15 | /** 16 | * Creates a PolicyManager instance. 17 | * 18 | * @param from The sender address. 19 | * @param hub The hub address. 20 | */ 21 | public createInstance(from: Address, hub: Address) { 22 | const args = [hub]; 23 | return this.createTransaction({ from, method: 'createInstance', args }); 24 | } 25 | } 26 | 27 | export interface PolicyManagerFactory extends Factory {} 28 | applyMixins(PolicyManagerFactory, [Factory]); 29 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/PriceTolerance.test.ts: -------------------------------------------------------------------------------- 1 | import { PriceTolerance } from './PriceTolerance'; 2 | import { TestEnvironment, createTestEnvironment } from '../../../utils/tests/createTestEnvironment'; 3 | import { PriceToleranceBytecode } from '../../../abis/PriceTolerance.bin'; 4 | import BigNumber from 'bignumber.js'; 5 | 6 | describe('PriceTolerance', () => { 7 | let environment: TestEnvironment; 8 | let priceTolerance: PriceTolerance; 9 | const tolerance = 10; 10 | 11 | beforeAll(async () => { 12 | environment = await createTestEnvironment(); 13 | 14 | const deploy = PriceTolerance.deploy(environment, PriceToleranceBytecode, environment.accounts[0], tolerance); 15 | priceTolerance = await deploy.send(await deploy.prepare()); 16 | }); 17 | 18 | it('should return the price tolerance', async () => { 19 | const result = await priceTolerance.getPriceTolerance(); 20 | expect(result.isEqualTo(new BigNumber('1e16').multipliedBy(tolerance))).toBe(true); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/PriceTolerance.ts: -------------------------------------------------------------------------------- 1 | import { PriceToleranceAbi } from '../../../abis/PriceTolerance.abi'; 2 | import { toBigNumber } from '../../../utils/toBigNumber'; 3 | import { IPolicy } from './IPolicy'; 4 | import { Environment } from '../../../Environment'; 5 | import { Address } from '../../../Address'; 6 | 7 | export class PriceTolerance extends IPolicy { 8 | public static readonly abi = PriceToleranceAbi; 9 | 10 | public static deploy(environment: Environment, bytecode: string, from: Address, tolerance: number) { 11 | return super.createDeployment(environment, bytecode, from, [tolerance]); 12 | } 13 | 14 | /** 15 | * Gets the price tolerance 16 | * 17 | * @param block The block number to execute the call on. 18 | */ 19 | public async getPriceTolerance(block?: number) { 20 | const result = await this.makeCall('tolerance', undefined, block); 21 | return toBigNumber(result); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/UserWhitelist.test.ts: -------------------------------------------------------------------------------- 1 | import { UserWhitelist } from './UserWhitelist'; 2 | import { TestEnvironment, createTestEnvironment } from '../../../utils/tests/createTestEnvironment'; 3 | import { randomAddress } from '../../../utils/tests/randomAddress'; 4 | import { UserWhitelistBytecode } from '../../../abis/UserWhitelist.bin'; 5 | import { range } from '../../../utils/range'; 6 | 7 | describe('UserWhitelist', () => { 8 | let environment: TestEnvironment; 9 | let userWhiteList: UserWhitelist; 10 | const addresses = range(5).map(() => randomAddress()); 11 | 12 | beforeAll(async () => { 13 | environment = await createTestEnvironment(); 14 | 15 | const deploy = UserWhitelist.deploy(environment, UserWhitelistBytecode, environment.accounts[0], addresses); 16 | userWhiteList = await deploy.send(await deploy.prepare()); 17 | }); 18 | 19 | it('should check whether a user is whitelisted', async () => { 20 | const result = await userWhiteList.isWhitelisted(addresses[3]); 21 | expect(result).toBe(true); 22 | }); 23 | 24 | it('should return the correct identifier', async () => { 25 | const result = await userWhiteList.getIdentifier(); 26 | expect(result).toBe('UserWhitelist'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/contracts/fund/policies/UserWhitelist.ts: -------------------------------------------------------------------------------- 1 | import { Environment } from '../../../Environment'; 2 | import { Address } from '../../../Address'; 3 | import { UserWhitelistAbi } from '../../../abis/UserWhitelist.abi'; 4 | import { IPolicy } from './IPolicy'; 5 | 6 | export class UserWhitelist extends IPolicy { 7 | public static readonly abi = UserWhitelistAbi; 8 | 9 | public static deploy(environment: Environment, bytecode: string, from: Address, approved: Address[]) { 10 | return super.createDeployment(environment, bytecode, from, [approved]); 11 | } 12 | 13 | /** 14 | * Add a user address to a UserWhitelist. 15 | * 16 | * @param from The address of the sender 17 | * @param user The address of the user to be added to the UserWhitelist 18 | */ 19 | public addToWhitelist(from: Address, user: Address) { 20 | return this.createTransaction({ from, method: 'addToWhitelist', args: [user] }); 21 | } 22 | 23 | /** 24 | * Remove a user address from a UserWhitelist. 25 | * 26 | * @param from The address of the sender 27 | * @param user The address of the user to be removed from the UserWhitelist 28 | */ 29 | public removeFromWhitelist(from: Address, user: Address) { 30 | return this.createTransaction({ from, method: 'removeFromWhitelist', args: [user] }); 31 | } 32 | 33 | /** 34 | * Add a list of user addresses to a UserWhitelist. 35 | * 36 | * @param from The address of the sender 37 | * @param users The addresses of the users to be added to the UserWhitelist 38 | */ 39 | public batchAddToWhitelist(from: Address, users: Address[]) { 40 | return this.createTransaction({ from, method: 'batchAddToWhitelist', args: [users] }); 41 | } 42 | 43 | /** 44 | * Remove a list of user addresses from a UserWhitelist. 45 | * 46 | * @param from The address of the sender 47 | * @param users The addresses of the users to be removed from the UserWhitelist 48 | */ 49 | public batchRemoveFromWhitelist(from: Address, users: Address[]) { 50 | return this.createTransaction({ from, method: 'batchRemoveFromWhitelist', args: [users] }); 51 | } 52 | 53 | /** 54 | * Checks if an address is whitelisted 55 | * 56 | * @param address The address to check 57 | * @param block The block number to execute the call on. 58 | */ 59 | public async isWhitelisted(address: Address, block?: number) { 60 | return await this.makeCall('whitelisted', [address], block); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/contracts/fund/shares/Shares.test.ts: -------------------------------------------------------------------------------- 1 | import { createTestEnvironment, TestEnvironment } from '../../../utils/tests/createTestEnvironment'; 2 | import { Shares } from './Shares'; 3 | import { Hub } from '../hub/Hub'; 4 | import { deployHub } from '../../../utils/tests/deployHub'; 5 | import { deployShares } from '../../../utils/tests/deployShares'; 6 | 7 | describe('Shares', () => { 8 | let environment: TestEnvironment; 9 | let shares: Shares; 10 | let hub: Hub; 11 | 12 | beforeAll(async () => { 13 | environment = await createTestEnvironment(); 14 | hub = await deployHub(environment, environment.accounts[0], { 15 | manager: environment.accounts[1], 16 | name: 'vault-test-fund', 17 | }); 18 | 19 | shares = await deployShares(environment, environment.accounts[0], hub.contract.address); 20 | }); 21 | 22 | it('should return the name for shares', async () => { 23 | const result = await shares.getName(); 24 | expect(result.length).toBeGreaterThanOrEqual(0); 25 | }); 26 | 27 | it('should return the symbol for shares', async () => { 28 | const result = await shares.getSymbol(); 29 | expect(result).toBe('MLNF'); 30 | }); 31 | 32 | it('should return the decimals for shares', async () => { 33 | const result = await shares.getDecimals(); 34 | expect(result).toBeGreaterThanOrEqual(0); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/contracts/fund/shares/Shares.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../Contract'; 2 | import { Environment } from '../../../Environment'; 3 | import { Address } from '../../../Address'; 4 | import { Spoke } from '../hub/Spoke'; 5 | import { applyMixins } from '../../../utils/applyMixins'; 6 | import { SharesAbi } from '../../../abis/Shares.abi'; 7 | import { hexToString } from 'web3-utils'; 8 | import { StandardToken } from '../../dependencies/token/StandardToken'; 9 | 10 | export class Shares extends Contract { 11 | public static readonly abi = SharesAbi; 12 | 13 | public static deploy(environment: Environment, bytecode: string, from: Address, hub: Address) { 14 | return super.createDeployment(environment, bytecode, from, [hub]); 15 | } 16 | 17 | /** 18 | * Gets the name of the shares. 19 | * 20 | * @param block The block number to execute the call on. 21 | */ 22 | public async getName(block?: number) { 23 | const result = await this.makeCall('name', undefined, block); 24 | return hexToString(result); 25 | } 26 | 27 | /** 28 | * Gets the symbol of the shares. 29 | * 30 | * @param block The block number to execute the call on. 31 | */ 32 | public async getSymbol(block?: number) { 33 | const result = await this.makeCall('symbol', undefined, block); 34 | return result; 35 | } 36 | 37 | /** 38 | * Gets the decimals of the shares. 39 | * 40 | * @param block The block number to execute the call on. 41 | */ 42 | public async getDecimals(block?: number) { 43 | const result = await this.makeCall('decimals', undefined, block); 44 | return parseInt(result, 10); 45 | } 46 | } 47 | 48 | export interface Shares extends Spoke, StandardToken {} 49 | applyMixins(Shares, [Spoke, StandardToken]); 50 | -------------------------------------------------------------------------------- /src/contracts/fund/shares/SharesFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { createTestEnvironment, TestEnvironment } from '../../../utils/tests/createTestEnvironment'; 2 | 3 | import { randomAddress } from '../../../utils/tests/randomAddress'; 4 | import { deployHub } from '../../../utils/tests/deployHub'; 5 | import { SharesFactory } from './SharesFactory'; 6 | import { SharesFactoryBytecode } from '../../../abis/SharesFactory.bin'; 7 | 8 | describe('SharesFactory', () => { 9 | let environment: TestEnvironment; 10 | let sharesFactory: SharesFactory; 11 | 12 | beforeAll(async () => { 13 | environment = await createTestEnvironment(); 14 | 15 | const deploy = SharesFactory.deploy(environment, SharesFactoryBytecode, environment.accounts[0]); 16 | sharesFactory = await deploy.send(await deploy.prepare()); 17 | }); 18 | 19 | it('should check if a contract is an instance of the SharesFactory', async () => { 20 | const result = await sharesFactory.isInstance(randomAddress()); 21 | expect(typeof result).toBe('boolean'); 22 | }); 23 | 24 | it('should create an instance of a Shares contract', async () => { 25 | const hub = await deployHub(environment, environment.accounts[0], { 26 | name: 'shares-test-fund', 27 | manager: environment.accounts[0], 28 | }); 29 | 30 | const tx = sharesFactory.createInstance(environment.accounts[0], hub.contract.address); 31 | 32 | const txResult = await tx.send(await tx.prepare()); 33 | expect(txResult.gasUsed).toBeGreaterThanOrEqual(0); 34 | expect(txResult.status).toBe(true); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/contracts/fund/shares/SharesFactory.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../Contract'; 2 | import { Environment } from '../../../Environment'; 3 | import { Address } from '../../../Address'; 4 | import { applyMixins } from '../../../utils/applyMixins'; 5 | import { Factory } from '../../factory/Factory'; 6 | import { SharesFactoryAbi } from '../../../abis/SharesFactory.abi'; 7 | 8 | export class SharesFactory extends Contract { 9 | public static readonly abi = SharesFactoryAbi; 10 | 11 | public static deploy(environment: Environment, bytecode: string, from: Address) { 12 | return super.createDeployment(environment, bytecode, from); 13 | } 14 | 15 | /** 16 | * Creates a Shares instance. 17 | * 18 | * @param from The sender address. 19 | * @param hub The hub address. 20 | */ 21 | public createInstance(from: Address, hub: Address) { 22 | const args = [hub]; 23 | return this.createTransaction({ from, method: 'createInstance', args }); 24 | } 25 | } 26 | 27 | export interface SharesFactory extends Factory {} 28 | applyMixins(SharesFactory, [Factory]); 29 | -------------------------------------------------------------------------------- /src/contracts/fund/trading/TradingFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { createTestEnvironment, TestEnvironment } from '../../../utils/tests/createTestEnvironment'; 2 | 3 | import { randomAddress } from '../../../utils/tests/randomAddress'; 4 | import { deployHub } from '../../../utils/tests/deployHub'; 5 | import { TradingFactory } from './TradingFactory'; 6 | import { TradingFactoryBytecode } from '../../../abis/TradingFactory.bin'; 7 | import { deployRegistry } from '../../../utils/tests/deployRegistry'; 8 | 9 | describe('TradingFactory', () => { 10 | let environment: TestEnvironment; 11 | let tradingFactory: TradingFactory; 12 | 13 | beforeAll(async () => { 14 | environment = await createTestEnvironment(); 15 | 16 | const deploy = TradingFactory.deploy(environment, TradingFactoryBytecode, environment.accounts[0]); 17 | tradingFactory = await deploy.send(await deploy.prepare()); 18 | }); 19 | 20 | it('should check if a contract is an instance of TradingFactory', async () => { 21 | const result = await tradingFactory.isInstance(randomAddress()); 22 | expect(typeof result).toBe('boolean'); 23 | }); 24 | 25 | it('should create an instance of a trading contract', async () => { 26 | const hub = await deployHub(environment, environment.accounts[0], { 27 | manager: environment.accounts[1], 28 | name: 'trading-test-fund', 29 | }); 30 | 31 | const exchangeAddress = randomAddress(); 32 | const adapterAddress = randomAddress(); 33 | const registry = await deployRegistry(environment, environment.accounts[0], environment.accounts[0]); 34 | 35 | const txExchangeAdapter = registry.registerExchangeAdapter(environment.accounts[0], { 36 | exchangeAddress, 37 | adapterAddress, 38 | takesCustody: true, 39 | sigs: ['0x30303030'], 40 | }); 41 | 42 | await txExchangeAdapter.send(await txExchangeAdapter.prepare()); 43 | 44 | const tx = tradingFactory.createInstance(environment.accounts[0], { 45 | hub: hub.contract.address, 46 | exchanges: [exchangeAddress], 47 | adapters: [adapterAddress], 48 | registry: registry.contract.address, 49 | }); 50 | 51 | const txResult = await tx.send(await tx.prepare()); 52 | expect(txResult.gasUsed).toBeGreaterThanOrEqual(0); 53 | expect(txResult.status).toBe(true); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /src/contracts/fund/trading/TradingFactory.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../Contract'; 2 | import { Environment } from '../../../Environment'; 3 | import { Address } from '../../../Address'; 4 | import { applyMixins } from '../../../utils/applyMixins'; 5 | import { Factory } from '../../factory/Factory'; 6 | import { TradingFactoryAbi } from '../../../abis/TradingFactory.abi'; 7 | import { TradingDeployArguments } from './Trading'; 8 | 9 | export class TradingFactory extends Contract { 10 | public static readonly abi = TradingFactoryAbi; 11 | 12 | public static deploy(environment: Environment, bytecode: string, from: Address) { 13 | return super.createDeployment(environment, bytecode, from); 14 | } 15 | 16 | /** 17 | * Creates a Trading instance. 18 | * 19 | * @param from The sender address. 20 | * @param args The trading deploy arguments as [[TradingDeployArguments]]. 21 | */ 22 | public createInstance(from: Address, deployArgs: TradingDeployArguments) { 23 | const args = [deployArgs.hub, deployArgs.exchanges, deployArgs.adapters, deployArgs.registry]; 24 | return this.createTransaction({ from, method: 'createInstance', args }); 25 | } 26 | } 27 | 28 | export interface TradingFactory extends Factory {} 29 | applyMixins(TradingFactory, [Factory]); 30 | -------------------------------------------------------------------------------- /src/contracts/fund/trading/exchanges/BaseTradingAdapter.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../../../Address'; 2 | import { sameAddress } from '../../../../utils/sameAddress'; 3 | import { Trading, ExchangeInfo } from '../Trading'; 4 | import { ExchangeNotRegisteredWithFundError } from '../Trading.errors'; 5 | import { Contract } from '../../../../Contract'; 6 | import { Environment } from '../../../../Environment'; 7 | 8 | export class BaseTradingAdapter extends Contract { 9 | constructor( 10 | public readonly environment: Environment, 11 | public readonly adapterAddress: Address, 12 | public readonly trading: Trading, 13 | public readonly info: ExchangeInfo, 14 | public readonly index: number, 15 | ) { 16 | super(environment, adapterAddress); 17 | } 18 | 19 | public static async create( 20 | this: T, 21 | environment: Environment, 22 | adapterAddress: Address, 23 | trading: Trading, 24 | ) { 25 | const info = await trading.getExchangeInfo(); 26 | const index = info.findIndex(exchange => sameAddress(exchange.adapter, adapterAddress)); 27 | if (index === -1) { 28 | throw new ExchangeNotRegisteredWithFundError(adapterAddress); 29 | } 30 | 31 | return new this(environment, adapterAddress, trading, info[index], index) as InstanceType; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/contracts/fund/trading/exchanges/KyberTradingAdapter.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { Address } from '../../../../Address'; 3 | import { functionSignature } from '../../../../utils/functionSignature'; 4 | import { ExchangeAdapterAbi } from '../../../../abis/ExchangeAdapter.abi'; 5 | import { zeroAddress } from '../../../../utils/zeroAddress'; 6 | import { checkSufficientBalance } from '../utils/checkSufficientBalance'; 7 | import { checkFundIsNotShutdown } from '../utils/checkFundIsNotShutdown'; 8 | import { zeroBigNumber } from '../../../../utils/zeroBigNumber'; 9 | import { BaseTradingAdapter } from './BaseTradingAdapter'; 10 | import { CallOnExchangeArgs } from '../Trading'; 11 | import { padLeft } from 'web3-utils'; 12 | import { checkSenderIsFundManager } from '../utils/checkSenderIsFundManager'; 13 | 14 | export interface KyberTakeOrderArgs { 15 | makerAsset: Address; 16 | takerAsset: Address; 17 | makerQuantity: BigNumber; 18 | takerQuantity: BigNumber; 19 | } 20 | 21 | export class KyberTradingAdapter extends BaseTradingAdapter { 22 | /** 23 | * Take order on Kyber 24 | * 25 | * @param from The address of the sender 26 | * @param args The arguments as [[KyberTakeOrderArgs]] 27 | */ 28 | public takeOrder(from: Address, args: KyberTakeOrderArgs) { 29 | const paddedZeros = padLeft('0x0', 64); 30 | const methodArgs: CallOnExchangeArgs = { 31 | exchangeIndex: this.index, 32 | methodSignature: functionSignature(ExchangeAdapterAbi, 'takeOrder'), 33 | orderAddresses: [ 34 | zeroAddress, 35 | zeroAddress, 36 | args.makerAsset, 37 | args.takerAsset, 38 | zeroAddress, 39 | zeroAddress, 40 | zeroAddress, 41 | zeroAddress, 42 | ], 43 | orderValues: [ 44 | args.makerQuantity, 45 | args.takerQuantity, 46 | zeroBigNumber, 47 | zeroBigNumber, 48 | zeroBigNumber, 49 | zeroBigNumber, 50 | args.takerQuantity, 51 | zeroBigNumber, 52 | ], 53 | orderData: [paddedZeros, paddedZeros, paddedZeros, paddedZeros], 54 | identifier: paddedZeros, 55 | signature: paddedZeros, 56 | }; 57 | 58 | const validate = async () => { 59 | const vaultAddress = (await this.trading.getRoutes()).vault; 60 | const hubAddress = await this.trading.getHub(); 61 | 62 | await Promise.all([ 63 | checkSufficientBalance(this.trading.environment, args.takerAsset, args.takerQuantity, vaultAddress), 64 | checkFundIsNotShutdown(this.trading.environment, hubAddress), 65 | checkSenderIsFundManager(this.trading.environment, from, hubAddress), 66 | ]); 67 | }; 68 | 69 | return this.trading.callOnExchange(from, methodArgs, validate); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/contracts/fund/trading/exchanges/MelonEngineTradingAdapter.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { Address } from '../../../../Address'; 3 | import { functionSignature } from '../../../../utils/functionSignature'; 4 | import { ExchangeAdapterAbi } from '../../../../abis/ExchangeAdapter.abi'; 5 | import { zeroAddress } from '../../../../utils/zeroAddress'; 6 | import { checkSufficientBalance } from '../utils/checkSufficientBalance'; 7 | import { checkFundIsNotShutdown } from '../utils/checkFundIsNotShutdown'; 8 | import { zeroBigNumber } from '../../../../utils/zeroBigNumber'; 9 | import { BaseTradingAdapter } from './BaseTradingAdapter'; 10 | import { CallOnExchangeArgs } from '../Trading'; 11 | import { padLeft } from 'web3-utils'; 12 | import { checkSenderIsFundManager } from '../utils/checkSenderIsFundManager'; 13 | 14 | export interface TakeOrderMelonEngine { 15 | makerAsset: Address; 16 | takerAsset: Address; 17 | makerQuantity: BigNumber; 18 | takerQuantity: BigNumber; 19 | } 20 | 21 | export class MelonEngineTradingAdapter extends BaseTradingAdapter { 22 | /** 23 | * Take order on the Melon Engine 24 | * 25 | * @param from The address of the sender 26 | * @param args The arguments as [[TakeOrderMelonEngine]] 27 | */ 28 | public takeOrder(from: Address, args: TakeOrderMelonEngine) { 29 | const paddedZeros = padLeft('0x0', 64); 30 | const methodArgs: CallOnExchangeArgs = { 31 | exchangeIndex: this.index, 32 | methodSignature: functionSignature(ExchangeAdapterAbi, 'takeOrder'), 33 | orderAddresses: [ 34 | zeroAddress, 35 | zeroAddress, 36 | args.makerAsset, 37 | args.takerAsset, 38 | zeroAddress, 39 | zeroAddress, 40 | zeroAddress, 41 | zeroAddress, 42 | ], 43 | orderValues: [ 44 | args.makerQuantity, 45 | args.takerQuantity, 46 | zeroBigNumber, 47 | zeroBigNumber, 48 | zeroBigNumber, 49 | zeroBigNumber, 50 | args.takerQuantity, 51 | zeroBigNumber, 52 | ], 53 | orderData: [paddedZeros, paddedZeros, paddedZeros, paddedZeros], 54 | identifier: paddedZeros, 55 | signature: paddedZeros, 56 | }; 57 | 58 | const validate = async () => { 59 | const vaultAddress = (await this.trading.getRoutes()).vault; 60 | const hubAddress = await this.trading.getHub(); 61 | 62 | await Promise.all([ 63 | checkSufficientBalance(this.trading.environment, args.takerAsset, args.takerQuantity, vaultAddress), 64 | checkFundIsNotShutdown(this.trading.environment, hubAddress), 65 | checkSenderIsFundManager(this.trading.environment, from, hubAddress), 66 | ]); 67 | }; 68 | 69 | return this.trading.callOnExchange(from, methodArgs, validate); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/contracts/fund/trading/exchanges/UniswapTradingAdapter.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { Address } from '../../../../Address'; 3 | import { functionSignature } from '../../../../utils/functionSignature'; 4 | import { ExchangeAdapterAbi } from '../../../../abis/ExchangeAdapter.abi'; 5 | import { zeroAddress } from '../../../../utils/zeroAddress'; 6 | import { checkSufficientBalance } from '../utils/checkSufficientBalance'; 7 | import { checkFundIsNotShutdown } from '../utils/checkFundIsNotShutdown'; 8 | import { zeroBigNumber } from '../../../../utils/zeroBigNumber'; 9 | import { BaseTradingAdapter } from './BaseTradingAdapter'; 10 | import { CallOnExchangeArgs } from '../Trading'; 11 | import { padLeft } from 'web3-utils'; 12 | import { checkSenderIsFundManager } from '../utils/checkSenderIsFundManager'; 13 | 14 | export interface UniswapTakeOrderArgs { 15 | makerAsset: Address; 16 | takerAsset: Address; 17 | makerQuantity: BigNumber; 18 | takerQuantity: BigNumber; 19 | } 20 | 21 | export class UniswapTradingAdapter extends BaseTradingAdapter { 22 | /** 23 | * Take order on Uniswap 24 | * 25 | * @param from The address of the sender 26 | * @param args The arguments as [[UniswapTakeOrderArgs]] 27 | */ 28 | public takeOrder(from: Address, args: UniswapTakeOrderArgs) { 29 | const paddedZeros = padLeft('0x0', 64); 30 | const methodArgs: CallOnExchangeArgs = { 31 | exchangeIndex: this.index, 32 | methodSignature: functionSignature(ExchangeAdapterAbi, 'takeOrder'), 33 | orderAddresses: [ 34 | zeroAddress, 35 | zeroAddress, 36 | args.makerAsset, 37 | args.takerAsset, 38 | zeroAddress, 39 | zeroAddress, 40 | zeroAddress, 41 | zeroAddress, 42 | ], 43 | orderValues: [ 44 | args.makerQuantity, 45 | args.takerQuantity, 46 | zeroBigNumber, 47 | zeroBigNumber, 48 | zeroBigNumber, 49 | zeroBigNumber, 50 | args.takerQuantity, 51 | zeroBigNumber, 52 | ], 53 | orderData: [paddedZeros, paddedZeros, paddedZeros, paddedZeros], 54 | identifier: paddedZeros, 55 | signature: paddedZeros, 56 | }; 57 | 58 | const validate = async () => { 59 | const vaultAddress = (await this.trading.getRoutes()).vault; 60 | const hubAddress = await this.trading.getHub(); 61 | 62 | await Promise.all([ 63 | checkSufficientBalance(this.trading.environment, args.takerAsset, args.takerQuantity, vaultAddress), 64 | checkFundIsNotShutdown(this.trading.environment, hubAddress), 65 | checkSenderIsFundManager(this.trading.environment, from, hubAddress), 66 | ]); 67 | }; 68 | 69 | return this.trading.callOnExchange(from, methodArgs, validate); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/contracts/fund/trading/utils/checkCooldownReached.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../../../Address'; 2 | import { Trading } from '../Trading'; 3 | import { CooldownForMakerAssetNotReachedError } from '../Trading.errors'; 4 | import BigNumber from 'bignumber.js'; 5 | 6 | export const checkCooldownReached = async (trading: Trading, asset: Address, block?: number) => { 7 | const [makerAssetCooldown, blockObject] = await Promise.all([ 8 | trading.getMakerAssetCooldown(asset, block), 9 | trading.environment.client.getBlock(block || 'latest'), 10 | ]); 11 | 12 | if (!makerAssetCooldown) { 13 | return; 14 | } 15 | 16 | const currentBlockTime = new BigNumber(blockObject.timestamp).multipliedBy(1000); 17 | if (currentBlockTime.isLessThan(makerAssetCooldown!.getTime())) { 18 | throw new CooldownForMakerAssetNotReachedError(asset); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/contracts/fund/trading/utils/checkExistingOpenMakeOrder.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../../../Address'; 2 | import { Trading } from '../Trading'; 3 | import { AssetAlreadyHasOpenMakeOrderError } from '../Trading.errors'; 4 | 5 | export const checkExistingOpenMakeOrder = async (trading: Trading, asset: Address, block?: number) => { 6 | const isInOpenMakeOrder = await trading.checkOpenMakeOrder(asset, block); 7 | if (isInOpenMakeOrder) { 8 | throw new AssetAlreadyHasOpenMakeOrderError(asset); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/contracts/fund/trading/utils/checkFundIsNotShutdown.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../../../Address'; 2 | import { Environment } from '../../../../Environment'; 3 | import { FundIsShutDownError } from '../Trading.errors'; 4 | import { Hub } from '../../hub/Hub'; 5 | 6 | export const checkFundIsNotShutdown = async (environment: Environment, hubAddress: Address) => { 7 | const hub = new Hub(environment, hubAddress); 8 | 9 | const isShutDown = await hub.isShutDown(); 10 | if (isShutDown) { 11 | throw new FundIsShutDownError(hub.contract.address); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/contracts/fund/trading/utils/checkSenderIsFundManager.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../../../Address'; 2 | import { Environment } from '../../../../Environment'; 3 | import { Hub } from '../../hub/Hub'; 4 | import { sameAddress } from '../../../../utils/sameAddress'; 5 | import { SenderIsNotFundManagerError } from '../Trading.errors'; 6 | 7 | export const checkSenderIsFundManager = async (environment: Environment, sender: Address, hubAddress: Address) => { 8 | const hub = new Hub(environment, hubAddress); 9 | const manager = await hub.getManager(); 10 | 11 | if (!sameAddress(sender, manager)) { 12 | throw new SenderIsNotFundManagerError(sender); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/contracts/fund/trading/utils/checkSufficientBalance.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { Address } from '../../../../Address'; 3 | import { Environment } from '../../../../Environment'; 4 | import { InsufficientBalanceError } from '../Trading.errors'; 5 | import { ERC20WithFields } from '../../../dependencies/token/ERC20WithFields'; 6 | 7 | export const checkSufficientBalance = async ( 8 | environment: Environment, 9 | tokenAddress: Address, 10 | tokenQuantity: BigNumber, 11 | vaultAddress: Address, 12 | ) => { 13 | const token = new ERC20WithFields(environment, tokenAddress); 14 | const vaultTokenBalance = await token.getBalanceOf(vaultAddress); 15 | if (vaultTokenBalance.isLessThan(tokenQuantity)) { 16 | throw new InsufficientBalanceError(tokenQuantity, vaultTokenBalance); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/contracts/fund/vault/Vault.test.ts: -------------------------------------------------------------------------------- 1 | import { createTestEnvironment, TestEnvironment } from '../../../utils/tests/createTestEnvironment'; 2 | import { Hub } from '../hub/Hub'; 3 | import { Vault } from './Vault'; 4 | import { deployHub } from '../../../utils/tests/deployHub'; 5 | import { deployVault } from '../../../utils/tests/deployVault'; 6 | 7 | describe('Vault', () => { 8 | let environment: TestEnvironment; 9 | let vault: Vault; 10 | let hub: Hub; 11 | 12 | beforeAll(async () => { 13 | environment = await createTestEnvironment(); 14 | hub = await deployHub(environment, environment.accounts[0], { 15 | manager: environment.accounts[1], 16 | name: 'vault-test-fund-1', 17 | }); 18 | 19 | vault = await deployVault(environment, environment.accounts[0], hub.contract.address); 20 | }); 21 | 22 | it('should return the version contract address', async () => { 23 | const result = await vault.getVersion(); 24 | expect(result.startsWith('0x')).toBe(true); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/contracts/fund/vault/Vault.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../Contract'; 2 | import { Environment } from '../../../Environment'; 3 | import { Address } from '../../../Address'; 4 | import { Spoke } from '../hub/Spoke'; 5 | import { applyMixins } from '../../../utils/applyMixins'; 6 | import { VaultAbi } from '../../../abis/Vault.abi'; 7 | import BigNumber from 'bignumber.js'; 8 | 9 | export class Vault extends Contract { 10 | public static readonly abi = VaultAbi; 11 | 12 | public static deploy(environment: Environment, bytecode: string, from: Address, hub: Address) { 13 | return super.createDeployment(environment, bytecode, from, [hub]); 14 | } 15 | 16 | /** 17 | * Withdraw from Vault 18 | * 19 | * @param from The sender address. 20 | * @param token The address of the token to withdraw 21 | * @param amount The amount to withdraw 22 | */ 23 | public withdraw(from: Address, token: Address, amount: BigNumber) { 24 | return this.createTransaction({ from, method: 'withdraw', args: [token, amount.toFixed(0)] }); 25 | } 26 | } 27 | 28 | export interface Vault extends Spoke {} 29 | applyMixins(Vault, [Spoke]); 30 | -------------------------------------------------------------------------------- /src/contracts/fund/vault/VaultFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { createTestEnvironment, TestEnvironment } from '../../../utils/tests/createTestEnvironment'; 2 | 3 | import { randomAddress } from '../../../utils/tests/randomAddress'; 4 | import { deployHub } from '../../../utils/tests/deployHub'; 5 | import { VaultFactory } from './VaultFactory'; 6 | import { VaultFactoryBytecode } from '../../../abis/VaultFactory.bin'; 7 | 8 | describe('VaultFactory', () => { 9 | let environment: TestEnvironment; 10 | let vaultFactory: VaultFactory; 11 | 12 | beforeAll(async () => { 13 | environment = await createTestEnvironment(); 14 | 15 | const deploy = VaultFactory.deploy(environment, VaultFactoryBytecode, environment.accounts[0]); 16 | vaultFactory = await deploy.send(await deploy.prepare()); 17 | }); 18 | 19 | it('should check if a contract is an instance of the VaultFactory', async () => { 20 | const result = await vaultFactory.isInstance(randomAddress()); 21 | expect(typeof result).toBe('boolean'); 22 | }); 23 | 24 | it('should create an instance of a Vault contract', async () => { 25 | const hub = await deployHub(environment, environment.accounts[0], { 26 | name: 'vault-test-fund', 27 | manager: environment.accounts[0], 28 | }); 29 | 30 | const tx = vaultFactory.createInstance(environment.accounts[0], hub.contract.address); 31 | 32 | const txResult = await tx.send(await tx.prepare()); 33 | expect(txResult.gasUsed).toBeGreaterThanOrEqual(0); 34 | expect(txResult.status).toBe(true); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/contracts/fund/vault/VaultFactory.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../../Contract'; 2 | import { Environment } from '../../../Environment'; 3 | import { Address } from '../../../Address'; 4 | import { applyMixins } from '../../../utils/applyMixins'; 5 | import { Factory } from '../../factory/Factory'; 6 | import { VaultFactoryAbi } from '../../../abis/VaultFactory.abi'; 7 | 8 | export class VaultFactory extends Contract { 9 | public static readonly abi = VaultFactoryAbi; 10 | 11 | public static deploy(environment: Environment, bytecode: string, from: Address) { 12 | return super.createDeployment(environment, bytecode, from); 13 | } 14 | 15 | /** 16 | * Creates a Vault instance. 17 | * 18 | * @param from The sender address. 19 | * @param hub The hub address. 20 | */ 21 | public createInstance(from: Address, hub: Address) { 22 | const args = [hub]; 23 | return this.createTransaction({ from, method: 'createInstance', args }); 24 | } 25 | } 26 | 27 | export interface VaultFactory extends Factory {} 28 | applyMixins(VaultFactory, [Factory]); 29 | -------------------------------------------------------------------------------- /src/contracts/prices/KyberPriceFeed.test.ts: -------------------------------------------------------------------------------- 1 | import { Eth } from 'web3-eth'; 2 | import { HttpProvider } from 'web3-providers'; 3 | import { Environment } from '../../Environment'; 4 | import { sameAddress } from '../../utils/sameAddress'; 5 | import { KyberPriceFeed } from './KyberPriceFeed'; 6 | 7 | describe('KyberPriceFeed', () => { 8 | let environment: Environment; 9 | let source: KyberPriceFeed; 10 | 11 | beforeAll(() => { 12 | // TODO: This should be replaced with a local ganache test environment using proper test fixtures. 13 | const client = new Eth(new HttpProvider('https://mainnet.melonport.com')); 14 | environment = new Environment(client); 15 | source = new KyberPriceFeed(environment, '0x4559DDD9E0a567bD8AB071ac106C1bC2d0C0b6Ef'); 16 | }); 17 | 18 | xit('should return the last price feed update as date object', async () => { 19 | const result = await source.getLastUpdate(); 20 | expect(result).toBeInstanceOf(Date); 21 | 22 | // We can't actually check the correctness of the concrete value because 23 | // currently we are not using a local test fixture setup based on ganache 24 | // but the actual mainnet deployment. 25 | // 26 | // NOTE: For other calls like e.g. Hub.name() we can actually check for 27 | // correctness because we can assume that the name of a fund at a given 28 | // address does not change. 29 | expect(result.getTime()).toBeLessThan(Date.now()); 30 | }); 31 | 32 | xit('should return the quote token', async () => { 33 | const result = await source.getQuoteAsset(); 34 | const weth = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; 35 | 36 | expect(sameAddress(weth, result)).toBe(true); 37 | }); 38 | 39 | xit('should return the price of a token pair', async () => { 40 | const zrx = '0xe41d2489571d322189246dafa5ebde1f4699f498'; 41 | 42 | const result = await source.getPrice(zrx); 43 | expect(result.price.isGreaterThanOrEqualTo(0)).toBe(true); 44 | }); 45 | 46 | xit('should return the prices of token pairs', async () => { 47 | const zrx = '0xe41d2489571d322189246dafa5ebde1f4699f498'; 48 | const mln = '0xec67005c4e498ec7f55e092bd1d35cbc47c91892'; 49 | 50 | const result = await source.getPrices([zrx, mln]); 51 | expect(Object.keys(result).length).toBe(2); 52 | }); 53 | 54 | xit('should return whether the price of a token is valid', async () => { 55 | const zrx = '0xe41d2489571d322189246dafa5ebde1f4699f498'; 56 | const result = await source.hasValidPrice(zrx); 57 | 58 | expect(result === true || result === false).toBe(true); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /src/contracts/prices/KyberPriceFeed.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '../../Contract'; 2 | import { KyberPriceFeedAbi } from '../../abis/KyberPriceFeed.abi'; 3 | import { applyMixins } from '../../utils/applyMixins'; 4 | import { IPriceSource } from './IPriceSource'; 5 | import { Address } from '../../Address'; 6 | import { Registry } from '../version/Registry'; 7 | import { sameAddress } from '../../utils/sameAddress'; 8 | import { ValidationError } from '../../errors/ValidationError'; 9 | 10 | export class OnlyRegistryOwnerOrUpdaterCanCallError extends ValidationError { 11 | public readonly name = 'OnlyRegistryOwnerOrUpdaterCanCallError'; 12 | 13 | constructor(message: string = 'Only registry owner or updater can call') { 14 | super(message); 15 | } 16 | } 17 | 18 | export class KyberPriceFeed extends Contract { 19 | public static readonly abi = KyberPriceFeedAbi; 20 | 21 | /** 22 | * Update the price feed. 23 | * 24 | * @param from The address of the sender. 25 | */ 26 | public update(from: Address) { 27 | const validate = async () => { 28 | const registry = new Registry(this.environment, await this.getRegistry()); 29 | 30 | if (!(sameAddress(from, await registry.getOwner()) || sameAddress(from, await this.getUpdater()))) { 31 | throw new OnlyRegistryOwnerOrUpdaterCanCallError(); 32 | } 33 | }; 34 | 35 | return this.createTransaction({ from, method: 'update', validate }); 36 | } 37 | 38 | /** 39 | * Gets the address of the kyber network proxy contract. 40 | * 41 | * @param block The block number to execute the call on. 42 | */ 43 | public getKyberNetworkProxy(block?: number) { 44 | return this.makeCall
('KYBER_NETWORK_PROXY', undefined, block); 45 | } 46 | 47 | /** 48 | * Gets the max spread value. 49 | * 50 | * @param block The block number to execute the call on. 51 | */ 52 | public getMaxSpread(block?: number) { 53 | return this.makeCall
('MAX_SPREAD', undefined, block); 54 | } 55 | 56 | /** 57 | * Gets the address of the registry contract. 58 | * 59 | * @param block The block number to execute the call on. 60 | */ 61 | public getRegistry(block?: number) { 62 | return this.makeCall
('REGISTRY', undefined, block); 63 | } 64 | 65 | /** 66 | * Gets the address of the updater. 67 | * 68 | * @param block The block number to execute the call on. 69 | */ 70 | public getUpdater(block?: number) { 71 | return this.makeCall
('UPDATER', undefined, block); 72 | } 73 | } 74 | 75 | export interface KyberPriceFeed extends IPriceSource {} 76 | applyMixins(KyberPriceFeed, [IPriceSource]); 77 | -------------------------------------------------------------------------------- /src/contracts/prices/TestingPriceFeed.ts: -------------------------------------------------------------------------------- 1 | import { TestingPriceFeedAbi } from '../../abis/TestingPriceFeed.abi'; 2 | import { IPriceSource } from './IPriceSource'; 3 | import { Address } from '../../Address'; 4 | import BigNumber from 'bignumber.js'; 5 | 6 | export interface PriceUpdate { 7 | asset: Address; 8 | price: BigNumber; 9 | } 10 | 11 | export class TestingPriceFeed extends IPriceSource { 12 | public static readonly abi = TestingPriceFeedAbi; 13 | 14 | /** 15 | * Update the price feed. 16 | * 17 | * @param block The block number to execute the call on. 18 | */ 19 | public update(from: Address, updates: PriceUpdate[]) { 20 | const prices = updates.map((item) => item.price.toFixed(0)); 21 | const assets = updates.map((item) => item.asset); 22 | return this.createTransaction({ from, method: 'update', args: [assets, prices] }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/contracts/version/Version.test.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { TestEnvironment, createTestEnvironment } from '../../utils/tests/createTestEnvironment'; 3 | import { randomAddress } from '../../utils/tests/randomAddress'; 4 | import { Version } from './Version'; 5 | import { sameAddress } from '../../utils/sameAddress'; 6 | import { deployVersion } from '../../utils/tests/deployVersion'; 7 | import { Weth } from '../dependencies/token/Weth'; 8 | import { deployWeth } from '../../utils/tests/deployWeth'; 9 | import { deployRegistry } from '../../utils/tests/deployRegistry'; 10 | import { Registry } from './Registry'; 11 | import { range } from '../../utils/range'; 12 | 13 | describe('Version', () => { 14 | let environment: TestEnvironment; 15 | let version: Version; 16 | let registry: Registry; 17 | let weth: Weth; 18 | 19 | beforeAll(async () => { 20 | environment = await createTestEnvironment(); 21 | weth = await deployWeth(environment, environment.accounts[0]); 22 | registry = await deployRegistry(environment, environment.accounts[0], environment.accounts[0]); 23 | version = await deployVersion(environment, environment.accounts[0], registry, weth); 24 | }); 25 | 26 | it('should return the native asset', async () => { 27 | const result = await registry.getNativeAsset(); 28 | expect(sameAddress(result, weth.contract.address)).toBe(true); 29 | }); 30 | 31 | it('should return the address of the registry', async () => { 32 | const result = await version.getRegistry(); 33 | expect(result.startsWith('0x')).toBe(true); 34 | }); 35 | 36 | it('should create a hub (without spokes)', async () => { 37 | const tx = version.beginSetup(environment.accounts[0], { 38 | name: 'version-test-fund', 39 | fees: [randomAddress(), randomAddress()], 40 | feeRates: [new BigNumber(2).multipliedBy('1e16'), new BigNumber(20).multipliedBy('1e16')], 41 | feePeriods: [new BigNumber(0), new BigNumber(90).multipliedBy(60 * 60 * 24)], 42 | exchanges: range(5).map(() => randomAddress()), 43 | adapters: range(5).map(() => randomAddress()), 44 | denominationAsset: weth.contract.address, 45 | defaultAssets: [weth.contract.address], 46 | }); 47 | 48 | const txResult = await tx.send(await tx.prepare()); 49 | expect(txResult.gasUsed).toBeGreaterThan(0); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /src/contracts/version/Version.ts: -------------------------------------------------------------------------------- 1 | import { VersionAbi } from '../../abis/Version.abi'; 2 | import { Contract } from '../../Contract'; 3 | import { FundFactory, FundFactoryDeployArguments } from '../factory/FundFactory'; 4 | import { applyMixins } from '../../utils/applyMixins'; 5 | import { Address } from '../../Address'; 6 | import { Environment } from '../../Environment'; 7 | import { sameAddress } from '../../utils/sameAddress'; 8 | import { ValidationError } from '../../errors/ValidationError'; 9 | 10 | export interface VersionDeployArguments extends Omit { 11 | registry: Address; 12 | postDeployOwner: Address; 13 | } 14 | 15 | export class OnlyManagerCanShutDownFundError extends ValidationError { 16 | public name = 'OnlyManagerCanShutDownFundError'; 17 | 18 | constructor(message: string = 'Only the manager can shutdown their fund.') { 19 | super(message); 20 | } 21 | } 22 | 23 | export class Version extends Contract { 24 | public static readonly abi = VersionAbi; 25 | 26 | public static deploy(environment: Environment, bytecode: string, from: Address, args: VersionDeployArguments) { 27 | return super.createDeployment(environment, bytecode, from, [ 28 | args.accountingFactory, 29 | args.feeManagerFactory, 30 | args.participationFactory, 31 | args.sharesFactory, 32 | args.tradingFactory, 33 | args.vaultFactory, 34 | args.policyManagerFactory, 35 | args.registry, 36 | args.postDeployOwner, 37 | ]); 38 | } 39 | 40 | /** 41 | * Shut down a fund 42 | * 43 | * @param from The address of the sender 44 | * @param hub The address of the fund hub 45 | * @param block The block number to execute the call on. 46 | */ 47 | public shutDownFund(from: Address, hub: Address) { 48 | const validate = async () => { 49 | const managersToHubs = await this.getManagersToHubs(from); 50 | if (!sameAddress(managersToHubs, hub)) { 51 | throw new OnlyManagerCanShutDownFundError(); 52 | } 53 | }; 54 | 55 | return this.createTransaction({ from, method: 'shutDownFund', args: [hub], validate }); 56 | } 57 | } 58 | 59 | export interface Version extends FundFactory {} 60 | applyMixins(Version, [FundFactory]); 61 | -------------------------------------------------------------------------------- /src/errors/CallError.ts: -------------------------------------------------------------------------------- 1 | export class CallError extends Error { 2 | public name = 'CallError'; 3 | 4 | constructor(message?: string) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/errors/HubIsShutdownError.ts: -------------------------------------------------------------------------------- 1 | import { ValidationError } from './ValidationError'; 2 | import { Address } from '../Address'; 3 | 4 | export class HubIsShutdownError extends ValidationError { 5 | public name = 'HubIsShutdownError'; 6 | 7 | constructor(public readonly hub: Address, message: string = 'The fund is not active.') { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/errors/InsufficientAllowanceError.ts: -------------------------------------------------------------------------------- 1 | import { ValidationError } from './ValidationError'; 2 | import BigNumber from 'bignumber.js'; 3 | 4 | export class InsufficientAllowanceError extends ValidationError { 5 | public name = 'InsufficientAllowanceError'; 6 | 7 | constructor( 8 | public readonly amount: BigNumber, 9 | public readonly allowance: BigNumber, 10 | message: string = 'Requested amount exceeds current allowance.', 11 | ) { 12 | super(message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/errors/OutOfBalanceError.ts: -------------------------------------------------------------------------------- 1 | import { ValidationError } from './ValidationError'; 2 | import BigNumber from 'bignumber.js'; 3 | 4 | export class OutOfBalanceError extends ValidationError { 5 | public name = 'OutOfBalanceError'; 6 | 7 | constructor( 8 | public readonly amount: BigNumber, 9 | public readonly balance: BigNumber, 10 | message: string = 'Requested amount exceeds current balance.', 11 | ) { 12 | super(message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/errors/SpokeNotInitializedError.ts: -------------------------------------------------------------------------------- 1 | import { ValidationError } from './ValidationError'; 2 | import { Address } from '../Address'; 3 | 4 | export class SpokeNotInitializedError extends ValidationError { 5 | public name = 'SpokeNotInitializedError'; 6 | 7 | constructor(public readonly spoke: Address, message: string = 'The spoke is not initialized.') { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/errors/ValidationError.ts: -------------------------------------------------------------------------------- 1 | export class ValidationError extends Error { 2 | public name = 'ValidationError'; 3 | 4 | constructor(message?: string) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/errors/ZeroAddressError.ts: -------------------------------------------------------------------------------- 1 | import { ValidationError } from './ValidationError'; 2 | 3 | export class ZeroAddressError extends ValidationError { 4 | public name = 'ZeroAddressError'; 5 | public message = "The address can't be empty (address(0))."; 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/applyMixins.ts: -------------------------------------------------------------------------------- 1 | // @see https://www.typescriptlang.org/docs/handbook/mixins.html 2 | export function applyMixins(derivedCtor: any, baseCtors: any[]) { 3 | baseCtors.forEach((baseCtor) => { 4 | Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => { 5 | if (name === 'constructor') { 6 | return; 7 | } 8 | 9 | Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name)); 10 | }); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/availableExchanges.ts: -------------------------------------------------------------------------------- 1 | import { DeploymentOutput } from '../Deployment'; 2 | 3 | export enum ExchangeIdentifier { 4 | 'MelonEngine' = 'MelonEngine', 5 | 'KyberNetwork' = 'KyberNetwork', 6 | 'Uniswap' = 'Uniswap', 7 | 'OasisDex' = 'OasisDex', 8 | 'ZeroExV2' = 'ZeroExV2', 9 | 'ZeroExV3' = 'ZeroExV3', 10 | } 11 | 12 | export interface ExchangeDefinition { 13 | id: ExchangeIdentifier | string; 14 | name: string; 15 | exchange: string; 16 | adapter: string; 17 | historic: boolean; 18 | } 19 | 20 | export function availableExchanges(deployment: DeploymentOutput): ExchangeDefinition[] { 21 | const exchanges = [ 22 | deployment.melon && { 23 | name: 'Melon Engine (v2)', 24 | id: ExchangeIdentifier.MelonEngine, 25 | exchange: deployment.melon.addr.Engine, 26 | adapter: deployment.melon.addr.EngineAdapter, 27 | historic: false, 28 | }, 29 | deployment.kyber && { 30 | name: 'Kyber Network', 31 | id: ExchangeIdentifier.KyberNetwork, 32 | adapter: deployment.melon.addr.KyberAdapter, 33 | exchange: deployment.kyber.addr.KyberNetworkProxy, 34 | historic: false, 35 | }, 36 | deployment.uniswap && { 37 | name: 'Uniswap', 38 | id: ExchangeIdentifier.Uniswap, 39 | adapter: deployment.melon.addr.UniswapAdapter, 40 | exchange: deployment.uniswap.addr.UniswapFactory, 41 | historic: false, 42 | }, 43 | deployment.oasis && { 44 | name: 'OasisDEX', 45 | id: ExchangeIdentifier.OasisDex, 46 | adapter: deployment.melon.addr.OasisDexAdapter, 47 | exchange: deployment.oasis.addr.OasisDexExchange, 48 | historic: false, 49 | }, 50 | deployment.zeroExV2 && { 51 | name: '0x (v2.1)', 52 | id: ExchangeIdentifier.ZeroExV2, 53 | adapter: deployment.melon.addr.ZeroExV2Adapter, 54 | exchange: deployment.zeroExV2.addr.ZeroExV2Exchange, 55 | historic: false, 56 | }, 57 | deployment.zeroExV3 && { 58 | name: '0x (v3)', 59 | id: ExchangeIdentifier.ZeroExV3, 60 | adapter: deployment.melon.addr.ZeroExV3Adapter, 61 | exchange: deployment.zeroExV3.addr.ZeroExV3Exchange, 62 | historic: false, 63 | }, 64 | ]; 65 | 66 | return [...exchanges].filter((value) => !!(value && value.exchange && value.adapter)); 67 | } 68 | -------------------------------------------------------------------------------- /src/utils/availablePolicies.ts: -------------------------------------------------------------------------------- 1 | import { ExchangeAdapterAbi } from '../abis/ExchangeAdapter.abi'; 2 | import { ParticipationAbi } from '../abis/Participation.abi'; 3 | import { encodeFunctionSignature } from './encodeFunctionSignature'; 4 | 5 | export interface PolicyDefinition { 6 | id: string; 7 | name: string; 8 | signatures: string[]; 9 | historic: boolean; 10 | } 11 | 12 | export function availablePolicies(): PolicyDefinition[] { 13 | const tradingSignatures = [ 14 | encodeFunctionSignature(ExchangeAdapterAbi, 'makeOrder'), 15 | encodeFunctionSignature(ExchangeAdapterAbi, 'takeOrder'), 16 | ]; 17 | 18 | const investmentSignatures = [encodeFunctionSignature(ParticipationAbi, 'requestInvestment')]; 19 | 20 | const policies = [ 21 | { 22 | id: 'priceTolerance', 23 | name: 'Price tolerance', 24 | signatures: [...tradingSignatures], 25 | historic: false, 26 | }, 27 | { 28 | id: 'maxPositions', 29 | name: 'Maximum number of positions', 30 | signatures: [...tradingSignatures, ...investmentSignatures], 31 | historic: false, 32 | }, 33 | 34 | { 35 | id: 'maxConcentration', 36 | name: 'Maximum concentration', 37 | signatures: [...tradingSignatures], 38 | historic: false, 39 | }, 40 | { 41 | id: 'userWhitelist', 42 | name: 'Investor whitelist', 43 | signatures: [...investmentSignatures], 44 | historic: false, 45 | }, 46 | { 47 | id: 'assetWhitelist', 48 | name: 'Asset whitelist', 49 | signatures: [...tradingSignatures, ...investmentSignatures], 50 | historic: false, 51 | }, 52 | { 53 | id: 'assetBlacklist', 54 | name: 'Asset blacklist', 55 | signatures: [...tradingSignatures, ...investmentSignatures], 56 | historic: false, 57 | }, 58 | ] as PolicyDefinition[]; 59 | 60 | return policies; 61 | } 62 | -------------------------------------------------------------------------------- /src/utils/availableTokens.ts: -------------------------------------------------------------------------------- 1 | import { DeploymentOutput } from '../Deployment'; 2 | 3 | export interface TokenDefinition { 4 | symbol: string; 5 | name: string; 6 | address: string; 7 | decimals: number; 8 | historic: boolean; 9 | } 10 | 11 | export function availableTokens(deployment: DeploymentOutput): TokenDefinition[] { 12 | const symbols = Object.keys(deployment.tokens.addr); 13 | const tokens = symbols.map((symbol) => ({ 14 | symbol, 15 | address: deployment.tokens.addr[symbol], 16 | decimals: deployment.tokens.conf[symbol].decimals, 17 | name: deployment.tokens.conf[symbol].name, 18 | historic: false, 19 | })); 20 | 21 | return tokens; 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/encodeFunctionSignature.ts: -------------------------------------------------------------------------------- 1 | import { AbiItem } from 'web3-utils'; 2 | import { AbiCoder } from 'web3-eth-abi'; 3 | 4 | export function encodeFunctionSignature(abi: AbiItem[], name: string): string { 5 | const abiItem = abi.find((item) => item.name === name); 6 | const abiCoder = new AbiCoder(); 7 | return abiCoder.encodeFunctionSignature(abiItem); 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/functionSignature.ts: -------------------------------------------------------------------------------- 1 | import { AbiItem } from 'web3-utils'; 2 | 3 | export function functionSignature(abi: AbiItem[], name: string): string { 4 | const abiItem = abi.find((item) => item.name === name); 5 | return `${abiItem.name}(${abiItem.inputs.map((d) => d.type).join(',')})`; 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/hexToString.ts: -------------------------------------------------------------------------------- 1 | import { toUtf8, hexToNumberString, isHexStrict } from 'web3-utils'; 2 | 3 | export function hexToString(hex: string): string { 4 | if (isHexStrict(hex)) { 5 | try { 6 | return toUtf8(hex); 7 | } catch (e) { 8 | try { 9 | return hexToNumberString(hex); 10 | } catch (e) { 11 | return hex; 12 | } 13 | } 14 | } 15 | 16 | return hex; 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/includesAddress.ts: -------------------------------------------------------------------------------- 1 | import { toChecksumAddress } from 'web3-utils'; 2 | import { Address } from '../Address'; 3 | 4 | export function includesAddress(list: Address[], address: Address) { 5 | const checksums = list.map((item) => toChecksumAddress(item)); 6 | return checksums.includes(toChecksumAddress(address)); 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/isZeroAddress.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../Address'; 2 | import { sameAddress } from './sameAddress'; 3 | 4 | export function isZeroAddress(address: Address) { 5 | return sameAddress(address, '0x0000000000000000000000000000000000000000'); 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/range.ts: -------------------------------------------------------------------------------- 1 | export function range(length: number) { 2 | return Array.from(Array(length).keys()); 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/sameAddress.ts: -------------------------------------------------------------------------------- 1 | import { toChecksumAddress } from 'web3-utils'; 2 | import { Address } from '../Address'; 3 | 4 | export function sameAddress(a?: Address, b?: Address) { 5 | if (a && b && a === b) { 6 | return true; 7 | } 8 | 9 | return a && b && toChecksumAddress(a) === toChecksumAddress(b); 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/stringToBytes.ts: -------------------------------------------------------------------------------- 1 | import { padLeft, stringToHex } from 'web3-utils'; 2 | 3 | export function stringToBytes(str: string, bytes: number) { 4 | return padLeft(stringToHex(str), bytes * 2); 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/tests/createTestEnvironment.ts: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3'; 2 | import { Eth } from 'web3-eth'; 3 | import { Address } from '../../Address'; 4 | import { Environment, EnvironmentOptions } from '../../Environment'; 5 | import { HttpProvider } from 'web3-providers'; 6 | 7 | interface TestEnvironmentOptions extends EnvironmentOptions { 8 | accounts: Address[]; 9 | } 10 | 11 | export class TestEnvironment extends Environment { 12 | public readonly accounts: Address[]; 13 | public readonly gasPrice: string; 14 | 15 | constructor(client: Eth, options: TestEnvironmentOptions) { 16 | super(client, options); 17 | 18 | this.accounts = options.accounts; 19 | } 20 | } 21 | 22 | export async function createTestEnvironment(options?: EnvironmentOptions) { 23 | const port = process.env.GANACHE_PORT || 8555; 24 | const client = new Web3(new HttpProvider(`http://localhost:${port}`), undefined, { 25 | transactionConfirmationBlocks: 1, 26 | }); 27 | 28 | const accounts = await client.eth.getAccounts(); 29 | const environment = new TestEnvironment(client.eth, { 30 | ...options, 31 | accounts, 32 | }); 33 | 34 | return environment; 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/tests/deployAccounting.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../Address'; 2 | import { TestEnvironment } from './createTestEnvironment'; 3 | import { AccountingDeployArguments, Accounting } from '../../contracts/fund/accounting/Accounting'; 4 | import { AccountingBytecode } from '../../abis/Accounting.bin'; 5 | 6 | export async function deployAccounting( 7 | environment: TestEnvironment, 8 | creator: Address, 9 | args: AccountingDeployArguments, 10 | ) { 11 | const deploy = Accounting.deploy(environment, AccountingBytecode, creator, args); 12 | 13 | const out = await deploy.send(await deploy.prepare()); 14 | return out; 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/tests/deployEngine.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../Address'; 2 | import { TestEnvironment } from './createTestEnvironment'; 3 | import { EngineDeployArguments, Engine } from '../../contracts/engine/Engine'; 4 | import { EngineBytecode } from '../../abis/Engine.bin'; 5 | 6 | export async function deployEngine(environment: TestEnvironment, creator: Address, args: EngineDeployArguments) { 7 | const deploy = Engine.deploy(environment, EngineBytecode, creator, args); 8 | return await deploy.send(await deploy.prepare()); 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/tests/deployFeeManager.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../Address'; 2 | import { TestEnvironment } from './createTestEnvironment'; 3 | import { FeeManagerDeployArguments, FeeManager } from '../../contracts/fund/fees/FeeManager'; 4 | import { FeeManagerBytecode } from '../../abis/FeeManager.bin'; 5 | 6 | export async function deployFeeManager( 7 | environment: TestEnvironment, 8 | creator: Address, 9 | args: FeeManagerDeployArguments, 10 | ) { 11 | const deploy = FeeManager.deploy(environment, FeeManagerBytecode, creator, args); 12 | 13 | return await deploy.send(await deploy.prepare()); 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/tests/deployHub.ts: -------------------------------------------------------------------------------- 1 | import { Hub, HubDeployArguments } from '../../contracts/fund/hub/Hub'; 2 | import { Address } from '../../Address'; 3 | import { HubBytecode } from '../../abis/Hub.bin'; 4 | import { TestEnvironment } from './createTestEnvironment'; 5 | 6 | export async function deployHub(environment: TestEnvironment, creator: Address, args: HubDeployArguments) { 7 | const deploy = Hub.deploy(environment, HubBytecode, creator, args); 8 | return await deploy.send(await deploy.prepare()); 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/tests/deployManagementFee.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../Address'; 2 | import { TestEnvironment } from './createTestEnvironment'; 3 | import { ManagementFee } from '../../contracts/fund/fees/ManagementFee'; 4 | import { ManagementFeeBytecode } from '../../abis/ManagementFee.bin'; 5 | 6 | export async function deployManagementFee(environment: TestEnvironment, creator: Address) { 7 | const deploy = ManagementFee.deploy(environment, ManagementFeeBytecode, creator); 8 | return await deploy.send(await deploy.prepare()); 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/tests/deployParticipation.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../Address'; 2 | import { TestEnvironment } from './createTestEnvironment'; 3 | import { ParticipationDeployArguments, Participation } from '../../contracts/fund/participation/Participation'; 4 | import { ParticipationBytecode } from '../../abis/Participation.bin'; 5 | 6 | export async function deployParticipation( 7 | environment: TestEnvironment, 8 | creator: Address, 9 | args: ParticipationDeployArguments, 10 | ) { 11 | const deploy = Participation.deploy(environment, ParticipationBytecode, creator, args); 12 | 13 | return await deploy.send(await deploy.prepare()); 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/tests/deployPerformanceFee.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../Address'; 2 | import { TestEnvironment } from './createTestEnvironment'; 3 | import { PerformanceFee } from '../../contracts/fund/fees/PerformanceFee'; 4 | import { PerformanceFeeBytecode } from '../../abis/PerformanceFee.bin'; 5 | 6 | export async function deployPerformanceFee(environment: TestEnvironment, creator: Address) { 7 | const deploy = PerformanceFee.deploy(environment, PerformanceFeeBytecode, creator); 8 | return await deploy.send(await deploy.prepare()); 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/tests/deployRegistry.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../Address'; 2 | import { TestEnvironment } from './createTestEnvironment'; 3 | import { RegistryBytecode } from '../../abis/Registry.bin'; 4 | import { Registry } from '../../contracts/version/Registry'; 5 | 6 | export async function deployRegistry(environment: TestEnvironment, creator: Address, owner: Address) { 7 | const deploy = Registry.deploy(environment, RegistryBytecode, creator, owner); 8 | return await deploy.send(await deploy.prepare()); 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/tests/deployShares.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../Address'; 2 | import { SharesBytecode } from '../../abis/Shares.bin'; 3 | import { Shares } from '../../contracts/fund/shares/Shares'; 4 | import { TestEnvironment } from './createTestEnvironment'; 5 | 6 | export async function deployShares(environment: TestEnvironment, creator: Address, hub: Address) { 7 | const deploy = Shares.deploy(environment, SharesBytecode, creator, hub); 8 | return await deploy.send(await deploy.prepare()); 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/tests/deploySpoke.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../Address'; 2 | import { TestEnvironment } from './createTestEnvironment'; 3 | import { Spoke } from '../../contracts/fund/hub/Spoke'; 4 | import { SpokeBytecode } from '../../abis/Spoke.bin'; 5 | 6 | export async function deploySpoke(environment: TestEnvironment, creator: Address, hub: Address) { 7 | const deploy = Spoke.deploy(environment, SpokeBytecode, creator, hub); 8 | return await deploy.send(await deploy.prepare()); 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/tests/deployTrading.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../Address'; 2 | import { TestEnvironment } from './createTestEnvironment'; 3 | import { TradingDeployArguments, Trading } from '../../contracts/fund/trading/Trading'; 4 | import { TradingBytecode } from '../../abis/Trading.bin'; 5 | 6 | export async function deployTrading(environment: TestEnvironment, creator: Address, args: TradingDeployArguments) { 7 | const deploy = Trading.deploy(environment, TradingBytecode, creator, args); 8 | 9 | return await deploy.send(await deploy.prepare()); 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/tests/deployVault.ts: -------------------------------------------------------------------------------- 1 | import { Vault } from '../../contracts/fund/vault/Vault'; 2 | import { VaultBytecode } from '../../abis/Vault.bin'; 3 | import { Address } from '../../Address'; 4 | import { TestEnvironment } from './createTestEnvironment'; 5 | 6 | export async function deployVault(environment: TestEnvironment, creator: Address, hub: Address) { 7 | const deploy = Vault.deploy(environment, VaultBytecode, creator, hub); 8 | return await deploy.send(await deploy.prepare()); 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/tests/deployVersion.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../Address'; 2 | import { TestEnvironment } from './createTestEnvironment'; 3 | import { AccountingFactory } from '../../contracts/fund/accounting/AccountingFactory'; 4 | import { AccountingFactoryBytecode } from '../../abis/AccountingFactory.bin'; 5 | import { Version } from '../../contracts/version/Version'; 6 | import { VersionBytecode } from '../../abis/Version.bin'; 7 | import { randomAddress } from './randomAddress'; 8 | import { BigNumber } from 'bignumber.js'; 9 | import { Registry } from '../../contracts/version/Registry'; 10 | import { Weth } from '../../contracts/dependencies/token/Weth'; 11 | import { FeeManagerFactory } from '../../contracts/fund/fees/FeeManagerFactory'; 12 | 13 | export async function deployVersion(environment: TestEnvironment, creator: Address, registry: Registry, weth: Weth) { 14 | const deployAccountingFactory = AccountingFactory.deploy( 15 | environment, 16 | AccountingFactoryBytecode, 17 | environment.accounts[0], 18 | ); 19 | const accountingFactory = await deployAccountingFactory.send(await deployAccountingFactory.prepare()); 20 | 21 | const deployFeeManagerFactory = FeeManagerFactory.deploy( 22 | environment, 23 | AccountingFactoryBytecode, 24 | environment.accounts[0], 25 | ); 26 | const feeManagerFactory = await deployFeeManagerFactory.send(await deployAccountingFactory.prepare()); 27 | 28 | const deploy = Version.deploy(environment, VersionBytecode, creator, { 29 | accountingFactory: accountingFactory.contract.address, 30 | feeManagerFactory: feeManagerFactory.contract.address, 31 | participationFactory: randomAddress(), 32 | policyManagerFactory: randomAddress(), 33 | sharesFactory: randomAddress(), 34 | tradingFactory: randomAddress(), 35 | vaultFactory: randomAddress(), 36 | registry: registry.contract.address, 37 | postDeployOwner: creator, 38 | }); 39 | const version = await deploy.send(await deploy.prepare()); 40 | 41 | { 42 | const tx = registry.registerAsset(creator, { 43 | address: weth.contract.address, 44 | name: 'Test Asset', 45 | symbol: 'TAT', 46 | url: 'https://tat.tat/', 47 | reserveMin: new BigNumber(100000), 48 | standards: [1, 2, 3], 49 | sigs: ['0x30303030'], 50 | }); 51 | await tx.send(await tx.prepare()); 52 | } 53 | 54 | { 55 | const tx = registry.setNativeAsset(creator, weth.contract.address); 56 | await tx.send(await tx.prepare()); 57 | } 58 | 59 | { 60 | const tx = registry.registerVersion(creator, version.contract.address, 'test-version'); 61 | await tx.send(await tx.prepare()); 62 | } 63 | 64 | { 65 | const tx = registry.setMlnToken(creator, randomAddress()); 66 | await tx.send(await tx.prepare()); 67 | } 68 | 69 | return version; 70 | } 71 | -------------------------------------------------------------------------------- /src/utils/tests/deployWeth.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../Address'; 2 | import { Weth } from '../../contracts/dependencies/token/Weth'; 3 | import { WETHBytecode } from '../../abis/WETH.bin'; 4 | import { TestEnvironment } from './createTestEnvironment'; 5 | 6 | export async function deployWeth(environment: TestEnvironment, creator: Address) { 7 | const deploy = Weth.deploy(environment, WETHBytecode, creator); 8 | return await deploy.send(await deploy.prepare()); 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/tests/randomAddress.ts: -------------------------------------------------------------------------------- 1 | import { randomHex } from 'web3-utils'; 2 | import { Address } from '../../Address'; 3 | 4 | export function randomAddress(): Address { 5 | return randomHex(20); 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/toBigNumber.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | 3 | export function toBigNumber(value: any) { 4 | if (BigNumber.isBigNumber(value)) { 5 | return value; 6 | } 7 | 8 | const bn = new BigNumber(`${value}`); 9 | if (bn.isEqualTo('3963877391197344453575983046348115674221700746820753546331534351508065746944')) { 10 | return new BigNumber('NaN'); 11 | } 12 | 13 | return bn; 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/toDate.ts: -------------------------------------------------------------------------------- 1 | import { toBigNumber } from './toBigNumber'; 2 | 3 | export function toDate(timestamp: any) { 4 | const result = toBigNumber(timestamp).multipliedBy(1000).toNumber(); 5 | return new Date(result); 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/tradingSignatureName.ts: -------------------------------------------------------------------------------- 1 | const signatures = { 2 | '0xe51be6e8f1c20394ac4bc9b52300bea4a3697c14c468087e25e8b916b34aa373': 'Take order', 3 | '0x79705be7d675563c1e2321f67e8b325f7dd168f51975b104d5f4588cf7e82725': 'Make order', 4 | '0x613466791ec33946b8819ce34672fed07c05cbddfd8152db7f548a582612dde9': 'Cancel order', 5 | }; 6 | 7 | export function tradingSignatureName(signature: string) { 8 | if (signatures.hasOwnProperty(signature)) { 9 | const key = signature as keyof typeof signatures; 10 | return signatures[key]; 11 | } 12 | 13 | return 'Invalid signature'; 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/zeroAddress.ts: -------------------------------------------------------------------------------- 1 | export const zeroAddress = '0x0000000000000000000000000000000000000000'; 2 | -------------------------------------------------------------------------------- /src/utils/zeroBigNumber.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | 3 | export const zeroBigNumber = new BigNumber(0); 4 | -------------------------------------------------------------------------------- /tests/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '^.+\\.ts$': 'ts-jest', 4 | }, 5 | rootDir: '..', 6 | roots: ['/src'], 7 | globalSetup: '/tests/jest.setup.js', 8 | globalTeardown: '/tests/jest.teardown.js', 9 | setupFilesAfterEnv: ['/tests/jest.timeout.js'], 10 | moduleFileExtensions: ['js', 'ts'], 11 | globals: { 12 | 'ts-jest': { 13 | tsConfig: 'tsconfig.json', 14 | }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /tests/jest.setup.js: -------------------------------------------------------------------------------- 1 | const ganache = require('ganache-core'); 2 | 3 | module.exports = () => { 4 | global.__GANACHE__ = ganache.server(); 5 | global.__GANACHE__.listen(process.env.GANACHE_PORT || 8555); 6 | }; 7 | -------------------------------------------------------------------------------- /tests/jest.teardown.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | global.__GANACHE__.close(); 3 | }; 4 | -------------------------------------------------------------------------------- /tests/jest.timeout.js: -------------------------------------------------------------------------------- 1 | jest.setTimeout(20000); 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "lib": ["es2015", "es2017.object", "es2017.string", "dom"], 6 | "rootDir": "src", 7 | "outDir": "dist", 8 | "declaration": true, 9 | "sourceMap": false, 10 | "skipLibCheck": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": false, 13 | "noImplicitAny": true, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "allowSyntheticDefaultImports": true, 17 | "allowJs": false, 18 | "esModuleInterop": true, 19 | "moduleResolution": "node", 20 | "removeComments": false 21 | }, 22 | "typedocOptions": { 23 | "mode": "modules", 24 | "out": "typedocs", 25 | "exclude": ["**/*.test.ts", "src/index.ts", "src/utils/**", "src/abis/**", "src/deployments/**"], 26 | "excludeExternals": true, 27 | "excludeNotExported": true, 28 | "excludePrivate": true, 29 | "media": "assets", 30 | "gitRevision": "master" 31 | }, 32 | "include": ["src/**/*"] 33 | } 34 | --------------------------------------------------------------------------------