├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── config └── default.json ├── custom_typings ├── system.d.ts ├── web3.d.ts └── web3_global.d.ts ├── docs ├── Configuration.md ├── Daos.md ├── DeveloperDocs.md ├── Events.md ├── GanacheDb.md ├── Proposals.md ├── README.md ├── Scripts.md ├── Transactions.md ├── Wrappers.md ├── index.html └── index.md ├── lib ├── accountService.ts ├── avatarService.ts ├── commonTypes.ts ├── configService.ts ├── contractWrapperBase.ts ├── contractWrapperFactory.ts ├── controllerService.ts ├── dao.ts ├── iConfigService.ts ├── iContractWrapperBase.ts ├── index.ts ├── loggingService.ts ├── promiseEventService.ts ├── proposalGeneratorBase.ts ├── proposalService.ts ├── pubSubEventService.ts ├── schemeWrapperBase.ts ├── scripts │ └── createGenesisDao.ts ├── test │ └── wrappers │ │ └── testWrapper.ts ├── transactionService.ts ├── uSchemeWrapperBase.ts ├── utils.ts ├── utilsInternal.ts ├── web3EventService.ts ├── wrapperService.ts └── wrappers │ ├── absoluteVote.ts │ ├── auction4Reputation.ts │ ├── commonEventInterfaces.ts │ ├── contributionReward.ts │ ├── daoCreator.ts │ ├── daoToken.ts │ ├── externalLocking4Reputation.ts │ ├── fixedReputationAllocation.ts │ ├── genesisProtocol.ts │ ├── globalConstraintRegistrar.ts │ ├── iBurnableToken.ts │ ├── iErc827Token.ts │ ├── iIntVoteInterface.ts │ ├── intVoteInterface.ts │ ├── locking4Reputation.ts │ ├── lockingEth4Reputation.ts │ ├── lockingToken4Reputation.ts │ ├── mintableToken.ts │ ├── redeemer.ts │ ├── reputation.ts │ ├── schemeRegistrar.ts │ ├── standardToken.ts │ ├── tokenCapGC.ts │ ├── upgradeScheme.ts │ ├── vestingScheme.ts │ └── voteInOrganizationScheme.ts ├── mkdocs.yml ├── package-lock.json ├── package-scripts.js ├── package-scripts ├── archiveGanacheDb.js ├── cleanMigrationJson.js ├── createApiPagesList.js ├── createGenesisDao.js ├── fail.js ├── migrateContracts.js ├── recursiveCopy.js ├── typedoc.js └── unArchiveGanacheDb.js ├── package.json ├── test ├── absoluteVote.ts ├── accountService.ts ├── config.ts ├── contributionReward.ts ├── dao.ts ├── daoCreator.ts ├── estimateGas.ts ├── genesisProtocol.ts ├── globalConstraintRegistrar.ts ├── helpers.ts ├── redeemer.ts ├── schemeRegistrar.ts ├── tokens.ts ├── transactionService.ts ├── tsconfig.json ├── upgradeScheme.ts ├── utils.ts ├── vestingScheme.ts ├── voteInOrganizationScheme.ts ├── web3EventService.ts └── wrapperService.ts ├── truffle.js ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .node-xmlhttprequest-sync* 3 | .node-xmlhttprequest-content* 4 | *.tgz 5 | .vscode/ 6 | ganacheDb/ 7 | dist/ 8 | ganacheDb.zip 9 | migrated_contracts/ 10 | *.sublime-project 11 | yarn.lock 12 | test-build/ 13 | site/ 14 | docs/api/ 15 | .vs/ 16 | migration.json 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - "9.3.0" 7 | 8 | before_install: 9 | - sudo apt-get update -qq 10 | - sudo apt-get install software-properties-common -y -qq 11 | - sudo add-apt-repository -y ppa:ethereum/ethereum 12 | - sudo add-apt-repository -y ppa:ethereum/ethereum-dev 13 | - sudo apt-get update -qq 14 | - sudo apt-get install geth -y -qq 15 | 16 | install: 17 | - npm install 18 | - nohup npm start ganache & 19 | - npm start migrateContracts.fetchContracts 20 | - npm start migrateContracts 21 | 22 | script: 23 | - npm start lint 24 | - npm start test 25 | 26 | notifications: 27 | slack: daostack:fGuaFPsiQiV5mgmzRcSzbYqw 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | ___ 3 | 4 | Firstly, thanks for wanting to help with the development of DAOSTACK. All contributions, code or documents, should come from a forked version of the respective repository. Then the proposed changes must be submitted via a pull request to the master branch. All pull requests must be reviewed by the maintainers of the repository in question. Once a pull request has been reviewed & approved; you should merge and rebase, and then delete the branch. 5 | GitHub [keywords](https://help.github.com/articles/closing-issues-using-keywords/) should be used when closing pull requests and issues. 6 | 7 | If you wish to submit more substantial changes or additions, please see the feature contributions section below. 8 | 9 | 10 | ## Git Practice 11 | 12 | Branches should be named with a brief semantic title. 13 | Commit messages should be capitalised and follow these rules: 14 | ``` 15 | Short (50 chars or less) summary of changes 16 | 17 | More detailed explanatory text, if necessary. Wrap it to about 72 18 | characters or so. In some contexts, the first line is treated as the 19 | subject of an email and the rest of the text as the body. The blank 20 | line separating the summary from the body is critical (unless you omit 21 | the body entirely); tools like rebase can get confused if you run the 22 | two together. 23 | 24 | Further paragraphs come after blank lines. 25 | 26 | - Bullet points are okay, too 27 | 28 | - Typically a hyphen or asterisk is used for the bullet, preceded by a 29 | single space, with blank lines in between, but conventions vary here 30 | 31 | Issue: #1, #2 32 | ``` 33 | A properly formed Git commit subject line should always be able to complete the following sentence: 34 | 35 | If applied, this commit will _Your subject line here_ 36 | 37 | **Please refer to [this guide](https://chris.beams.io/posts/git-commit/) for additional information.** 38 | 39 | 40 | ## Feature Contributions 41 | 42 | For the submission of more substantial changes or additions, an issue should be opened outlining what is being proposed for implementation. The title of the issue should be descriptive and brief, follow the same rules as a commit message, as outlined above. The body of the issue should detail the reasoning for the need of the work to be carried out and the desired outcome. 43 | 44 | 45 | ## Code Formatting and Commentary 46 | 47 | ### JavaScript 48 | All JavaScript must be formatted with [ESLint](https://eslint.org/) using the DAOSTACK [configuration](https://github.com/daostack/arc.js/blob/master/.eslintrc.json). 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://api.travis-ci.org/daostack/arc.js.svg?branch=master)](https://travis-ci.org/daostack/arc.js) 2 | [![NPM Package](https://img.shields.io/npm/v/@daostack/arc.js.svg?style=flat-square)](https://www.npmjs.org/package/@daostack/arc.js) 3 | 4 | # DAOstack Arc.js 5 | 6 | [DAOstack Arc.js](https://github.com/daostack/arc.js) sits just above [DAOstack Arc](https://github.com/daostack/arc) on the DAO stack. It is a library that facilitates javascript application access to the DAOstack Arc ethereum smart contracts. 7 | 8 | For more information about Arc contracts and the entire DAOstack ecosystem, please refer to the [Arc documentation](https://daostack.github.io/arc/README/). 9 | 10 | ### Documentation 11 | Check out the [complete documentation on Arc.js](https://daostack.github.io/arc.js). 12 | 13 | ## Contribute to Arc.js 14 | 15 | PRs are welcome but please first consult with the [Contribution guide](https://github.com/daostack/arc/blob/master/CONTRIBUTING.md). 16 | 17 | Refer to the [Arc.js Developer Documentation](docs/DeveloperDocs.md). 18 | 19 | Join us on [Slack](https://daostack.slack.com/)! 20 | 21 | Join us on [Telegram](https://t.me/daostackcommunity)! 22 | 23 | ## Security 24 | DAOstack Arc.js is still on its alpha version. It is meant to provide secure, tested and community-audited code, but please use common sense when doing anything that deals with real money! We take no responsibility for your implementation decisions and any security problem you might experience. 25 | 26 | ## License 27 | This is an open source project ([GPL license](https://github.com/daostack/arc.js/blob/master/LICENSE)). 28 | -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "autoApproveTokenTransfers": true, 3 | "defaultVotingMachine": "AbsoluteVote", 4 | "cacheContractWrappers": true, 5 | "logLevel": 9, 6 | "estimateGas": false, 7 | "defaultGasLimit": 4543760, 8 | "gasPriceAdjustor": null, 9 | "txDepthRequiredForConfirmation": { 10 | "default": 7, 11 | "ganache": 0, 12 | "live": 20 13 | }, 14 | "providerPort": 8545, 15 | "providerUrl": "127.0.0.1", 16 | "networkDefaults": { 17 | "live": { 18 | "host": "127.0.0.1", 19 | "port": 8546 20 | }, 21 | "private": { 22 | "host": "127.0.0.1", 23 | "port": 8545 24 | }, 25 | "ganache": { 26 | "host": "127.0.0.1", 27 | "port": 8545 28 | }, 29 | "ropsten": { 30 | "host": "127.0.0.1", 31 | "port": 8548 32 | }, 33 | "kovan": { 34 | "host": "127.0.0.1", 35 | "port": 8547 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /custom_typings/system.d.ts: -------------------------------------------------------------------------------- 1 | declare module "system" { 2 | global { 3 | var window: Window; 4 | var artifacts: any; 5 | } 6 | } 7 | 8 | declare module "*.json" { 9 | const value: any; 10 | export default value; 11 | } 12 | -------------------------------------------------------------------------------- /custom_typings/web3_global.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable-next-line:no-reference */ 2 | /// 3 | declare module "web3" { 4 | global { 5 | let web3: Web3; 6 | let accounts: Array; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/DeveloperDocs.md: -------------------------------------------------------------------------------- 1 | # Arc.js Architecture Review 2 | 3 | The following is a brief sketch of the primary structures of the code in Arc.js. 4 | 5 | Git repository is [here](https://github.com/daostack/arc.js). 6 | 7 | User documentation is [here](https://daostack.github.io/arc.js). 8 | 9 | Both code and automated tests are written in TypeScript. 10 | 11 | Code standards are enforced by TsLint rules defined in [tslint.json](https://github.com/daostack/arc.js/blob/master/tslint.json). 12 | 13 | User documentation is generated using [TypeDoc](http://typedoc.org/) and [MkDocs](https://www.mkdocs.org/). Typedocs is configured and executed using [typedoc.js](https://github.com/daostack/arc.js/blob/master/package-scripts/typedoc.js). MkDocs is configured in [mkdocs.yml](https://github.com/daostack/arc.js/blob/master/mkdocs.yml). 14 | 15 | While some scripts are available in package.json, all are defined in [package-scripts.js](https://github.com/daostack/arc.js/blob/master/package-scripts.js). Package-script.js leverages [nps](https://github.com/kentcdodds/nps) and defers to several custom javascript node scripts contained [here](https://github.com/daostack/arc.js/tree/master/package-scripts). 16 | 17 | Code is located in the [lib folder](https://github.com/daostack/arc.js/tree/master/lib), tests under [test](https://github.com/daostack/arc.js/tree/master/test). 18 | 19 | Most of the code modules define either an Arc contract wrapper class or a service class. 20 | 21 | Arc contract wrapper classes are all located under [lib/wrappers](https://github.com/daostack/arc.js/tree/master/lib/wrappers). 22 | 23 | Service classes are all located in lib (though there is a [ticket to move them](https://github.com/daostack/arc.js/issues/208)) 24 | 25 | More on wrappers and services follows. 26 | 27 | ## Installation 28 | 29 | When you first clone the arc.js repo, run the script: 30 | 31 | ```script 32 | npm install 33 | npm start migrateContracts.fetchContracts 34 | ``` 35 | 36 | This will install the Truffle artifact files from Arc and the migration.json file from [DAOstack Migrations](https://github.com/daostack/migration). 37 | 38 | 39 | ## Arc Contract Wrappers 40 | Every Arc contract wrapper class has as its root base the [ContractWrapperBase](https://github.com/daostack/arc.js/blob/master/lib/contractWrapperBase.ts) class. 41 | 42 | Several classes inherit from `ContractWrapperBase`, including: 43 | 44 | * [IntVoteInterfaceWrapper](https://github.com/daostack/arc.js/blob/master/lib/intVoteInterfaceWrapper.ts) 45 | * [SchemeWrapperBase](https://github.com/daostack/arc.js/blob/master/lib/schemeWrapperBase.ts) 46 | * [USchemeWrapperBase](https://github.com/daostack/arc.js/blob/master/lib/uSchemeWrapperBase.ts) 47 | * [ProposalGeneratorBase](https://github.com/daostack/arc.js/blob/master/lib/proposalGeneratorBase.ts) 48 | 49 | 50 | Each wrapper can be instantiated and hydrated using the [ContractWrapperFactory class](https://github.com/daostack/arc.js/blob/master/lib/contractWrapperFactory.ts). The word “hydrated” means to initialize a wrapper instance with information from the chain using `.new`, `.at` or `.deployed`. 51 | 52 | Not all wrapper classes inherit directly from `ContractWrapperBase`. The two voting machine classes inherit from [IntVoteInterfaceWrapper](https://github.com/daostack/arc.js/blob/master/lib/wrappers/intVoteInterface.ts) which in turn inherits from `ContractWrapperBase`. 53 | 54 | ## Other classes 55 | 56 | **[utils.ts](https://github.com/daostack/arc.js/blob/master/lib/utils.ts)** - provides miscellaneous functionality, including initializing `web3`, creating a truffle contract from a truffle contract artifact (json) file, and others. 57 | 58 | **[utilsInternal.ts](https://github.com/daostack/arc.js/blob/master/lib/utilsInternal.ts)** -- internal helper functions not exported to the client. 59 | 60 | **[Dao.ts](https://github.com/daostack/arc.js/blob/master/lib/dao.ts)** -- not a wrapper, nor defined as a service, more like an entity, it provides helper functions for DAOs, particularly `DAO.new` and `DAO.at`. 61 | 62 | ## Arc.js initialization 63 | 64 | Arc.js typings are available to application via [index.ts](https://github.com/daostack/arc.js/blob/master/lib/index.ts). 65 | 66 | At runtime, applications must initialize Arc.js by calling `InitializeArcJs` which is defined in [index.ts](https://github.com/daostack/arc.js/blob/master/lib/index.ts). This might be viewed as the entry-point to Arc.js. 67 | 68 | ## Migrations 69 | Arc.js uses the [DAOstack Migrations](https://github.com/daostack/migration) package to migrate contracts to Ganache, and as a source of Arc contract addresses as migrated to the various networks and to Ganache after running the migration script that Arc.js provides. These addresses are stored in "/migration.json". 70 | 71 | !!! note 72 | As of this writing, the DAOstack Migration package only includes Ganache addresses. 73 | 74 | ## Scripts 75 | 76 | 77 | ### Build 78 | 79 | Build the distributable code like this: 80 | 81 | ```script 82 | npm start build 83 | ``` 84 | 85 | Build the test code like this: 86 | 87 | ```script 88 | npm start test.build 89 | ``` 90 | 91 | ### Lint 92 | 93 | Run lint on both library and test code like this: 94 | 95 | ```script 96 | npm start lint 97 | ``` 98 | 99 | !!! info 100 | The above script runs `npm start lint.code` and `npm start lint.test` 101 | 102 | To lint and fix: 103 | 104 | ```script 105 | npm start lint.andFix 106 | ``` 107 | 108 | !!! info 109 | You can also fix code and test separately: `npm start lint.code.andFix` and `npm start lint.test.andFix` 110 | 111 | 112 | ### Tests 113 | 114 | To run the Arc.js tests, run the following script in the Arc.js root folder, assuming you have already run `npm install`, and are running a ganache with migrated Arc contracts (see "Getting Started" in the [Arc.js Documentation](https://daostack.github.io/arc.js)): 115 | 116 | ```script 117 | npm start test 118 | ``` 119 | 120 | This script builds all of the code and runs all of the tests. 121 | 122 | !!! info 123 | Both application and test code are written in TypeScript. 124 | 125 | #### Stop tests on the first failure 126 | 127 | ```script 128 | npm start test.bail 129 | ``` 130 | 131 | #### Run tests defined in a single test module 132 | 133 | Sometimes you want to run just a single test module: 134 | 135 | ```script 136 | npm start "test.run test-build/test/[filename]" 137 | ``` 138 | 139 | To bail: 140 | 141 | ```script 142 | npm start "test.run --bail test-build/test/[filename]" 143 | ``` 144 | 145 | Unlike `test`, the script `test.run` does not build the code first, it assumes the code has already been built, which you can do like this: 146 | 147 | ```script 148 | npm start test.build 149 | ``` 150 | 151 | ### Build Documentation 152 | 153 | Build the documentation like this: 154 | 155 | ```script 156 | npm start docs.build 157 | ``` 158 | 159 | Preview the documentation: 160 | 161 | ```script 162 | npm start docs.build.andPreview 163 | ``` 164 | 165 | Publish the documentation: 166 | 167 | ```script 168 | npm start docs.build.andPublish 169 | ``` 170 | -------------------------------------------------------------------------------- /docs/GanacheDb.md: -------------------------------------------------------------------------------- 1 | # Running Arc.js Against a Ganache database 2 | 3 | It can be very handy to run Arc.js tests or your application against a Ganache database that is a snapshot of the chain at any given point. Here's how, assuming you are running the script from your application (which is why you see "`npm explore @daostack/arc.js -- `" prepended to each script command). 4 | 5 | !!! note 6 | These instructions are very similar to those you would use when [_not_ running Ganache against a database](index.md#migratetoganache). 7 | 8 | ### Start Ganache 9 | 10 | First you want to run Ganache with the appropriate flags that will create a database. 11 | 12 | ```script 13 | npm explore @daostack/arc.js -- npm start ganacheDb 14 | ``` 15 | 16 | You can use this same command when you a restarting Ganache against a pre-populated database. 17 | 18 | ### Migrate Contracts 19 | 20 | Now migrate the Arc contracts. You only absolutely need to do this when you are starting from scratch with a new database, but you can do it whenever you wish. 21 | 22 | ```script 23 | npm explore @daostack/arc.js -- npm start ganacheDb.migrateContracts 24 | ``` 25 | 26 | ### Terminate Ganache 27 | To save the current state so that you can restore it later in cases where the database has become no longer useful, manually, in your own OS, terminate the Ganache process you spawned above. 28 | 29 | ### Zip the Ganache Database 30 | If you want you can zip the database for later reuse when you wish to restore a database to the zipped snapshot. 31 | 32 | ```script 33 | npm explore @daostack/arc.js -- npm start ganacheDb.zip 34 | ``` 35 | 36 | At this point you can restart Ganache as above and it will recommence from the point represented in the zipped snapshot. 37 | 38 | ### Restore Ganache Snapshot 39 | 40 | After running against the database you may want to restart Ganache, recommencing at the point at which you [zipped up a snapshot](#zip-the-ganache-database). 41 | 42 | First make sure you have [terminated Ganache](#terminate-ganache), then unzip the database: 43 | 44 | ```script 45 | npm explore @daostack/arc.js -- npm start ganacheDb.restoreFromZip 46 | ``` 47 | Now when you restart ganacheDb it will be running against the previously-zipped snapshot. 48 | 49 | ### Start Clean 50 | To start again fully from scratch, an empty database, you can clean out the pre-existing database. Note this can take a long time as there may be thousands of files to delete: 51 | 52 | ```script 53 | npm explore @daostack/arc.js -- npm start ganacheDb.clean 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # DAOstack Arc.js Documentation Repository 2 | 3 | **Attention**: Viewing documentation directly in the Arc.js GitHub repository is not supported as a means of obtaining Arc.js documentation online; you will find that not all of the links work and some of the styling doesn't appear the way it is supposed to. 4 | 5 | Much better is the [official Arc.js documentation site](https://daostack.github.io/arc.js). 6 | 7 | -------------------------------------------------------------------------------- /docs/Scripts.md: -------------------------------------------------------------------------------- 1 | # Running Arc.js Scripts 2 | Arc.js contains a set of scripts for building, publishing, running tests and migrating contracts to any network. These scripts are meant to be accessible to and readily usable by client applications. 3 | 4 | Typically an application that has installed the Arc.js `npm` package will run an Arc.js script by prefixing "`npm explore @daostack/arc.js -- `" to the name Arc.js script command. For example, to run the Arc.js script `npm start ganache` from your application, you would run: 5 | 6 | ```script 7 | npm explore @daostack/arc.js -- npm start ganache 8 | ``` 9 | 10 | Otherwise, when running the scripts at the root of an Arc.js repo, you must omit the `npm explore @daostack/arc.js -- ` so it looks like this. 11 | 12 | ```script 13 | npm start ganache 14 | ``` 15 | 16 | !!! info "nps" 17 | Other scripts not described here are defined in `package-scripts.js` that is used to configure a tool called [nps](https://www.npmjs.com/package/nps). Arc.js uses `nps` run all of its scripts. While `nps` is installed locally by Arc.js, you can also install it globally and then substitute `npm start` with `nps`, so, when running scripts from the root of an Arc.js repo, it looks like this: 18 | 19 | ```script 20 | nps ganache 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/Wrappers.md: -------------------------------------------------------------------------------- 1 | # Arc Contract Wrappers 2 | 3 | ## Overview 4 | 5 | Arc.js wraps several Arc contracts in a "contract wrapper" JavaScript class. Every wrapper class inherits ultimately from [ContractWrapperBase](/arc.js/api/classes/ContractWrapperBase) providing a common set of functions and properties and specific helper functions for operations specific to the contract it wraps. 6 | 7 | Each wrapper contains some basic properties: 8 | 9 | - `name` - the name of the wrapped Arc contract 10 | - `friendlyName` - a more friendly name of the Arc contract 11 | - `address` - the address of the wrapped Arc contract 12 | - `contract` - the original "wrapped" [Truffle contract](https://github.com/trufflesuite/truffle-contract) that you can use to access all of the Truffle and Web3 functionality of the specific Arc contract being wrapped. 13 | - `factory` - a static instance of a wrapper factory class based on [ContractWrapperFactory<TWrapper>](/arc.js/api/classes/ContractWrapperFactory) (where `TWrapper` is the type (class) of the wrapper). Each factory contains static methods: 14 | - `at(someAddress)` 15 | - `new()` 16 | - `deployed()` 17 | 18 | ... that you can use to instantiate the associated wrapper class. 19 | 20 | ### Events 21 | Each wrapper includes the wrapped contract's events as properties that give you enhanced capabilities over the straight Truffle/Web3 event API. For more information about wrapped contract events, see [Web3 Events](#web3events). 22 | 23 | 24 | 25 | ### Types of Wrappers 26 | 27 | Arc contracts and associated Arc.js contract wrapper classes are categorized as follows: 28 | 29 | **Universal Schemes** 30 | 31 | * ContributionReward 32 | * GlobalConstraintRegistrar 33 | * SchemeRegistrar 34 | * UpgradeScheme 35 | * VestingScheme 36 | * VoteInOrganizationScheme 37 | 38 | **Voting Machines** 39 | 40 | * AbsoluteVote 41 | * GenesisProtocol 42 | 43 | **Global Constraints** 44 | 45 | * TokenCapGC 46 | 47 | **Others** 48 | 49 | * DaoCreator 50 | * Redeemer 51 | 52 | See more at [Enumerate wrappers by contract type](#wrappersByContractType). 53 | 54 | ### Obtaining Wrappers 55 | 56 | Arc.js provides multiple ways to obtain contract wrappers, each optimal for particular use cases. It all starts with the [WrapperService](/arc.js/api/classes/WrapperService) which provides means of organizing and obtaining contract wrappers. The `WrapperService` API is primarily in the form of four static properties, each of which are exported for easy import in your code: 57 | 58 | 59 | Export | WrapperService property | Description 60 | ---------|----------|--------- 61 | ContractWrappers | WrapperService.wrappers | Properties are contract names, values are the corresponding contract wrapper 62 | ContractWrapperFactories | WrapperService.factories | Properties are contract names, values are the corresponding contract wrapper factory 63 | ContractWrappersByType | WrapperService.wrappersByType | Properties are a contract category name (see [Contract Types](#contracttypes)), values are an array of `IContractWrapper` 64 | ContractWrappersByAddress | WrapperService.wrappersByAddress | a `Map` where the key is an address and the associated value is a `IContractWrapper` for a contract as deployed by the currently-running version of Arc.js. 65 | 66 | The following sections describe how to obtain wrapper classes in several use cases: 67 | 68 | - [get a deployed wrapper by the Arc contract name](#get-a-deployed-wrapper-by-name) 69 | - [get a wrapper at a given address](#get-a-wrapper-at-a-given-address) 70 | - [deploy a new contract](#deploy-a-new-contract) 71 | - [enumerate all of the deployed wrappers](#enumerate-all-of-the-deployed-wrappers) 72 | - [enumerate wrappers by contract type](#wrappersByContractType) 73 | 74 | 75 | !!! note "Keep in mind" 76 | In Arc.js all token and reputation amounts should always be expressed in Wei, either as a `string` or a `BigNumber`. 77 | 78 | 79 | ## Get a deployed wrapper by Arc contract name 80 | 81 | You can obtain, by its Arc contract name, any wrapper deployed by the running version of Arc.js: 82 | 83 | ```javascript 84 | import { ContractWrappers } from "@daostack/arc.js"; 85 | const upgradeScheme = ContractWrappers.UpgradeScheme; 86 | ``` 87 | 88 | 89 | ## Get a wrapper at a given address 90 | 91 | You can use a wrapper's factory class to obtain a wrapper for a contract deployed to any given address: 92 | 93 | ```javascript 94 | import { UpgradeSchemeFactory} from "@daostack/arc.js"; 95 | const upgradeScheme = await UpgradeSchemeFactory.at(someAddress); 96 | ``` 97 | 98 | !!! info 99 | `.at` will return `undefined` if it can't find the contract at the given address. 100 | 101 | Another way to get a wrapper at a given address is using [WrapperService.getContractWrapper](/arc.js/api/classes/WrapperService#getContractWrapper). This is most useful when you have a contract name 102 | and may or may not have an address and wish to most efficiently return the associated wrapper, or undefined when not found: 103 | 104 | ```javascript 105 | import { WrapperService } from "@daostack/arc.js"; 106 | // returns undefined when not found, unlike the factory `.at` which throws an exception 107 | const upgradeScheme = await WrapperService.getContractWrapper("UpgradeScheme", someAddressThatMayBeUndefined); 108 | } 109 | ``` 110 | 111 | 112 | ## Deploy a new contract 113 | 114 | You can use a wrapper's factory class to deploy a new instance of a contract and obtain a wrapper for it: 115 | 116 | ```javascript 117 | import { UpgradeSchemeFactory} from "@daostack/arc.js"; 118 | const newUpgradeScheme = await UpgradeSchemeFactory.new(); 119 | ``` 120 | 121 | 122 | ## Enumerate all of the deployed wrappers 123 | 124 | You can enumerate all of the wrappers of contracts deployed by the running version of Arc.js: 125 | 126 | ```javascript 127 | import { ContractWrappers } from "@daostack/arc.js"; 128 | for (var wrapper in ContractWrappers) { 129 | console.log(`${wrapper.friendlyName} is at ${wrapper.address}`); 130 | } 131 | ``` 132 | 133 | 134 | ## Enumerate wrappers by contract type 135 | 136 | You can enumerate the wrappers by contract category, for example, universalSchemes: 137 | 138 | ```javascript 139 | import { ContractWrappersByType } from "@daostack/arc.js"; 140 | for (var schemeWrapper of ContractWrappersByType.universalSchemes) { 141 | console.log(`${schemeWrapper.friendlyName} is at ${schemeWrapper.address}`); 142 | } 143 | ``` 144 | 145 | The set of contract categories is defined in [ArcWrappersByType](/arc.js/api/interfaces/ArcWrappersByType). 146 | 147 | ## Can't Find What You Need? 148 | 149 | Arc.js doesn't wrap every Arc contact nor give you a helper class for everything, but it does give you some more options described in the following sections. 150 | 151 | ### Truffle Contracts and Web3 152 | 153 | Under the hood Arc.js uses Truffle contracts and `Web3`. When you find that Arc.js doesn't directly provide you a piece of information or functionality that you need, you might be able to use them to find what you want. You can obtain `Web3` via [Utils.getWeb3](/arc.js/api/classes/Utils#getWeb3) and the Truffle contract associated with each contract wrapper instance via the `contract` property on each wrapper class. 154 | 155 | !!! info "More on `Web3` and Truffle contracts" 156 | - [Web3](https://github.com/ethereum/wiki/wiki/JavaScript-API) 157 | - [Truffle contracts](https://github.com/trufflesuite/truffle-contract) 158 | 159 | ### Undeployed Arc Contracts 160 | 161 | Some Arc contracts are wrapped but not deployed by Arc.js, for example `DaoToken` and others. `ContractWrappers` (`WrapperService.wrappers`) will not contain entries for these wrappers since they are not deployed. But you will find their factories where you can use `.at`. or `.new`. 162 | 163 | ### Unwrapped Arc Contracts 164 | 165 | Not all Arc contracts have been given wrapper classes, for example, `Avatar`, `UController` and many more. But using `Utils.requireContract` you can obtain a raw [Truffle contract](https://github.com/trufflesuite/truffle-contract) for any contract, enabling you to work with the contract just by providing the name of the Arc contract: 166 | 167 | ```javascript 168 | import { Utils } from "@daostack/arc.js"; 169 | const avatarTruffleContract = await Utils.requireContract("Avatar"); 170 | ``` 171 | 172 | !!! info 173 | `Utils.requireContract` throws an exception when there is any problem creating the truffle contract object. 174 | 175 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lib/accountService.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from "es6-promisify"; 2 | import { Address } from "./commonTypes"; 3 | import { LoggingService } from "./loggingService"; 4 | import { IEventSubscription, PubSubEventService } from "./pubSubEventService"; 5 | import { Utils } from "./utils"; 6 | 7 | /** 8 | * Watch for changes in the default account. 9 | * 10 | * For more information, see [Account Changes](/Configuration.md#accountchanges). 11 | */ 12 | export class AccountService { 13 | 14 | public static AccountChangedEventTopic: string = "AccountService.account.changed"; 15 | public static NetworkChangedEventTopic: string = "AccountService.network.changed"; 16 | 17 | /** 18 | * Initializes the system that watches for default account changes. 19 | * 20 | * `initiateAccountWatch` is called automatically by Arc.js when you pass `true` 21 | * for `watchForAccountChanges` to `InitializeArcJs`. You may also call it manually yourself. 22 | * 23 | * Then you may request to be notified whenever the current account changes by calling 24 | * [AccountService.subscribeToAccountChanges](/arc.js/api/classes/AccountService#subscribeToAccountChanges) 25 | */ 26 | public static async initiateAccountWatch(): Promise { 27 | 28 | if (AccountService.accountChangedTimerId) { 29 | return; 30 | } 31 | 32 | LoggingService.info("Initiating account watch"); 33 | 34 | if (!AccountService.currentAccount) { 35 | try { 36 | AccountService.currentAccount = await Utils.getDefaultAccount(); 37 | } catch { 38 | AccountService.currentAccount = undefined; 39 | } 40 | } 41 | 42 | AccountService.accountChangedTimerId = setInterval(async () => { 43 | 44 | if (AccountService.accountChangedLock) { 45 | return; // prevent reentrance 46 | } 47 | 48 | AccountService.accountChangedLock = true; 49 | 50 | let currentAccount = AccountService.currentAccount; 51 | try { 52 | currentAccount = await Utils.getDefaultAccount(); 53 | } catch { 54 | currentAccount = undefined; 55 | } 56 | if (currentAccount !== AccountService.currentAccount) { 57 | AccountService.currentAccount = currentAccount; 58 | LoggingService.info(`Account watch: account changed: ${currentAccount}`); 59 | PubSubEventService.publish(AccountService.AccountChangedEventTopic, currentAccount); 60 | } 61 | AccountService.accountChangedLock = false; 62 | }, 1000); 63 | } 64 | 65 | /** 66 | * Initializes the system that watches for blockchain network id changes. 67 | * 68 | * `initiateNetworkWatch` is called automatically by Arc.js when you pass `true` 69 | * for `watchForNetworkChanges` to `InitializeArcJs`. You may also call it manually yourself. 70 | * 71 | * Then you may request to be notified whenever the current account changes by calling 72 | * [AccountService.subscribeToNetworkChanges](/arc.js/api/classes/AccountService#subscribeToNetworkChanges) 73 | * 74 | * When the network is found to have changed you should call `InitializeArcJs` so Arc.js will set 75 | * itself up with the new network and return to you a new `Web3` object. 76 | */ 77 | public static async initiateNetworkWatch(): Promise { 78 | 79 | if (AccountService.networkChangedTimerId) { 80 | return; 81 | } 82 | 83 | LoggingService.info("Initiating account watch"); 84 | 85 | if (!AccountService.currentNetworkId) { 86 | try { 87 | AccountService.currentNetworkId = await AccountService.getNetworkId(); 88 | } catch { 89 | AccountService.currentNetworkId = undefined; 90 | } 91 | } 92 | 93 | AccountService.networkChangedTimerId = setInterval(async () => { 94 | 95 | if (AccountService.networkChangedLock) { 96 | return; // prevent reentrance 97 | } 98 | 99 | AccountService.networkChangedLock = true; 100 | 101 | let currentNetworkId = AccountService.currentNetworkId; 102 | try { 103 | currentNetworkId = await AccountService.getNetworkId(); 104 | } catch { 105 | currentNetworkId = undefined; 106 | } 107 | if (currentNetworkId !== AccountService.currentNetworkId) { 108 | AccountService.currentNetworkId = currentNetworkId; 109 | LoggingService.info(`Network watch: network changed: ${currentNetworkId}`); 110 | PubSubEventService.publish(AccountService.NetworkChangedEventTopic, currentNetworkId); 111 | } 112 | AccountService.networkChangedLock = false; 113 | }, 1000); 114 | } 115 | /** 116 | * Turn off the system that watches for default account changes. 117 | */ 118 | public static endAccountWatch(): void { 119 | if (AccountService.accountChangedTimerId) { 120 | clearInterval(AccountService.accountChangedTimerId); 121 | AccountService.accountChangedTimerId = undefined; 122 | } 123 | } 124 | 125 | /** 126 | * Turn off the system that watches for default account changes. 127 | */ 128 | public static endNetworkWatch(): void { 129 | if (AccountService.networkChangedTimerId) { 130 | clearInterval(AccountService.networkChangedTimerId); 131 | AccountService.networkChangedTimerId = undefined; 132 | } 133 | } 134 | /** 135 | * Subscribe to be notified whenever the current account changes, like this: 136 | * 137 | * ```typescript 138 | * AccountService.subscribeToAccountChanges((account: Address): void => { ... }); 139 | * ``` 140 | * @param callback 141 | * @returns A subscription to the event. Unsubscribe by calling `[theSubscription].unsubscribe()`. 142 | */ 143 | public static subscribeToAccountChanges(callback: (address: Address) => void): IEventSubscription { 144 | return PubSubEventService.subscribe(AccountService.AccountChangedEventTopic, 145 | (topic: string, address: Address): any => callback(address)); 146 | } 147 | 148 | /** 149 | * Subscribe to be notified whenever the current network changes, like this: 150 | * 151 | * ```typescript 152 | * AccountService.subscribeToAccountChanges((networkId: number): void => { ... }); 153 | * ``` 154 | * @param callback 155 | * @returns A subscription to the event. Unsubscribe by calling `[theSubscription].unsubscribe()`. 156 | */ 157 | public static subscribeToNetworkChanges(callback: (networkId: number) => void): IEventSubscription { 158 | return PubSubEventService.subscribe(AccountService.NetworkChangedEventTopic, 159 | (topic: string, networkId: number): any => callback(networkId)); 160 | } 161 | 162 | private static currentAccount: Address | undefined; 163 | private static currentNetworkId: number | undefined; 164 | private static accountChangedLock: boolean = false; 165 | private static accountChangedTimerId: any; 166 | private static networkChangedLock: boolean = false; 167 | private static networkChangedTimerId: any; 168 | 169 | private static async getNetworkId(): Promise { 170 | const web3 = await Utils.getWeb3(); 171 | return web3 ? 172 | Number.parseInt(await promisify(web3.version.getNetwork)() as string, 10) as number | undefined : undefined; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /lib/avatarService.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "bignumber.js"; 2 | import { promisify } from "es6-promisify"; 3 | import { Address } from "./commonTypes"; 4 | import { ControllerService } from "./controllerService"; 5 | import { LoggingService } from "./loggingService"; 6 | import { Utils } from "./utils"; 7 | import { DaoTokenFactory, DaoTokenWrapper } from "./wrappers/daoToken"; 8 | import { ReputationFactory, ReputationWrapper } from "./wrappers/reputation"; 9 | 10 | /** 11 | * Methods for querying information about an Avatar. 12 | * Use it by: 13 | * 14 | * ```javascript 15 | * const avatarService = new AvatarService(avatarAddress); 16 | * ``` 17 | * 18 | */ 19 | export class AvatarService { 20 | 21 | public controllerService: ControllerService; 22 | private avatarAddress: Address; 23 | private avatar: any; 24 | private nativeReputationAddress: any; 25 | private nativeReputation: ReputationWrapper; 26 | private nativeTokenAddress: any; 27 | private nativeToken: DaoTokenWrapper; 28 | 29 | constructor(avatarAddress: Address) { 30 | this.avatarAddress = avatarAddress; 31 | this.controllerService = new ControllerService(avatarAddress); 32 | } 33 | 34 | /** 35 | * Returns promise of the Avatar Truffle contract wrapper. 36 | * Returns undefined if not found. 37 | */ 38 | public async getAvatar(): Promise { 39 | if (!this.avatar) { 40 | const Avatar = await Utils.requireContract("Avatar"); 41 | return Avatar.at(this.avatarAddress) 42 | .then((avatar: any) => avatar) // only way to get to catch 43 | 44 | /* have to handle the catch or promise rejection goes unhandled */ 45 | .catch((ex: Error) => { 46 | LoggingService.error(`AvatarService: unable to load avatar at ${this.avatarAddress}: ${ex.message}`); 47 | return undefined; 48 | }); 49 | } 50 | } 51 | 52 | public getIsUController(): Promise { 53 | return this.controllerService.getIsUController(); 54 | } 55 | 56 | /** 57 | * Returns promise of the address of the controller 58 | */ 59 | public async getControllerAddress(): Promise { 60 | return this.controllerService.getControllerAddress(); 61 | } 62 | 63 | /** 64 | * Returns promise of a Truffle contract wrapper for the controller. Could be 65 | * either UController or Controller. You can know which one 66 | * by called `getIsUController`. 67 | */ 68 | public async getController(): Promise { 69 | return this.controllerService.getController(); 70 | } 71 | 72 | /** 73 | * Returns promise of the address of the avatar's native reputation. 74 | */ 75 | public async getNativeReputationAddress(): Promise { 76 | if (!this.nativeReputationAddress) { 77 | const avatar = await this.getAvatar(); 78 | if (avatar) { 79 | this.nativeReputationAddress = await avatar.nativeReputation(); 80 | } 81 | } 82 | return this.nativeReputationAddress; 83 | } 84 | 85 | /** 86 | * Returns promise of the avatar's native reputation Truffle contract wrapper. 87 | */ 88 | public async getNativeReputation(): Promise { 89 | if (!this.nativeReputation) { 90 | const reputationAddress = await this.getNativeReputationAddress(); 91 | if (reputationAddress) { 92 | this.nativeReputation = await ReputationFactory.at(reputationAddress); 93 | } 94 | } 95 | return this.nativeReputation; 96 | } 97 | 98 | /** 99 | * Returns promise of the address of the avatar's native token. 100 | */ 101 | public async getNativeTokenAddress(): Promise { 102 | if (!this.nativeTokenAddress) { 103 | const avatar = await this.getAvatar(); 104 | if (avatar) { 105 | this.nativeTokenAddress = await avatar.nativeToken(); 106 | } 107 | } 108 | return this.nativeTokenAddress; 109 | } 110 | 111 | /** 112 | * Returns promise of the avatar's native token Truffle contract wrapper. 113 | * Assumes the token is a `DAOToken`. 114 | */ 115 | public async getNativeToken(): Promise { 116 | if (!this.nativeToken) { 117 | const tokenAddress = await this.getNativeTokenAddress(); 118 | if (tokenAddress) { 119 | this.nativeToken = await DaoTokenFactory.at(tokenAddress); 120 | } 121 | } 122 | return this.nativeToken; 123 | } 124 | 125 | /** 126 | * Return a current token balance for this avatar, in Wei. 127 | * If tokenAddress is not supplied, then uses native token. 128 | */ 129 | public async getTokenBalance(tokenAddress?: Address): Promise { 130 | let token: DaoTokenWrapper; 131 | 132 | if (!tokenAddress) { 133 | token = await this.getNativeToken(); 134 | } else { 135 | token = await DaoTokenFactory.at(tokenAddress); 136 | } 137 | if (!token) { 138 | LoggingService.error(`AvatarService: Unable to load token at ${tokenAddress}`); 139 | return Promise.resolve(undefined); 140 | } 141 | return token.getBalanceOf(this.avatarAddress); 142 | } 143 | 144 | /** 145 | * Return the current ETH balance for this avatar, in Wei. 146 | */ 147 | public async getEthBalance(): Promise { 148 | const web3 = await Utils.getWeb3(); 149 | 150 | return promisify((callback: any) => web3.eth.getBalance(this.avatarAddress, web3.eth.defaultBlock, callback))() 151 | .then((balance: BigNumber) => { 152 | return balance; 153 | }); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lib/commonTypes.ts: -------------------------------------------------------------------------------- 1 | import { Utils } from "./utils"; 2 | 3 | export type fnVoid = () => void; 4 | export type Hash = string; 5 | export type Address = string; 6 | 7 | export enum BinaryVoteResult { 8 | Abstain = 0, 9 | Yes = 1, 10 | No = 2, 11 | } 12 | 13 | export enum SchemePermissions { 14 | None = 0, 15 | /** 16 | * A scheme always automatically gets this bit when registered to a DAO 17 | */ 18 | IsRegistered = 1, 19 | CanRegisterSchemes = 2, 20 | CanAddRemoveGlobalConstraints = 4, 21 | CanUpgradeController = 8, 22 | CanCallDelegateCall = 0x10, 23 | All = 0x1f, 24 | } 25 | /* tslint:disable:no-bitwise */ 26 | /* tslint:disable:max-line-length */ 27 | /** 28 | * These are the permissions that are the minimum that each scheme must have to 29 | * be able to perform its full range of functionality. 30 | * 31 | * Note that '1' is always assigned to a scheme by the Controller when the 32 | * scheme is registered with the controller. 33 | */ 34 | export class DefaultSchemePermissions { 35 | public static NoPermissions: SchemePermissions = SchemePermissions.None; 36 | public static MinimumPermissions: SchemePermissions = SchemePermissions.IsRegistered; 37 | public static AllPermissions: SchemePermissions = SchemePermissions.All; 38 | public static ContributionReward: SchemePermissions = SchemePermissions.IsRegistered; 39 | public static GlobalConstraintRegistrar: SchemePermissions = SchemePermissions.IsRegistered | SchemePermissions.CanAddRemoveGlobalConstraints; 40 | /** 41 | * Has all permissions so that it can register/unregister all schemes 42 | */ 43 | public static SchemeRegistrar: SchemePermissions = SchemePermissions.All; 44 | public static UpgradeScheme: SchemePermissions = SchemePermissions.IsRegistered | SchemePermissions.CanRegisterSchemes | SchemePermissions.CanUpgradeController; 45 | public static VestingScheme: SchemePermissions = SchemePermissions.IsRegistered; 46 | public static VoteInOrganizationScheme: SchemePermissions = SchemePermissions.IsRegistered | SchemePermissions.CanCallDelegateCall; 47 | } 48 | /* tslint:enable:no-bitwise */ 49 | /* tslint:enable:max-line-length */ 50 | /* tslint:disable:no-namespace */ 51 | export namespace SchemePermissions { 52 | export function toString(perms: SchemePermissions): string { 53 | return Utils.numberToPermissionsString(perms); 54 | } 55 | export function fromString(perms: string): SchemePermissions { 56 | return Utils.permissionsStringToNumber(perms); 57 | } 58 | } 59 | /*tslint:enable:no-namespace */ 60 | 61 | export interface TruffleContract { 62 | /** 63 | * Migrate a new instance of the contract. Returns promise of being 64 | * migrated. 65 | * Note that the so-called promise returned by Truffle only supplies a 'then' 66 | * function, You have to call 'then' to get the real promise. 67 | */ 68 | new: (...rest: Array) => Promise; 69 | /** 70 | * Returns a promise of an existing instance of the contract. 71 | * Note that the so-called promise returned by Truffle only supplies a 'then' 72 | * function, You have to call 'then' to get the real promise. 73 | */ 74 | at: (address: string) => Promise; 75 | /** 76 | * Returns a promise of the deployed instance of the contract. 77 | * Note that the so-called promise returned by Truffle only supplies a 'then' 78 | * function, You have to call 'then' to get the real promise. 79 | */ 80 | deployed: () => Promise; 81 | } 82 | -------------------------------------------------------------------------------- /lib/configService.ts: -------------------------------------------------------------------------------- 1 | import { IConfigService } from "./iConfigService"; 2 | import { PubSubEventService } from "./pubSubEventService"; 3 | 4 | /** 5 | * Set and set global Arc.js settings. 6 | * 7 | * For more information, refer to [Configuring Arc.js](/Configuration.md). 8 | */ 9 | export class ConfigService { 10 | public static instance: IConfigService; 11 | public static data: any; 12 | 13 | public static get(setting: string): any { 14 | const parts = setting.split("."); 15 | let result; 16 | if (parts.length) { 17 | result = ConfigService.data; 18 | parts.forEach((part: any): void => { 19 | result = result[part]; 20 | }); 21 | } 22 | return result; 23 | } 24 | 25 | public static set(setting: string, value: any): void { 26 | const parts = setting.split("."); 27 | const count = parts.length - 1; 28 | let section = ConfigService.data; 29 | if (count > 0) { 30 | for (let i = 0; i < count; ++i) { 31 | section = section[parts[i]]; 32 | } 33 | } 34 | section[parts[count]] = value; 35 | PubSubEventService.publish(`ConfigService.settingChanged.${setting}`, value); 36 | } 37 | 38 | constructor() { 39 | if (!ConfigService.instance) { 40 | const defaults = require("../config/default.json"); 41 | const prefix = "arcjs_"; 42 | if (process && process.env) { 43 | Object.keys(process.env).forEach((key: string) => { 44 | if (key.startsWith(prefix)) { 45 | const internalKey = key.replace(prefix, ""); 46 | if (defaults.hasOwnProperty(internalKey)) { 47 | defaults[internalKey] = process.env[key]; 48 | } 49 | } 50 | }); 51 | } 52 | 53 | ConfigService.data = defaults; 54 | ConfigService.instance = this; 55 | } 56 | return ConfigService.instance; 57 | } 58 | 59 | public get(setting: string): any { 60 | return ConfigService.instance.get(setting); 61 | } 62 | 63 | public set(setting: string, value: any): void { 64 | ConfigService.instance.set(setting, value); 65 | } 66 | } 67 | 68 | /** 69 | * This will automagically create a static instance of ConfigService that will be used whenever 70 | * someone imports ConfigService. 71 | */ 72 | Object.freeze(new ConfigService()); 73 | -------------------------------------------------------------------------------- /lib/contractWrapperFactory.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from "es6-promisify"; 2 | import { Address } from "./commonTypes"; 3 | import { ConfigService } from "./configService"; 4 | import { IConfigService } from "./iConfigService"; 5 | import { IContractWrapper, IContractWrapperFactory } from "./iContractWrapperBase"; 6 | import { LoggingService } from "./loggingService"; 7 | import { Utils } from "./utils"; 8 | import { UtilsInternal } from "./utilsInternal"; 9 | import { Web3EventService } from "./web3EventService"; 10 | 11 | /** 12 | * Generic class factory for all of the contract wrapper classes. 13 | */ 14 | export class ContractWrapperFactory 15 | implements IContractWrapperFactory { 16 | 17 | public static setConfigService(configService: IConfigService): void { 18 | this.configService = configService; 19 | } 20 | 21 | public static clearContractCache(): void { 22 | this.contractCache.clear(); 23 | } 24 | 25 | /** 26 | * this is a Map keyed by contract name of a Map keyed by address to an `IContractWrapper` 27 | */ 28 | private static contractCache: Map> 29 | = new Map>(); 30 | 31 | private static configService: IConfigService; 32 | 33 | private solidityContract: any; 34 | 35 | /** 36 | * Connstructor to create a contract wrapper factory for the given 37 | * Arc contract name and wrapper class. 38 | * @param solidityContract Name of the contract 39 | * @param wrapper - Class of the contract 40 | */ 41 | public constructor( 42 | private solidityContractName: string, 43 | private wrapper: new (solidityContract: any, web3EventService: Web3EventService) => TWrapper, 44 | private web3EventService: Web3EventService) { 45 | } 46 | 47 | /** 48 | * Deploy a new instance of the contract and return a wrapper around it. 49 | * @param rest Optional arguments to the Arc contracts constructor. 50 | */ 51 | public async new(...rest: Array): Promise { 52 | 53 | await this.ensureSolidityContract(); 54 | 55 | let gas; 56 | 57 | if (ConfigService.get("estimateGas") && (!rest || !rest.length || (!rest[rest.length - 1].gas))) { 58 | gas = await this.estimateConstructorGas(...rest); 59 | LoggingService.debug(`Instantiating ${this.solidityContractName} with gas: ${gas}`); 60 | } 61 | 62 | if (gas) { 63 | rest = [...rest, { gas }]; 64 | } 65 | 66 | const hydratedWrapper = 67 | await new this.wrapper(this.solidityContract, this.web3EventService).hydrateFromNew(...rest); 68 | 69 | if (hydratedWrapper && ContractWrapperFactory.configService.get("cacheContractWrappers")) { 70 | this.setCachedContract(this.solidityContractName, hydratedWrapper); 71 | } 72 | return hydratedWrapper; 73 | } 74 | 75 | /** 76 | * Return a wrapper around the contract, hydrated from the given address. 77 | * Returns undefined if not found. 78 | * @param address 79 | */ 80 | public async at(address: string): Promise { 81 | 82 | await this.ensureSolidityContract(); 83 | 84 | const getWrapper = (): Promise => { 85 | return new this.wrapper(this.solidityContract, this.web3EventService).hydrateFromAt(address); 86 | }; 87 | 88 | return this.getHydratedWrapper(getWrapper, address); 89 | } 90 | 91 | /** 92 | * Return a wrapper around the contract as deployed by the current version of Arc.js. 93 | * Note this is usually not needed as the WrapperService provides these 94 | * wrappers already hydrated. 95 | * Returns undefined if not found. 96 | */ 97 | public async deployed(): Promise { 98 | /** 99 | * use deployed address if supplied for this contract 100 | */ 101 | const externallyDeployedAddress = Utils.getDeployedAddress(this.solidityContractName); 102 | 103 | if (!externallyDeployedAddress) { 104 | throw new Error("ContractWrapperFactory: No deployed contract address has been supplied."); 105 | } 106 | 107 | return this.at(externallyDeployedAddress); 108 | } 109 | 110 | public async ensureSolidityContract(): Promise { 111 | /** 112 | * requireContract caches and uncaches the contract appropriately 113 | */ 114 | return Utils.requireContract(this.solidityContractName) 115 | .then((contract: any): any => this.solidityContract = contract); 116 | } 117 | 118 | protected async estimateConstructorGas(...params: Array): Promise { 119 | 120 | const web3 = await Utils.getWeb3(); 121 | await this.ensureSolidityContract(); 122 | 123 | const callData = (web3.eth.contract(this.solidityContract.abi).new as any).getData( 124 | ...params, 125 | { 126 | data: this.solidityContract.bytecode, 127 | }); 128 | 129 | const currentNetwork = await Utils.getNetworkName(); 130 | 131 | const maxGasLimit = await UtilsInternal.computeMaxGasLimit(); 132 | 133 | // note that Ganache is identified specifically as the one instantiated by arc.js (by the networkId) 134 | if (currentNetwork === "Ganache") { 135 | return maxGasLimit; // because who cares with ganache and we can't get good estimates from it 136 | } 137 | 138 | const gas = await promisify((callback: any) => web3.eth.estimateGas({ data: callData }, callback))() as number; 139 | 140 | return Math.max(Math.min(gas, maxGasLimit), 21000); 141 | } 142 | 143 | private async getHydratedWrapper( 144 | getWrapper: () => Promise, 145 | address?: Address): Promise { 146 | 147 | let hydratedWrapper: TWrapper; 148 | if (ContractWrapperFactory.configService.get("cacheContractWrappers")) { 149 | if (!address) { 150 | try { 151 | address = this.solidityContract.address; 152 | } catch { 153 | // the contract has not been deployed, so can't get it's address 154 | } 155 | } 156 | 157 | if (address) { 158 | hydratedWrapper = this.getCachedContract(this.solidityContractName, address) as TWrapper; 159 | if (hydratedWrapper) { 160 | LoggingService.debug(`ContractWrapperFactory: obtained wrapper from cache: ${hydratedWrapper.address}`); 161 | } 162 | } 163 | 164 | if (!hydratedWrapper) { 165 | hydratedWrapper = await getWrapper(); 166 | if (hydratedWrapper) { 167 | this.setCachedContract(this.solidityContractName, hydratedWrapper); 168 | } 169 | } 170 | } else { 171 | hydratedWrapper = await getWrapper(); 172 | } 173 | return hydratedWrapper; 174 | } 175 | 176 | private getCachedContract(name: string, at: string): IContractWrapper | undefined { 177 | if (!at) { 178 | return undefined; 179 | } 180 | const addressMap = ContractWrapperFactory.contractCache.get(name); 181 | if (!addressMap) { 182 | return undefined; 183 | } 184 | return addressMap.get(at); 185 | } 186 | 187 | private setCachedContract( 188 | name: string, 189 | wrapper: IContractWrapper): void { 190 | 191 | if (wrapper) { 192 | let addressMap = ContractWrapperFactory.contractCache.get(name); 193 | if (!addressMap) { 194 | addressMap = new Map(); 195 | ContractWrapperFactory.contractCache.set(name, addressMap); 196 | } 197 | addressMap.set(wrapper.address, wrapper); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /lib/controllerService.ts: -------------------------------------------------------------------------------- 1 | import { Address } from "./commonTypes"; 2 | import { LoggingService } from "./loggingService"; 3 | import { Utils } from "./utils"; 4 | 5 | /** 6 | * Methods for querying information about an Avatar's controller. 7 | * Use it by: 8 | * 9 | * ```javascript 10 | * const controllerService = new ControllerService(avatarAddress); 11 | * ``` 12 | * 13 | */ 14 | export class ControllerService { 15 | 16 | private isUController: boolean; 17 | private avatarAddress: Address; 18 | private avatar: any; 19 | private controllerAddress: any; 20 | private controller: any; 21 | 22 | constructor(avatarAddress: Address) { 23 | this.avatarAddress = avatarAddress; 24 | this.isUController = undefined; 25 | } 26 | 27 | /** 28 | * Returns promise of whether avatar has a universal controller 29 | */ 30 | public async getIsUController(): Promise { 31 | await this.getController(); 32 | return this.isUController; 33 | } 34 | 35 | /** 36 | * Returns promise of the address of the controller 37 | */ 38 | public async getControllerAddress(): Promise { 39 | if (!this.controllerAddress) { 40 | const avatar = await this.getAvatar(); 41 | if (avatar) { 42 | this.controllerAddress = await avatar.owner(); 43 | } 44 | } 45 | return this.controllerAddress; 46 | } 47 | 48 | /** 49 | * Returns promise of a Truffle contract wrapper for the controller. Could be 50 | * either UController or Controller. You can know which one 51 | * by checking the ControllerService instance property `isUController`. 52 | */ 53 | public async getController(): Promise { 54 | 55 | if (!this.controller) { 56 | const controllerAddress = await this.getControllerAddress(); 57 | if (controllerAddress) { 58 | /** 59 | * anticipate case where UController hasn't been deployed 60 | */ 61 | let uControllerAddress; 62 | let UControllerContract; 63 | try { 64 | /** 65 | * TODO: check for previous and future versions of UController here 66 | */ 67 | UControllerContract = await Utils.requireContract("UController"); 68 | uControllerAddress = (await UControllerContract.deployed()).address; 69 | /* tslint:disable-next-line:no-empty */ 70 | } catch { } 71 | 72 | this.isUController = uControllerAddress === controllerAddress; 73 | 74 | if (this.isUController) { 75 | this.controller = await UControllerContract.at(controllerAddress); 76 | } else { 77 | const ControllerContract = await Utils.requireContract("Controller"); 78 | this.controller = await ControllerContract.at(controllerAddress); 79 | } 80 | } 81 | } 82 | return this.controller; 83 | } 84 | 85 | /** 86 | * Returns promise of the Avatar Truffle contract wrapper. 87 | * Returns undefined if not found. 88 | */ 89 | private async getAvatar(): Promise { 90 | if (!this.avatar) { 91 | const Avatar = await Utils.requireContract("Avatar"); 92 | return Avatar.at(this.avatarAddress) 93 | .then((avatar: any) => avatar) // only way to get to catch 94 | 95 | /* have to handle the catch or promise rejection goes unhandled */ 96 | .catch((ex: Error) => { 97 | LoggingService.error(`ControllerService: unable to load avatar at ${this.avatarAddress}: ${ex.message}`); 98 | return undefined; 99 | }); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/iConfigService.ts: -------------------------------------------------------------------------------- 1 | export interface IConfigService { 2 | get(setting: string): any; 3 | set(setting: string, value: any): void; 4 | } 5 | -------------------------------------------------------------------------------- /lib/iContractWrapperBase.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | import { Address, Hash, SchemePermissions } from "./commonTypes"; 3 | import { 4 | TransactionReceiptTruffle, 5 | TransactionService 6 | } from "./transactionService"; 7 | import { IIntVoteInterface } from "./wrappers/iIntVoteInterface"; 8 | 9 | export interface IContractWrapper { 10 | factory: IContractWrapperFactory; 11 | name: string; 12 | friendlyName: string; 13 | address: Address; 14 | contract: any; 15 | hydrateFromNew(...rest: Array): Promise; 16 | hydrateFromAt(address: string): Promise; 17 | } 18 | 19 | /** 20 | * The minimum requirements for a scheme that can be registered with a DAO/controller. 21 | */ 22 | export interface ISchemeWrapper extends IContractWrapper { 23 | getSchemePermissions(avatarAddress: Address): Promise; 24 | getDefaultPermissions(): SchemePermissions; 25 | } 26 | 27 | /** 28 | * The minimum requirements for a universal scheme. 29 | */ 30 | export interface IUniversalSchemeWrapper extends ISchemeWrapper { 31 | getParameters(paramsHash: Hash): Promise; 32 | getParametersHash(params: any): Promise; 33 | setParameters(params: any): Promise>; 34 | getSchemeParameters(avatarAddress: Address): Promise; 35 | getParametersArray(paramsHash: Hash): Promise>; 36 | } 37 | 38 | /** 39 | * The minimum requirements for a voting machine wrapper. 40 | */ 41 | export interface IVotingMachineWrapper extends IContractWrapper { 42 | getParameters(paramsHash: Hash): Promise; 43 | getParametersHash(params: any): Promise; 44 | setParameters(params: any): Promise>; 45 | getParametersArray(paramsHash: Hash): Promise>; 46 | } 47 | 48 | export interface IContractWrapperFactory { 49 | new: (...rest: Array) => Promise; 50 | at: (address: string) => Promise; 51 | deployed: () => Promise; 52 | ensureSolidityContract(): Promise; 53 | } 54 | 55 | export class ArcTransactionResult { 56 | 57 | constructor( 58 | /** 59 | * The transaction hash 60 | */ 61 | public tx: Hash, 62 | /** 63 | * the Truffle contract wrapper 64 | */ 65 | private contract: string | object) { 66 | } 67 | 68 | /** 69 | * Returns a promise of the transaction if it is mined, 70 | * converted to a TransactionReceiptTruffle (with readable logs). 71 | * 72 | * Returns null if the transaciton is not yet mined. 73 | */ 74 | public async getTxMined(): Promise { 75 | if (!this.tx) { 76 | return null; 77 | } 78 | return TransactionService.getMinedTransaction( 79 | this.tx, 80 | this.contract) as Promise; 81 | } 82 | 83 | /** 84 | * Returns a promise of the transaction if it is confirmed, 85 | * converted to a TransactionReceiptTruffle (with readable logs). 86 | * 87 | * Returns null if the transaction is not yet found at the required depth. 88 | * 89 | * @param requiredDepth Optional minimum block depth required to resolve the promise. 90 | * Default comes from the `ConfigService`. 91 | */ 92 | public async getTxConfirmed(requiredDepth?: number): Promise { 93 | if (!this.tx) { 94 | return null; 95 | } 96 | return TransactionService.getConfirmedTransaction( 97 | this.tx, 98 | this.contract, 99 | requiredDepth) as Promise; 100 | } 101 | 102 | /** 103 | * Returns promise of a mined transaction once it has been mined, 104 | * converted to a TransactionReceiptTruffle (with readable logs). 105 | */ 106 | public async watchForTxMined(): Promise { 107 | if (!this.tx) { 108 | return null; 109 | } 110 | return TransactionService.watchForMinedTransaction( 111 | this.tx, 112 | this.contract) as Promise; 113 | } 114 | 115 | /** 116 | * Returns a promise of a TransactionReceipt once the given transaction has been confirmed, 117 | * converted to a TransactionReceiptTruffle (with readable logs), 118 | * according to the optional `requiredDepth`. 119 | * 120 | * @param requiredDepth Optional minimum block depth required to resolve the promise. 121 | * Default comes from the `ConfigService`. 122 | */ 123 | public async watchForTxConfirmed(requiredDepth?: number): Promise { 124 | if (!this.tx) { 125 | return null; 126 | } 127 | return TransactionService.watchForConfirmedTransaction(this.tx, 128 | this.contract, 129 | requiredDepth) as Promise; 130 | } 131 | 132 | /** 133 | * Returns promise of a value from the logs of the mined transaction. Will watch for the mined tx, 134 | * so could take a while to return. 135 | * @param valueName - The name of the property whose value we wish to return 136 | * @param eventName - Name of the event in whose log we are to look for the value 137 | * @param index - Index of the log in which to look for the value, when eventName is not given. 138 | * Default is the index of the last log in the transaction. 139 | */ 140 | public async getValueFromMinedTx( 141 | valueName: string, 142 | eventName: string = null, index: number = 0): Promise { 143 | if (!this.tx) { 144 | return null; 145 | } 146 | const txMined = await this.watchForTxMined(); 147 | return TransactionService.getValueFromLogs(txMined, valueName, eventName, index); 148 | } 149 | } 150 | /** 151 | * Base or actual type returned by all contract wrapper methods that generate a transaction and initiate a proposal. 152 | */ 153 | export class ArcTransactionProposalResult extends ArcTransactionResult { 154 | 155 | constructor( 156 | tx: Hash, 157 | contract: any, 158 | /** 159 | * The proposal's voting machine, as IntVoteInterface 160 | */ 161 | public votingMachine: IIntVoteInterface) { 162 | super(tx, contract); 163 | this.votingMachine = votingMachine; 164 | } 165 | 166 | /** 167 | * Returns promise of the proposal id from the logs of the mined transaction. Will watch for the mined tx; 168 | * if it hasn't yet been mined, could take a while to return. 169 | */ 170 | public async getProposalIdFromMinedTx(): Promise { 171 | return this.getValueFromMinedTx("_proposalId"); 172 | } 173 | } 174 | 175 | /** 176 | * Base or actual type returned by all contract wrapper methods that generate a transaction and any other result. 177 | */ 178 | export class ArcTransactionDataResult extends ArcTransactionResult { 179 | constructor( 180 | tx: Hash, 181 | contract: any, 182 | /** 183 | * Additional data being returned. 184 | */ 185 | public result: TData) { 186 | super(tx, contract); 187 | this.result = result; 188 | } 189 | } 190 | 191 | /** 192 | * Common scheme parameters for schemes that are able to create proposals. 193 | */ 194 | export interface StandardSchemeParams { 195 | /** 196 | * Hash of the voting machine parameters to use when voting on a proposal. 197 | */ 198 | voteParametersHash: Hash; 199 | /** 200 | * Address of the voting machine to use when voting on a proposal. 201 | */ 202 | votingMachineAddress: Address; 203 | } 204 | 205 | export { DecodedLogEntryEvent, TransactionReceipt } from "web3"; 206 | 207 | /** 208 | * The value of the global config setting `gasPriceAdjustor` 209 | * This function will be invoked to obtain promise of a desired gas price 210 | * given the current default gas price which will be determined by the x latest blocks 211 | * median gas price. 212 | */ 213 | export type GasPriceAdjustor = (defaultGasPrice: BigNumber) => Promise; 214 | -------------------------------------------------------------------------------- /lib/loggingService.ts: -------------------------------------------------------------------------------- 1 | import * as JSON from "circular-json"; 2 | 3 | export enum LogLevel { 4 | none = 0, 5 | info = 1, 6 | warn = 2, 7 | debug = 4, 8 | error = 8, 9 | all = 15, 10 | } 11 | 12 | export interface ILogger { 13 | /** 14 | * Logs a debug message. 15 | * 16 | * @param message The message to log. 17 | */ 18 | debug(message: string): void; 19 | 20 | /** 21 | * Logs info. 22 | * 23 | * @param message The message to log. 24 | */ 25 | info(message: string): void; 26 | 27 | /** 28 | * Logs a warning. 29 | * 30 | * @param message The message to log. 31 | */ 32 | warn(message: string): void; 33 | 34 | /** 35 | * Logs an error. 36 | * 37 | * @param message The message to log. 38 | */ 39 | error(message: string): void; 40 | } 41 | 42 | class ConsoleLogger implements ILogger { 43 | 44 | /* tslint:disable:max-line-length */ 45 | /* tslint:disable:no-console */ 46 | /* tslint:disable:no-bitwise */ 47 | public debug(message: string): void { if (LoggingService.logLevel & LogLevel.debug) { console.log(`${LoggingService.moduleName} (debug): ${message}`); } } 48 | 49 | public info(message: string): void { if (LoggingService.logLevel & LogLevel.info) { console.log(`${LoggingService.moduleName} (info): ${message}`); } } 50 | 51 | public warn(message: string): void { if (LoggingService.logLevel & LogLevel.warn) { console.log(`${LoggingService.moduleName} (warn): ${message}`); } } 52 | 53 | public error(message: string): void { if (LoggingService.logLevel & LogLevel.error) { console.log(`${LoggingService.moduleName} (error): ${message}`); } } 54 | /* tslint:enable:no-console */ 55 | /* tslint:enable:no-bitwise */ 56 | /* tslint:enable:max-line-length */ 57 | } 58 | 59 | /** 60 | * Provides logging support, logging by default to the JavaScript console. You can provide 61 | * alternate or additional loggers by using `LoggingService.addLogger` and `LoggingService.removeLogger`. 62 | * 63 | * You can set the `LogLevel` by setting `LoggingService.logLevel` with flags 64 | * from [LogLevel](/arc.js/api/enums/LogLevel/), or by using the [ConfigService](/Configuration.md#logging). 65 | * 66 | * Logically, LogLevels are simply or'd together, there is no hierarchy to them. 67 | */ 68 | export class LoggingService { 69 | 70 | public static loggers: Array = [new ConsoleLogger()]; 71 | 72 | public static logLevel: LogLevel = LogLevel.none; 73 | 74 | public static moduleName: string = "Arc.js"; 75 | 76 | public static debug(message: string): void { 77 | LoggingService.loggers.forEach((logger: ILogger) => { 78 | logger.debug(message); 79 | }); 80 | } 81 | 82 | public static info(message: string): void { 83 | LoggingService.loggers.forEach((logger: ILogger) => { 84 | logger.info(message); 85 | }); 86 | } 87 | 88 | public static warn(message: string): void { 89 | LoggingService.loggers.forEach((logger: ILogger) => { 90 | logger.warn(message); 91 | }); 92 | } 93 | 94 | public static error(message: string): void { 95 | LoggingService.loggers.forEach((logger: ILogger) => { 96 | logger.error(message); 97 | }); 98 | } 99 | 100 | /** 101 | * Log a message at potentially multiple levels instead of just one. 102 | * 103 | * The message will be logged just once, at the first log level in the intersection between 104 | * the given log level and the current log level, in the following order of precendence: 105 | * 106 | * 1. error 107 | * 2. warn 108 | * 3. info 109 | * 4. debug 110 | * 111 | * So if the current log level is info|error and you call `message("a message", LogLevel.info|LogLevel.error)` 112 | * then you will see the message logged as an error. 113 | * 114 | * @param message 115 | * @param level log level(s) 116 | */ 117 | public static message(message: string, level: LogLevel = LoggingService.logLevel): void { 118 | 119 | if (level === LogLevel.none) { 120 | return; 121 | } 122 | 123 | // only issue the message once 124 | let messaged: boolean = false; 125 | 126 | /* tslint:disable:no-bitwise */ 127 | if (level & LogLevel.error) { 128 | LoggingService.error(message); 129 | messaged = true; 130 | } 131 | if (!messaged && (level & LogLevel.warn)) { 132 | LoggingService.warn(message); 133 | messaged = true; 134 | } 135 | if (!messaged && (level & LogLevel.info)) { 136 | LoggingService.info(message); 137 | messaged = true; 138 | } 139 | if (!messaged && (level & LogLevel.debug)) { 140 | LoggingService.debug(message); 141 | } 142 | /* tslint:enable:no-bitwise */ 143 | } 144 | 145 | public static addLogger(logger: ILogger): void { 146 | LoggingService.loggers.push(logger); 147 | } 148 | 149 | public static removeLogger(logger: ILogger): void { 150 | const ndx = LoggingService.loggers.indexOf(logger); 151 | if (ndx >= 0) { 152 | LoggingService.loggers.splice(ndx, 1); 153 | } 154 | } 155 | 156 | public static stringifyObject(obj: any): string { 157 | return JSON.stringify(obj); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /lib/promiseEventService.ts: -------------------------------------------------------------------------------- 1 | import { LoggingService } from "./loggingService"; 2 | import { PubSubEventService } from "./pubSubEventService"; 3 | import { UtilsInternal } from "./utilsInternal"; 4 | 5 | export class PromiseEventService { 6 | /** 7 | * Publish to the given topics the result of the given promise. 8 | * The payload of the event will be of type TResult. 9 | * @param topics 10 | * @param promise 11 | */ 12 | public static publish(topics: Array | string, promise: Promise): void { 13 | const topicsArray = UtilsInternal.ensureArray(topics); 14 | promise 15 | .then((result: TResult) => { 16 | topicsArray.forEach((topic: string) => { 17 | PubSubEventService.publish(topic, result); 18 | }); 19 | }) 20 | .catch((error: Error) => { 21 | LoggingService.error( 22 | `PromiseEventService.publish: unable to publish result of rejected promise: ${error.message}`); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/proposalGeneratorBase.ts: -------------------------------------------------------------------------------- 1 | import { Address } from "./commonTypes"; 2 | import { ContractWrapperFactory } from "./contractWrapperFactory"; 3 | import { ProposalService } from "./proposalService"; 4 | import { USchemeWrapperBase } from "./uSchemeWrapperBase"; 5 | import { Web3EventService } from "./web3EventService"; 6 | import { IntVoteInterfaceFactory, IntVoteInterfaceWrapper } from "./wrappers/intVoteInterface"; 7 | 8 | /** 9 | * Methods for Arc universal schemes that can create proposals. Note that a contract that 10 | * creates proposals doesn't necessary have to be a universal scheme, nor even a plain-old scheme. 11 | * But all of the Arc proposal-generating schemes currently are currently universal schemes, so 12 | * for the purposes of simplicity of organizating Arc.js and implementing these methods in one 13 | * place, we define this as a `USchemeWrapperBase`. 14 | */ 15 | export abstract class ProposalGeneratorBase extends USchemeWrapperBase { 16 | protected proposalService: ProposalService; 17 | protected votingMachineFactory: ContractWrapperFactory; 18 | 19 | constructor(solidityContract: any, web3EventService: Web3EventService) { 20 | super(solidityContract, web3EventService); 21 | this.proposalService = new ProposalService(web3EventService); 22 | this.votingMachineFactory = IntVoteInterfaceFactory; 23 | } 24 | 25 | /** 26 | * Return the address of the voting machine for this scheme as registered with the given avatar. 27 | * @param avatarAddress 28 | */ 29 | public async getVotingMachineAddress(avatarAddress: Address): Promise
{ 30 | return (await this._getSchemeParameters(avatarAddress)).votingMachineAddress; 31 | } 32 | 33 | /** 34 | * Return IntVoteInterfaceWrapper for this scheme as registered with the given avatar. 35 | * @param avatarAddress 36 | */ 37 | public async getVotingMachine(avatarAddress: Address): Promise { 38 | const votingMachineAddress = await this.getVotingMachineAddress(avatarAddress); 39 | return this.votingMachineFactory.at(votingMachineAddress); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/proposalService.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "bignumber.js"; 2 | import { Address, Hash } from "./commonTypes"; 3 | import { 4 | EntityFetcherFactory, 5 | EventFetcherFactory, 6 | TransformEventCallback, 7 | Web3EventService 8 | } from "./web3EventService"; 9 | import { 10 | IntVoteInterfaceWrapper, 11 | } from "./wrappers/intVoteInterface"; 12 | 13 | import { DecodedLogEntryEvent } from "web3"; 14 | import { 15 | ExecuteProposalEventResult, 16 | NewProposalEventResult 17 | } from "./wrappers/iIntVoteInterface"; 18 | /** 19 | * A single instance of ProposalService provides services relating to a single 20 | * type of proposal (TProposal), for example a proposal to contribute rewards to a beneficiary. 21 | * When constructing a ProposalService we pass to the constructor a `ProposalMaker` 22 | * that provides functions enabling ProposalService to do its job with respect to the given TProposal. 23 | * Note it is not scoped to a particular Avatar. 24 | * 25 | * For more information, see [Proposals](/Proposals.md#proposals). 26 | */ 27 | export class ProposalService { 28 | 29 | constructor(private web3EventService: Web3EventService) { 30 | 31 | } 32 | 33 | /** 34 | * Returns an EntityFetcherFactory for fetching proposal-related events. Can take any EventFetcherFactory 35 | * whose event args supply `_proposalId`. Returns events as a promise of `TProposal`. You must supply an 36 | * `EventFetcherFactory` for fetching the events and a callback to transform `TEventArgs` to a promise of `TProposal`. 37 | * Each entity, when the associated proposal is votable and options.votingMachine is supplied, 38 | * will also contain a `votingMachine` property of type `IntVoteInterfaceWrapper`. 39 | * @type TEventArgs The type of the `args` object in the event. 40 | * @type TProposal The type of object returned as a transformation of the `args` information in each event. 41 | * @param options 42 | */ 43 | public getProposalEvents( 44 | options: GetProposalEventsOptions) 45 | : EntityFetcherFactory { 46 | 47 | if (!options.transformEventCallback) { 48 | throw new Error("transformEventCallback must be supplied"); 49 | } 50 | 51 | if (!options.proposalsEventFetcher) { 52 | throw new Error("proposalsEventFetcher must be supplied"); 53 | } 54 | 55 | const votableOnly = !!options.votableOnly; 56 | 57 | if (votableOnly && !options.votingMachine) { 58 | throw new Error("votingMachine must be supplied when votableOnly is true"); 59 | } 60 | 61 | return this.web3EventService.createEntityFetcherFactory( 62 | options.proposalsEventFetcher, 63 | async (event: DecodedLogEntryEvent) 64 | : Promise => { 65 | let entity: TProposal | (TProposal & ProposalEntity) | undefined; 66 | 67 | if (options.votingMachine) { 68 | const isVotable = await options.votingMachine.isVotable({ proposalId: event.args._proposalId }); 69 | 70 | entity = await ( 71 | ((!votableOnly || isVotable) ? 72 | options.transformEventCallback(event) : 73 | Promise.resolve(undefined))); 74 | 75 | if (entity && isVotable) { 76 | (entity as (TProposal & ProposalEntity)).votingMachine = options.votingMachine; 77 | } 78 | } else { 79 | entity = await options.transformEventCallback(event); 80 | } 81 | return entity; 82 | }, 83 | options.baseArgFilter); 84 | } 85 | 86 | /** 87 | * Returns promise of an EntityFetcherFactory for fetching votable proposals from the 88 | * given `IntVoteInterfaceWrapper`. The proposals are returned as promises of instances 89 | * of `VotableProposal`. 90 | * 91 | * @param votingMachineAddress 92 | */ 93 | public getVotableProposals(votingMachine: IntVoteInterfaceWrapper): 94 | EntityFetcherFactory { 95 | 96 | return this.web3EventService.createEntityFetcherFactory( 97 | votingMachine.VotableProposals, 98 | (event: DecodedLogEntryEvent): Promise => { 99 | return Promise.resolve( 100 | { 101 | avatarAddress: event.args._organization, 102 | numOfChoices: event.args._numOfChoices.toNumber(), 103 | paramsHash: event.args._paramsHash, 104 | proposalId: event.args._proposalId, 105 | proposerAddress: event.args._proposer, 106 | } 107 | ); 108 | }); 109 | } 110 | 111 | /** 112 | * Returns promise of an EntityFetcherFactory for fetching executed proposals from the 113 | * given `IntVoteInterfaceWrapper`. 114 | * The proposals are returned as promises of instances of `ExecutedProposal`. 115 | * 116 | * @param votingMachineAddress 117 | */ 118 | public getExecutedProposals(votingMachine: IntVoteInterfaceWrapper): 119 | EntityFetcherFactory { 120 | 121 | return this.web3EventService.createEntityFetcherFactory( 122 | votingMachine.ExecuteProposal, 123 | (event: DecodedLogEntryEvent): Promise => { 124 | return Promise.resolve( 125 | { 126 | decision: event.args._decision.toNumber(), 127 | proposalId: event.args._proposalId, 128 | totalReputation: event.args._totalReputation, 129 | } 130 | ); 131 | }); 132 | } 133 | } 134 | 135 | export interface EventHasPropertyId { 136 | _proposalId: Hash; 137 | } 138 | 139 | export interface VotableProposal { 140 | numOfChoices: number; 141 | paramsHash: Hash; 142 | proposalId: Hash; 143 | proposerAddress: Address; 144 | avatarAddress: Address; 145 | } 146 | 147 | // TODO: include avatar address? 148 | export interface ExecutedProposal { 149 | /** 150 | * the vote choice that won. 151 | */ 152 | decision: number; 153 | /** 154 | * The id of the proposal that was executed. 155 | */ 156 | proposalId: Hash; 157 | /** 158 | * The total reputation in the DAO at the time the proposal was executed 159 | */ 160 | totalReputation: BigNumber; 161 | } 162 | 163 | export interface GetProposalEventsOptions { 164 | /** 165 | * Event fetcher to use to get or watch the event that supplies `TEventArgs`. 166 | */ 167 | proposalsEventFetcher: EventFetcherFactory; 168 | /** 169 | * Returns Promise of `TProposal` given `TEventArgs` for the event. Return of `undefined` will be ignored, not 170 | * passed-on to the caller. 171 | */ 172 | transformEventCallback: TransformEventCallback; 173 | /** 174 | * Optional to filter events on the given filter, like `{ _avatar: [anAddress] }`. 175 | * This will be merged with any filter that the caller provides when creating the EntityFetcher. 176 | */ 177 | baseArgFilter?: any; 178 | /** 179 | * True to only return votable proposals. Default is false. 180 | */ 181 | votableOnly?: boolean; 182 | /** 183 | * Used to determine whether proposals are votable. 184 | * This is only required when votableOnly is set to `true`. 185 | */ 186 | votingMachine?: IntVoteInterfaceWrapper; 187 | } 188 | 189 | export interface ProposalEntity { 190 | votingMachine: IntVoteInterfaceWrapper; 191 | } 192 | -------------------------------------------------------------------------------- /lib/pubSubEventService.ts: -------------------------------------------------------------------------------- 1 | import * as PubSub from "pubsub-js"; 2 | import { fnVoid } from "./commonTypes"; 3 | import { LoggingService } from "./loggingService"; 4 | import { UtilsInternal } from "./utilsInternal"; 5 | 6 | /** 7 | * A Pub/Sub event system that enables you to subscribe to various events published by Arc.js. 8 | * For more information, see [Pub/Sub Events](/Events.md#pubsubevents). 9 | */ 10 | export class PubSubEventService { 11 | 12 | /** 13 | * Send the given payload to subscribers of the given topic. 14 | * @param topic See [subscribe](PubSubEventService.md#subscribe) 15 | * @param payload Sent in the subscription callback. 16 | * @returns True if there are any subscribers 17 | */ 18 | public static publish(topic: string, payload: any): boolean { 19 | LoggingService.debug(`PubSubEventService: publishing ${topic}`); 20 | return PubSub.publish(topic, payload); 21 | } 22 | 23 | /** 24 | * Subscribe to the given topic or array of topics. 25 | * @param topics Identifies the event(s) to which you wish to subscribe 26 | * @param callback The function to call when the requested events are published 27 | * @returns An interface with `.unsubscribe()`. Be sure to call it! 28 | */ 29 | public static subscribe(topics: string | Array, callback: EventSubscriptionCallback): IEventSubscription { 30 | return Array.isArray(topics) ? 31 | PubSubEventService.aggregate(topics, callback) : 32 | new EventSubscription(PubSub.subscribe(topics, callback)); 33 | } 34 | 35 | /** 36 | * Remove all subscriptions 37 | */ 38 | public static clearAllSubscriptions(): void { 39 | PubSub.clearAllSubscriptions(); 40 | } 41 | 42 | /** 43 | * Unsubscribes after optional timeout. 44 | * When passed a token, removes a specific subscription, 45 | * when passed a callback, removes all subscriptions for that callback, 46 | * when passed a topic, removes all subscriptions for the topic hierarchy. 47 | * 48 | * @param key - A token, function or topic to unsubscribe. 49 | * @param milliseconds number of milliseconds to timeout. 50 | * Default is -1 which means not to timeout at all. 51 | */ 52 | public static unsubscribe( 53 | key: EventSubscriptionKey, 54 | milliseconds: number = -1): Promise { 55 | // timeout to allow lingering events to be handled before unsubscribing 56 | if (milliseconds === -1) { 57 | PubSub.unsubscribe(key); 58 | return Promise.resolve(); 59 | } 60 | // timeout to allow lingering events to be handled before unsubscribing 61 | return new Promise((resolve: fnVoid): void => { 62 | setTimeout(() => { 63 | PubSub.unsubscribe(key); 64 | resolve(); 65 | }, milliseconds); 66 | }); 67 | } 68 | 69 | /** 70 | * Return whether topic is specified by matchTemplates. 71 | * 72 | * Examples: 73 | * 74 | * matchTemplates: ["foo"] 75 | * topic: "foo.bar" 76 | * result: true 77 | * 78 | * matchTemplates: ["foo.bar"] 79 | * topic: "foo" 80 | * result: false 81 | * 82 | * Or a wildcard: 83 | * 84 | * matchTemplates: "*" 85 | * topic: "foo" 86 | * result: true 87 | * 88 | * @param matchTemplates 89 | * @param topic 90 | */ 91 | public static isTopicSpecifiedBy( 92 | matchTemplates: Array | string, 93 | topic: string): boolean { 94 | 95 | if (!topic) { return false; } 96 | if (!matchTemplates) { return false; } 97 | 98 | if ((typeof matchTemplates === "string") && (matchTemplates === "*")) { return true; } 99 | 100 | matchTemplates = UtilsInternal.ensureArray(matchTemplates); 101 | 102 | const topicWords = topic.split("."); 103 | 104 | for (const template of matchTemplates) { 105 | 106 | if (!template) { continue; } 107 | if (template === topic) { return true; } 108 | if (template.length > topic.length) { continue; } 109 | if (template[0] === ".") { continue; } 110 | 111 | const templateWords = template.split("."); 112 | 113 | if (templateWords.length > topicWords.length) { continue; } 114 | 115 | let matches = false; 116 | 117 | for (let i = 0; i < templateWords.length; ++i) { 118 | const templateWord = templateWords[i]; 119 | const topicWord = topicWords[i]; 120 | if ((templateWord === "*") || (templateWord === topicWord)) { matches = true; } else { matches = false; break; } 121 | } 122 | 123 | if (!matches) { continue; } 124 | 125 | // else matches 126 | return true; 127 | } 128 | 129 | return false; 130 | } 131 | 132 | /** 133 | * Subscribe to multiple topics with the single given callback. 134 | * @param topics topic or collection of topics 135 | * @param callback Callback to handle them all 136 | * @returns An interface with `.unsubscribe()`. Be sure to call it! 137 | */ 138 | private static aggregate( 139 | topics: Array, 140 | callback: EventSubscriptionCallback): IEventSubscription { 141 | 142 | return new SubscriptionCollection(topics, callback); 143 | } 144 | } 145 | 146 | /** 147 | * Creates a collection of subscriptions to which one can unsubscribe all at once. 148 | */ 149 | export class SubscriptionCollection implements IEventSubscription { 150 | 151 | /** 152 | * Collection of values returned by `subscribe`, or the token, or the handler function 153 | */ 154 | private subscriptions: Set; 155 | 156 | constructor(topics?: string | Array, callback?: EventSubscriptionCallback) { 157 | this.subscriptions = new Set(); 158 | if (topics) { 159 | if (!callback) { throw new Error("SubscriptionCollection: callback is not set"); } 160 | this.subscribe(topics, callback); 161 | } 162 | } 163 | 164 | /** 165 | * Subscribe a single callback to a set of events 166 | * @param topics 167 | * @param callback 168 | */ 169 | public subscribe(topics: string | Array, callback: EventSubscriptionCallback): void { 170 | 171 | topics = UtilsInternal.ensureArray(topics); 172 | 173 | topics.forEach((topic: string) => { 174 | const subscriptionKey = PubSub.subscribe(topic, callback); 175 | this.subscriptions.add(new EventSubscription(subscriptionKey)); 176 | }); 177 | } 178 | 179 | /** 180 | * Unsubscribe from all of the events 181 | * @param milliseconds number of milliseconds to timeout. 182 | * Default is -1 which means not to timeout at all. 183 | */ 184 | public unsubscribe(milliseconds: number = -1): Promise { 185 | const promises = new Array>(); 186 | this.subscriptions.forEach((s: EventSubscription) => { 187 | promises.push(s.unsubscribe.call(s, milliseconds)); 188 | }); 189 | 190 | return Promise.all(promises).then(() => { 191 | this.subscriptions.clear(); 192 | }); 193 | } 194 | } 195 | 196 | export type EventSubscriptionCallback = (topic: string, payload: any) => any; 197 | export type EventSubscriptionKey = string | EventSubscriptionCallback; 198 | 199 | export interface IEventSubscription { 200 | unsubscribe(milliseconds?: number): Promise; 201 | } 202 | 203 | export class EventSubscription implements IEventSubscription { 204 | public constructor(private key: EventSubscriptionKey) { 205 | } 206 | 207 | /** 208 | * Unsubscribes after optional timeout. 209 | * @param milliseconds number of milliseconds to timeout. 210 | * Default is -1 which means not to timeout at all. 211 | */ 212 | public unsubscribe(milliseconds: number = -1): Promise { 213 | if (milliseconds === -1) { 214 | PubSub.unsubscribe(this.key); 215 | return Promise.resolve(); 216 | } 217 | // timeout to allow lingering events to be handled before unsubscribing 218 | return new Promise((resolve: fnVoid): void => { 219 | setTimeout(() => { 220 | PubSub.unsubscribe(this.key); 221 | resolve(); 222 | }, milliseconds); 223 | }); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /lib/schemeWrapperBase.ts: -------------------------------------------------------------------------------- 1 | import { Address, DefaultSchemePermissions, SchemePermissions } from "./commonTypes"; 2 | import { ContractWrapperBase } from "./contractWrapperBase"; 3 | import { ControllerService } from "./controllerService"; 4 | import { ISchemeWrapper } from "./iContractWrapperBase"; 5 | 6 | /** 7 | * Abstract base class for all Arc scheme contract wrapper classes. A scheme is defined as an Arc 8 | * contract that can be registered with and can thus interact with a DAO controller. 9 | */ 10 | export abstract class SchemeWrapperBase extends ContractWrapperBase implements ISchemeWrapper { 11 | /** 12 | * Minimum permissions required by the scheme 13 | */ 14 | public getDefaultPermissions(): SchemePermissions { 15 | return DefaultSchemePermissions.MinimumPermissions as number; 16 | } 17 | 18 | /** 19 | * Returns the scheme permissions. 20 | * @param avatarAddress 21 | */ 22 | public getSchemePermissions(avatarAddress: Address): Promise { 23 | return Promise.resolve(this.getDefaultPermissions()); 24 | } 25 | 26 | /** 27 | * Returns this scheme's permissions. 28 | * @param avatarAddress 29 | */ 30 | protected async _getSchemePermissions(avatarAddress: Address): Promise { 31 | const controllerService = new ControllerService(avatarAddress); 32 | const controller = await controllerService.getController(); 33 | const permissions = await controller.getSchemePermissions(this.address, avatarAddress) as string; 34 | 35 | return SchemePermissions.fromString(permissions); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/scripts/createGenesisDao.ts: -------------------------------------------------------------------------------- 1 | import { Web3 } from "web3"; 2 | import { DAO, InitializeArcJs } from "../index"; 3 | 4 | /* tslint:disable:no-console */ 5 | /* tslint:disable:max-line-length */ 6 | 7 | interface FounderSpec { 8 | /** 9 | * Founders' address 10 | */ 11 | address: string; 12 | /** 13 | * string | number token amount to be awarded to each founder, in GEN 14 | */ 15 | tokens: string | number; 16 | /** 17 | * string | number reputation amount to be awarded to each founder, 18 | * in units of the Genesis Reputation system. 19 | */ 20 | reputation: string | number; 21 | } 22 | 23 | /** 24 | * Migration callback 25 | */ 26 | export class GenesisDaoCreator { 27 | 28 | constructor( 29 | private web3: Web3) { 30 | } 31 | 32 | public async run(): Promise { 33 | 34 | const spec = { 35 | founders: [ 36 | { 37 | address: "0xb0c908140fe6fd6fbd4990a5c2e35ca6dc12bfb2", 38 | reputation: "1000", 39 | tokens: "1000", 40 | }, 41 | { 42 | address: "0x9c7f9f45a22ad3d667a5439f72b563df3aa70aae", 43 | reputation: "1000", 44 | tokens: "1000", 45 | }, 46 | { 47 | address: "0xa2a064b3b22fc892dfb71923a6d844b953aa247c", 48 | reputation: "1000", 49 | tokens: "1000", 50 | }, 51 | { 52 | address: "0xdeeaa92e025ca7fe34679b0b92cd4ffa162c8de8", 53 | reputation: "1000", 54 | tokens: "1000", 55 | }, 56 | { 57 | address: "0x81cfdaf70273745a291a7cf9af801a4cffa87a95", 58 | reputation: "1000", 59 | tokens: "1000", 60 | }, 61 | { 62 | address: "0x8ec400484deb5330bcd0bc005c13a557c5247727", 63 | reputation: "1000", 64 | tokens: "1000", 65 | }, 66 | ], 67 | name: "Genesis Test", 68 | schemes: [ 69 | { 70 | name: "SchemeRegistrar", 71 | votingMachineParams: { 72 | votingMachineName: "AbsoluteVote", 73 | }, 74 | }, 75 | { 76 | name: "GlobalConstraintRegistrar", 77 | votingMachineParams: { 78 | votingMachineName: "AbsoluteVote", 79 | }, 80 | }, 81 | { 82 | name: "UpgradeScheme", 83 | votingMachineParams: { 84 | votingMachineName: "AbsoluteVote", 85 | }, 86 | }, 87 | { 88 | name: "ContributionReward", 89 | votingMachineParams: { 90 | votingMachineName: "GenesisProtocol", 91 | }, 92 | }, 93 | ], 94 | tokenName: "Genesis Test", 95 | tokenSymbol: "GDT", 96 | }; 97 | 98 | await InitializeArcJs(); 99 | 100 | spec.founders = spec.founders.map((f: FounderSpec) => { 101 | return { 102 | address: f.address, 103 | reputation: this.web3.toWei(f.reputation), 104 | tokens: this.web3.toWei(f.tokens), 105 | }; 106 | }); 107 | 108 | console.log(`Genesis Test DAO with ${spec.founders.length} founders...`); 109 | 110 | const dao = await DAO.new(spec); 111 | 112 | console.log(`new DAO created at: ${dao.avatar.address}`); 113 | console.log(`native token: ${dao.token.address}`); 114 | 115 | return Promise.resolve(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lib/test/wrappers/testWrapper.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import { DefaultSchemePermissions, Hash, SchemePermissions } from "../../commonTypes"; 3 | import { ContractWrapperBase } from "../../contractWrapperBase"; 4 | import { ContractWrapperFactory } from "../../contractWrapperFactory"; 5 | import { ArcTransactionDataResult, IContractWrapperFactory } from "../../iContractWrapperBase"; 6 | import { TxGeneratingFunctionOptions } from "../../transactionService"; 7 | import { Web3EventService } from "../../web3EventService"; 8 | import { AbsoluteVoteParams } from "../../wrappers/absoluteVote"; 9 | 10 | export class TestWrapperWrapper extends ContractWrapperBase { 11 | 12 | public name: string = "AbsoluteVote"; 13 | public friendlyName: string = "Test Wrapper"; 14 | public factory: IContractWrapperFactory = TestWrapperFactory; 15 | 16 | public foo(): string { 17 | return "bar"; 18 | } 19 | 20 | public aMethod(): string { 21 | return "abc"; 22 | } 23 | 24 | public setParameters( 25 | params: AbsoluteVoteParams & TxGeneratingFunctionOptions): Promise> { 26 | params = Object.assign({}, 27 | { 28 | ownerVote: true, 29 | votePerc: 50, 30 | }, 31 | params); 32 | 33 | return super._setParameters( 34 | "AbsoluteVote.setParameters", 35 | params.txEventContext, 36 | params.votePerc, 37 | params.ownerVote 38 | ); 39 | } 40 | 41 | public getDefaultPermissions(): SchemePermissions { 42 | return DefaultSchemePermissions.MinimumPermissions as number; 43 | } 44 | } 45 | 46 | export const TestWrapperFactory = 47 | new ContractWrapperFactory("AbsoluteVote", TestWrapperWrapper, new Web3EventService()); 48 | -------------------------------------------------------------------------------- /lib/uSchemeWrapperBase.ts: -------------------------------------------------------------------------------- 1 | import { Address, Hash } from "./commonTypes"; 2 | import { ControllerService } from "./controllerService"; 3 | import { 4 | ArcTransactionDataResult, 5 | IUniversalSchemeWrapper, 6 | StandardSchemeParams, 7 | } from "./iContractWrapperBase"; 8 | import { SchemeWrapperBase } from "./schemeWrapperBase"; 9 | import { TxEventContext } from "./transactionService"; 10 | 11 | /** 12 | * Abstract base class for all Arc universal scheme contract wrapper classes. A universal scheme 13 | * is defined as an Arc scheme (see `SchemeWrapperBase`) that follows the pattern of registering 14 | * operating parameters with the DAO's controller, thus enabling the contract to be reused across DAOs. 15 | */ 16 | export abstract class USchemeWrapperBase extends SchemeWrapperBase { 17 | 18 | /** 19 | * Given a hash, returns the associated parameters as an object. 20 | * @param paramsHash 21 | */ 22 | public abstract getParameters(paramsHash: Hash): Promise; 23 | 24 | public abstract getParametersHash(params: any): Promise; 25 | 26 | public abstract setParameters(params: any): Promise>; 27 | 28 | public abstract getSchemeParameters(avatarAddress: Address): Promise; 29 | 30 | /** 31 | * Given an avatar address, returns the schemes parameters hash 32 | * @param avatarAddress 33 | */ 34 | public async getSchemeParametersHash(avatarAddress: Address): Promise { 35 | const controllerService = new ControllerService(avatarAddress); 36 | const controller = await controllerService.getController(); 37 | return controller.getSchemeParameters(this.address, avatarAddress); 38 | } 39 | 40 | /** 41 | * Given a hash, returns the associated parameters as an array, ordered by the order 42 | * in which the parameters appear in the contract's Parameters struct. 43 | * @param paramsHash 44 | */ 45 | public getParametersArray(paramsHash: Hash): Promise> { 46 | return this.contract.parameters(paramsHash); 47 | } 48 | protected async _setParameters( 49 | functionName: string, 50 | txEventContext: TxEventContext, 51 | ...params: Array): Promise> { 52 | 53 | const parametersHash: Hash = await this.contract.getParametersHash(...params); 54 | 55 | const txResult = await this.wrapTransactionInvocation(functionName, 56 | // typically this is supposed to be an object, but here it is an array 57 | Object.assign(params, { txEventContext }), 58 | this.contract.setParameters, 59 | params); 60 | 61 | return new ArcTransactionDataResult(txResult.tx, this.contract, parametersHash); 62 | } 63 | 64 | protected async _getSchemeParameters(avatarAddress: Address): Promise { 65 | const paramsHash = await this.getSchemeParametersHash(avatarAddress); 66 | return this.getParameters(paramsHash); 67 | } 68 | 69 | protected _getParametersHash(...params: Array): Promise { 70 | return this.contract.getParametersHash(...params); 71 | } 72 | 73 | protected validateStandardSchemeParams(params: StandardSchemeParams): void { 74 | if (!params.voteParametersHash) { 75 | throw new Error(`voteParametersHash is not defined`); 76 | } 77 | if (!params.votingMachineAddress) { 78 | throw new Error(`votingMachineAddress is not defined`); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/utilsInternal.ts: -------------------------------------------------------------------------------- 1 | import { promisify } from "es6-promisify"; 2 | import { BlockWithoutTransactionData, FilterResult } from "web3"; 3 | import { Address, fnVoid, Hash } from "./commonTypes"; 4 | import { Utils, Web3 } from "./utils"; 5 | 6 | /** 7 | * Utils not meant to be exported to the public 8 | */ 9 | export class UtilsInternal { 10 | 11 | public static sleep(milliseconds: number): Promise { 12 | return new Promise((resolve: fnVoid): any => setTimeout(resolve, milliseconds)); 13 | } 14 | 15 | public static ensureArray(arr: Array | T): Array { 16 | if (!Array.isArray(arr)) { 17 | arr = [arr]; 18 | } 19 | return arr; 20 | } 21 | 22 | /** 23 | * Returns the last mined block in the chain. 24 | */ 25 | public static async lastBlock(): Promise { 26 | const web3 = await Utils.getWeb3(); 27 | return promisify((callback: any): any => web3.eth.getBlock("latest", callback))() as any; 28 | } 29 | 30 | /** 31 | * Returns the date of the last mined block in the chain. 32 | */ 33 | public static async lastBlockDate(): Promise { 34 | const web3 = await Utils.getWeb3(); 35 | let block; 36 | do { 37 | block = await promisify((callback: any): any => 38 | web3.eth.getBlock("latest", callback))() as BlockWithoutTransactionData; 39 | } 40 | while (!block); 41 | 42 | return new Date(block.timestamp * 1000); 43 | } 44 | 45 | /** 46 | * Returns the last mined block in the chain. 47 | */ 48 | public static async lastBlockNumber(): Promise { 49 | const web3 = await Utils.getWeb3(); 50 | return promisify(web3.eth.getBlockNumber)(); 51 | } 52 | 53 | /** 54 | * For environments that don't allow synchronous functions 55 | * @param filter 56 | */ 57 | public static stopWatchingAsync(filter: FilterResult): Promise { 58 | return promisify((callback: any): any => filter.stopWatching(callback))(); 59 | } 60 | 61 | public static getRandomNumber(): number { 62 | return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); 63 | } 64 | 65 | public static getWeb3Sync(): Web3 { 66 | return (Utils as any).web3; 67 | } 68 | 69 | public static isNullAddress(address: Address): boolean { 70 | return !address || !Number.parseInt(address, 16); 71 | } 72 | 73 | public static isNullHash(hash: Hash): boolean { 74 | return !hash || !Number.parseInt(hash, 16); 75 | } 76 | 77 | /** 78 | * Returns promise of the maximum gasLimit that we dare to ever use, given the 79 | * current state of the chain. 80 | */ 81 | public static async computeMaxGasLimit(): Promise { 82 | const web3 = await Utils.getWeb3(); 83 | return promisify((callback: any) => web3.eth.getBlock("latest", false, callback))() 84 | .then((block: any) => { 85 | return block.gasLimit - 100000; 86 | }); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/wrappers/absoluteVote.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import { Address, Hash } from "../commonTypes"; 3 | 4 | import { ContractWrapperFactory } from "../contractWrapperFactory"; 5 | import { 6 | ArcTransactionDataResult, 7 | ArcTransactionProposalResult, 8 | ArcTransactionResult, 9 | DecodedLogEntryEvent, 10 | IContractWrapperFactory, 11 | IVotingMachineWrapper 12 | } from "../iContractWrapperBase"; 13 | import { ProposalService, VotableProposal } from "../proposalService"; 14 | import { TransactionService, TxGeneratingFunctionOptions } from "../transactionService"; 15 | import { EntityFetcherFactory, EventFetcherFactory, Web3EventService } from "../web3EventService"; 16 | import { 17 | NewProposalEventResult, 18 | OwnerVoteOptions, 19 | ProposalIdOption, 20 | ProposeOptions, 21 | VoteOptions, 22 | VoteWithSpecifiedAmountsOptions 23 | } from "./iIntVoteInterface"; 24 | 25 | import { BigNumber } from "bignumber.js"; 26 | import { IntVoteInterfaceWrapper } from "./intVoteInterface"; 27 | 28 | export class AbsoluteVoteWrapper extends IntVoteInterfaceWrapper 29 | implements IVotingMachineWrapper { 30 | 31 | public name: string = "AbsoluteVote"; 32 | public friendlyName: string = "Absolute Vote"; 33 | public factory: IContractWrapperFactory = AbsoluteVoteFactory; 34 | 35 | /** 36 | * Events 37 | */ 38 | public AVVoteProposal: EventFetcherFactory; 39 | public RefreshReputation: EventFetcherFactory; 40 | 41 | /** 42 | * EntityFetcherFactory for votable proposals. 43 | * @param avatarAddress 44 | */ 45 | public get VotableAbsoluteVoteProposals(): 46 | EntityFetcherFactory { 47 | 48 | const proposalService = new ProposalService(this.web3EventService); 49 | 50 | return proposalService.getProposalEvents({ 51 | proposalsEventFetcher: this.NewProposal, 52 | transformEventCallback: async (event: DecodedLogEntryEvent): Promise => { 53 | return { 54 | avatarAddress: event.args._organization, 55 | numOfChoices: event.args._numOfChoices.toNumber(), 56 | paramsHash: event.args._paramsHash, 57 | proposalId: event.args._proposalId, 58 | proposerAddress: event.args._proposer, 59 | }; 60 | }, 61 | votableOnly: true, 62 | votingMachine: this, 63 | }); 64 | } 65 | 66 | public getParametersHash(params: AbsoluteVoteParams): Promise { 67 | params = Object.assign({}, 68 | { 69 | ownerVote: true, 70 | votePerc: 50, 71 | }, 72 | params); 73 | 74 | return this._getParametersHash( 75 | params.votePerc, 76 | params.ownerVote); 77 | } 78 | 79 | public setParameters( 80 | params: AbsoluteVoteParams & TxGeneratingFunctionOptions) 81 | : Promise> { 82 | 83 | params = Object.assign({}, 84 | { 85 | ownerVote: true, 86 | votePerc: 50, 87 | }, 88 | params); 89 | 90 | return super._setParameters( 91 | "AbsoluteVote.setParameters", 92 | params.txEventContext, 93 | params.votePerc, 94 | params.ownerVote 95 | ); 96 | } 97 | 98 | public async getParameters(paramsHash: Hash): Promise { 99 | const params = await this.getParametersArray(paramsHash); 100 | return { 101 | ownerVote: params[1], 102 | votePerc: params[0].toNumber(), 103 | }; 104 | } 105 | 106 | public async propose(options: ProposeOptions & TxGeneratingFunctionOptions): Promise { 107 | const functionName = "AbsoluteVote.propose"; 108 | const payload = TransactionService.publishKickoffEvent(functionName, options, 1); 109 | const eventContext = TransactionService.newTxEventContext(functionName, payload, options); 110 | return super.propose(Object.assign(options, { txEventContext: eventContext })); 111 | } 112 | 113 | public async vote(options: VoteOptions & TxGeneratingFunctionOptions): Promise { 114 | const functionName = "AbsoluteVote.vote"; 115 | const payload = TransactionService.publishKickoffEvent(functionName, options, 1); 116 | const eventContext = TransactionService.newTxEventContext(functionName, payload, options); 117 | return super.vote(Object.assign(options, { txEventContext: eventContext })); 118 | } 119 | 120 | public async voteWithSpecifiedAmounts( 121 | options: VoteWithSpecifiedAmountsOptions & TxGeneratingFunctionOptions): Promise { 122 | const functionName = "AbsoluteVote.voteWithSpecifiedAmounts"; 123 | const payload = TransactionService.publishKickoffEvent(functionName, options, 1); 124 | const eventContext = TransactionService.newTxEventContext(functionName, payload, options); 125 | return super.voteWithSpecifiedAmounts(Object.assign(options, { txEventContext: eventContext })); 126 | } 127 | public async execute(options: ProposalIdOption & TxGeneratingFunctionOptions): Promise { 128 | const functionName = "AbsoluteVote.execute"; 129 | const payload = TransactionService.publishKickoffEvent(functionName, options, 1); 130 | const eventContext = TransactionService.newTxEventContext(functionName, payload, options); 131 | return super.execute(Object.assign(options, { txEventContext: eventContext })); 132 | } 133 | public async cancelProposal(options: ProposalIdOption & TxGeneratingFunctionOptions): Promise { 134 | const functionName = "AbsoluteVote.execute"; 135 | const payload = TransactionService.publishKickoffEvent(functionName, options, 1); 136 | const eventContext = TransactionService.newTxEventContext(functionName, payload, options); 137 | return super.cancelProposal(Object.assign(options, { txEventContext: eventContext })); 138 | } 139 | public async ownerVote(options: OwnerVoteOptions & TxGeneratingFunctionOptions): Promise { 140 | const functionName = "AbsoluteVote.execute"; 141 | const payload = TransactionService.publishKickoffEvent(functionName, options, 1); 142 | const eventContext = TransactionService.newTxEventContext(functionName, payload, options); 143 | return super.ownerVote(Object.assign(options, { txEventContext: eventContext })); 144 | } 145 | public async cancelVote(options: ProposalIdOption & TxGeneratingFunctionOptions): Promise { 146 | const functionName = "AbsoluteVote.execute"; 147 | const payload = TransactionService.publishKickoffEvent(functionName, options, 1); 148 | const eventContext = TransactionService.newTxEventContext(functionName, payload, options); 149 | return super.cancelVote(Object.assign(options, { txEventContext: eventContext })); 150 | } 151 | 152 | protected hydrated(): void { 153 | super.hydrated(); 154 | /* tslint:disable:max-line-length */ 155 | this.AVVoteProposal = this.createEventFetcherFactory(this.contract.AVVoteProposal); 156 | this.RefreshReputation = this.createEventFetcherFactory(this.contract.RefreshReputation); 157 | /* tslint:enable:max-line-length */ 158 | } 159 | } 160 | 161 | export const AbsoluteVoteFactory = 162 | new ContractWrapperFactory("AbsoluteVote", AbsoluteVoteWrapper, new Web3EventService()); 163 | 164 | export interface AbsoluteVoteParams { 165 | ownerVote?: boolean; 166 | votePerc?: number; 167 | } 168 | 169 | export interface AbsoluteVoteParamsResult { 170 | ownerVote: boolean; 171 | votePerc: number; 172 | } 173 | 174 | export interface AVVoteProposalEventResult { 175 | /** 176 | * indexed 177 | */ 178 | _proposalId: Hash; 179 | _isOwnerVote: boolean; 180 | } 181 | 182 | export interface RefreshReputationEventResult { 183 | /** 184 | * indexed 185 | */ 186 | _proposalId: Hash; 187 | /** 188 | * indexed 189 | */ 190 | _organization: Address; 191 | /** 192 | * indexed 193 | */ 194 | _voter: Address; 195 | 196 | _reputation: BigNumber; 197 | } 198 | -------------------------------------------------------------------------------- /lib/wrappers/commonEventInterfaces.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "bignumber.js"; 2 | import { Address, Hash } from "../commonTypes"; 3 | 4 | export interface ProposalDeletedEventResult { 5 | /** 6 | * indexed 7 | */ 8 | _avatar: Address; 9 | /** 10 | * indexed 11 | */ 12 | _proposalId: Hash; 13 | } 14 | 15 | /** 16 | * fired by schemes 17 | */ 18 | export interface ProposalExecutedEventResult { 19 | /** 20 | * indexed 21 | */ 22 | _avatar: Address; 23 | _param: number; 24 | /** 25 | * indexed 26 | */ 27 | _proposalId: Hash; 28 | } 29 | 30 | /** 31 | * fired by schemes 32 | */ 33 | export interface SchemeProposalExecuted { 34 | avatarAddress: Address; 35 | winningVote: number; 36 | proposalId: Hash; 37 | } 38 | 39 | /** 40 | * fired by schemes 41 | */ 42 | export interface SchemeProposalExecutedEventResult { 43 | /** 44 | * indexed 45 | */ 46 | _avatar: Address; 47 | /** 48 | * typically the winning vote 49 | */ 50 | _param: number; 51 | /** 52 | * indexed 53 | */ 54 | _proposalId: Hash; 55 | } 56 | -------------------------------------------------------------------------------- /lib/wrappers/externalLocking4Reputation.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import BigNumber from "bignumber.js"; 3 | import { promisify } from "es6-promisify"; 4 | import { Address } from "../commonTypes"; 5 | import { ContractWrapperFactory } from "../contractWrapperFactory"; 6 | import { ArcTransactionResult, IContractWrapperFactory } from "../iContractWrapperBase"; 7 | import { TxGeneratingFunctionOptions } from "../transactionService"; 8 | import { Utils } from "../utils"; 9 | import { Web3EventService } from "../web3EventService"; 10 | import { Locking4ReputationWrapper } from "./locking4Reputation"; 11 | 12 | export class ExternalLocking4ReputationWrapper extends Locking4ReputationWrapper { 13 | public name: string = "ExternalLocking4Reputation"; 14 | public friendlyName: string = "External Locking For Reputation"; 15 | public factory: IContractWrapperFactory = ExternalLocking4ReputationFactory; 16 | 17 | public async initialize(options: ExternalLockingInitializeOptions & TxGeneratingFunctionOptions) 18 | : Promise { 19 | 20 | await super._initialize(options, false); 21 | 22 | if (!options.externalLockingContract) { 23 | throw new Error("externalLockingContract is not defined"); 24 | } 25 | if (!options.getBalanceFuncSignature) { 26 | throw new Error("getBalanceFuncSignature is not defined"); 27 | } 28 | 29 | this.logContractFunctionCall("ExternalLocking4Reputation.initialize", options); 30 | 31 | return this.wrapTransactionInvocation("ExternalLocking4Reputation.initialize", 32 | options, 33 | this.contract.initialize, 34 | [options.avatarAddress, 35 | options.reputationReward, 36 | options.lockingStartTime.getTime() / 1000, 37 | options.lockingEndTime.getTime() / 1000, 38 | options.redeemEnableTime.getTime() / 1000, 39 | options.externalLockingContract, 40 | options.getBalanceFuncSignature] 41 | ); 42 | } 43 | 44 | public async getLockBlocker(options: ExternalLockingClaimOptions): Promise { 45 | /** 46 | * stub out lockerAddress, amount and period -- they aren't relevant to external locking validation. 47 | */ 48 | const msg = await super.getLockBlocker(Object.assign({}, 49 | { lockerAddress: "0x", amount: "1", period: 1 } 50 | )); 51 | 52 | if (msg) { 53 | return msg; 54 | } 55 | 56 | const alreadyLocked = await this.getAccountHasLocked(options.lockerAddress); 57 | if (alreadyLocked) { 58 | return "account has already executed a claim"; 59 | } 60 | 61 | const currentAccount = (await Utils.getDefaultAccount()).toLowerCase(); 62 | let lockerAddress: Address | number = options.lockerAddress; 63 | 64 | if (lockerAddress && (lockerAddress.toLowerCase() === currentAccount)) { 65 | lockerAddress = 0; 66 | } 67 | 68 | if (lockerAddress && !(await this.isRegistered(lockerAddress as Address))) { 69 | throw new Error(`account does not own any MGN tokens`); 70 | } 71 | } 72 | 73 | /** 74 | * Claim the MGN tokens and lock them. Provide `lockerAddress` to claim on their behalf, 75 | * otherwise claims on behalf of the caller. 76 | * @param options 77 | */ 78 | public async lock( 79 | options: ExternalLockingClaimOptions & TxGeneratingFunctionOptions): Promise { 80 | 81 | const msg = await this.getLockBlocker(options); 82 | if (msg) { 83 | throw new Error(msg); 84 | } 85 | 86 | const currentAccount = (await Utils.getDefaultAccount()).toLowerCase(); 87 | let lockerAddress: Address | number = options.lockerAddress; 88 | 89 | if (lockerAddress && (lockerAddress.toLowerCase() === currentAccount)) { 90 | lockerAddress = 0; 91 | } 92 | 93 | this.logContractFunctionCall("ExternalLocking4Reputation.claim", options); 94 | 95 | return this.wrapTransactionInvocation("ExternalLocking4Reputation.claim", 96 | options, 97 | this.contract.claim, 98 | [lockerAddress] 99 | ); 100 | } 101 | 102 | /** 103 | * The caller is giving permission to the contract to allow someone else to claim 104 | * on their behalf. 105 | */ 106 | public async register(): Promise { 107 | 108 | this.logContractFunctionCall("ExternalLocking4Reputation.register"); 109 | 110 | return this.wrapTransactionInvocation("ExternalLocking4Reputation.register", 111 | {}, 112 | this.contract.register, 113 | [] 114 | ); 115 | } 116 | 117 | /** 118 | * Returns promise of whether the given locker has tokens that can be activated in the given MGN token contract. 119 | * Assumes that MGN token API is: `lockedTokenBalances(address)`. 120 | * 121 | * @param lockerAddress 122 | * @param mgnTokenAddress 123 | */ 124 | public async hasMgnToActivate(lockerAddress: Address): Promise { 125 | 126 | const web3 = await Utils.getWeb3(); 127 | 128 | const mgnTokenAddress = await this.getExternalLockingContract(); 129 | 130 | // tslint:disable 131 | const mgnToken = await web3.eth.contract( 132 | [ 133 | { 134 | constant: true, 135 | inputs: [ 136 | { 137 | name: "", 138 | type: "address" 139 | }, 140 | ], 141 | name: "lockedTokenBalances", 142 | outputs: [ 143 | { 144 | name: "", 145 | type: "uint256" 146 | }, 147 | ], 148 | payable: false, 149 | stateMutability: "view", 150 | "type": "function", 151 | } 152 | ] as any 153 | ).at(mgnTokenAddress); 154 | // tslint:enable 155 | 156 | const balance = await promisify((callback: any): any => 157 | mgnToken.lockedTokenBalances(lockerAddress, callback))() as any; 158 | 159 | return balance.gt(0); 160 | } 161 | 162 | /** 163 | * Returns promise of a boolean indicating whether the given address has registered 164 | * to have their tokens claimed for them (see `register`). 165 | * @param lockerAddress 166 | */ 167 | public isRegistered(lockerAddress: Address): Promise { 168 | this.logContractFunctionCall("ExternalLocking4Reputation.registrar", { lockerAddress }); 169 | return this.contract.registrar(lockerAddress); 170 | } 171 | 172 | public getExternalLockingContract(): Promise
{ 173 | this.logContractFunctionCall("ExternalLocking4Reputation.externalLockingContract"); 174 | return this.contract.externalLockingContract(); 175 | } 176 | 177 | public getGetBalanceFuncSignature(): Promise { 178 | this.logContractFunctionCall("ExternalLocking4Reputation.getBalanceFuncSignature"); 179 | return this.contract.getBalanceFuncSignature(); 180 | } 181 | 182 | /** 183 | * Promise of `true` if the given account has already executed a lock 184 | */ 185 | public getAccountHasLocked(lockerAddress: Address): Promise { 186 | if (!lockerAddress) { 187 | throw new Error("lockerAddress is not defined"); 188 | } 189 | this.logContractFunctionCall("ExternalLocking4Reputation.externalLockers"); 190 | return this.contract.externalLockers(lockerAddress); 191 | } 192 | 193 | } 194 | 195 | export class ExternalLocking4ReputationType extends ContractWrapperFactory { 196 | 197 | public async deployed(): Promise { 198 | throw new Error("ExternalLocking4Reputation has not been deployed"); 199 | } 200 | } 201 | 202 | export const ExternalLocking4ReputationFactory = 203 | new ExternalLocking4ReputationType( 204 | "ExternalLocking4Reputation", 205 | ExternalLocking4ReputationWrapper, 206 | new Web3EventService()) as ExternalLocking4ReputationType; 207 | 208 | export interface ExternalLockingInitializeOptions { 209 | avatarAddress: Address; 210 | externalLockingContract: Address; 211 | getBalanceFuncSignature: string; 212 | lockingEndTime: Date; 213 | lockingStartTime: Date; 214 | /** 215 | * Reputation cannot be redeemed until after this time, even if redeeming has been enabled. 216 | */ 217 | redeemEnableTime: Date; 218 | reputationReward: BigNumber | string; 219 | } 220 | 221 | export interface ExternalLockingClaimOptions { 222 | lockerAddress?: Address; 223 | } 224 | -------------------------------------------------------------------------------- /lib/wrappers/iBurnableToken.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import { BigNumber } from "bignumber.js"; 3 | import { Address } from "../commonTypes"; 4 | import { ArcTransactionResult } from "../iContractWrapperBase"; 5 | import { TxGeneratingFunctionOptions } from "../transactionService"; 6 | import { EventFetcherFactory } from "../web3EventService"; 7 | 8 | export interface IBurnableTokenWrapper { 9 | 10 | Burn: EventFetcherFactory; 11 | 12 | /** 13 | * Burn the given number of tokens 14 | * @param options 15 | */ 16 | burn(options: BurnableTokenBurnOptions & TxGeneratingFunctionOptions): Promise; 17 | } 18 | 19 | export interface BurnableTokenBurnOptions { 20 | /** 21 | * Amount to burn 22 | */ 23 | amount: BigNumber; 24 | } 25 | 26 | export interface BurnEventResult { 27 | /** 28 | * Who burnt the tokens 29 | * indexed 30 | */ 31 | burner: Address; 32 | /** 33 | * Amount burnt 34 | */ 35 | value: BigNumber; 36 | } 37 | -------------------------------------------------------------------------------- /lib/wrappers/iErc827Token.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import { ArcTransactionResult } from "../iContractWrapperBase"; 3 | import { TxGeneratingFunctionOptions } from "../transactionService"; 4 | import { 5 | StandardTokenApproveOptions, 6 | StandardTokenChangeApprovalOptions, 7 | StandardTokenTransferFromOptions, 8 | StandardTokenTransferOptions 9 | } from "./standardToken"; 10 | 11 | export interface IErc827TokenWrapper { 12 | 13 | /** 14 | * Approve transfer of tokens by msg.sender (or `onBehalfOf`, if given) 15 | * from the given "spender". Then call the function specified 16 | * by `callData`, all in a single transaction. 17 | * @param options 18 | */ 19 | approveAndCall(options: ApproveAndCallOptions & TxGeneratingFunctionOptions): Promise; 20 | 21 | /** 22 | * Transfer tokens from the current account to another. Then call the function specified 23 | * by `callData`, all in a single transaction. 24 | * @param options 25 | */ 26 | transferAndCall(options: TransferAndCallOptions & TxGeneratingFunctionOptions): Promise; 27 | 28 | /** 29 | * Transfer tokens from one address to another. Then call the function specified 30 | * by `callData`, all in a single transaction. 31 | * @param options 32 | */ 33 | transferFromAndCall(options: TransferFromAndCallOptions & TxGeneratingFunctionOptions) 34 | : Promise; 35 | 36 | /** 37 | * Increase the number of tokens approved that msg.sender (or `onBehalfOf`, if given) 38 | * may transfer from the given "spender". 39 | * Then call the function specified by `callData`, all in a single transaction. 40 | * @param options 41 | */ 42 | increaseApprovalAndCall(options: ChangeApprovalAndCallOptions & TxGeneratingFunctionOptions) 43 | : Promise; 44 | 45 | /** 46 | * Decrease the number of tokens approved that msg.sender (or `onBehalfOf` if given) 47 | * may transfer from the given "spender". 48 | * Then call the function specified by `callData`, all in a single transaction. 49 | * @param options 50 | */ 51 | decreaseApprovalAndCall(options: ChangeApprovalAndCallOptions & TxGeneratingFunctionOptions) 52 | : Promise; 53 | } 54 | 55 | export interface ApproveAndCallOptions extends StandardTokenApproveOptions { 56 | callData: string; 57 | } 58 | 59 | export interface TransferAndCallOptions extends StandardTokenTransferOptions { 60 | callData: string; 61 | } 62 | 63 | export interface TransferFromAndCallOptions extends StandardTokenTransferFromOptions { 64 | callData: string; 65 | } 66 | 67 | export interface ChangeApprovalAndCallOptions extends StandardTokenChangeApprovalOptions { 68 | callData: string; 69 | } 70 | -------------------------------------------------------------------------------- /lib/wrappers/iIntVoteInterface.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | import { Address, Hash } from "../commonTypes"; 3 | import { ArcTransactionProposalResult, ArcTransactionResult } from "../iContractWrapperBase"; 4 | import { TxGeneratingFunctionOptions } from "../transactionService"; 5 | import { EventFetcherFactory } from "../web3EventService"; 6 | 7 | /** 8 | * The Arc contract `IntVoteInterface`. 9 | */ 10 | export interface IIntVoteInterface { 11 | NewProposal: EventFetcherFactory; 12 | CancelProposal: EventFetcherFactory; 13 | ExecuteProposal: EventFetcherFactory; 14 | VoteProposal: EventFetcherFactory; 15 | CancelVoting: EventFetcherFactory; 16 | 17 | address: Address; 18 | 19 | propose(options: ProposeOptions & TxGeneratingFunctionOptions): Promise; 20 | cancelProposal(options: ProposalIdOption & TxGeneratingFunctionOptions): Promise; 21 | ownerVote(options: OwnerVoteOptions & TxGeneratingFunctionOptions): Promise; 22 | vote(options: VoteOptions & TxGeneratingFunctionOptions): Promise; 23 | voteWithSpecifiedAmounts( 24 | options: VoteWithSpecifiedAmountsOptions & TxGeneratingFunctionOptions): Promise; 25 | cancelVote(options: ProposalIdOption & TxGeneratingFunctionOptions): Promise; 26 | getNumberOfChoices(options: ProposalIdOption): Promise; 27 | isVotable(options: ProposalIdOption): Promise; 28 | voteStatus(options: VoteStatusOptions): Promise; 29 | isAbstainAllow(): Promise; 30 | execute(options: ProposalIdOption & TxGeneratingFunctionOptions): Promise; 31 | } 32 | 33 | export interface ProposeOptions { 34 | numOfChoices: number; 35 | /** 36 | * Typically this is the avatar address, but you can pass any address here, 37 | * or null, This argument is used to link a proposal-creating scheme with an organisation. 38 | * If it is not given then it will be set to the `msg.sender`. 39 | */ 40 | organizationAddress?: Address; 41 | proposerAddress?: Address; 42 | proposalParameters: Hash; 43 | } 44 | 45 | export interface OwnerVoteOptions extends ProposalIdOption { 46 | vote: number; 47 | voterAddress: Address; 48 | } 49 | 50 | export interface VoteOptions extends ProposalIdOption { 51 | vote: number; 52 | voterAddress?: Address; 53 | } 54 | 55 | export interface VoteWithSpecifiedAmountsOptions extends ProposalIdOption { 56 | reputation: BigNumber | string; 57 | vote: number; 58 | voterAddress?: Address; 59 | } 60 | 61 | export interface VoteStatusOptions extends ProposalIdOption { 62 | vote: number; 63 | } 64 | 65 | export interface ProposalIdOption { 66 | proposalId: Hash; 67 | } 68 | 69 | export interface CancelProposalEventResult { 70 | /** 71 | * indexed 72 | */ 73 | _organization: Address; 74 | /** 75 | * indexed 76 | */ 77 | _proposalId: Hash; 78 | } 79 | 80 | export interface CancelVotingEventResult { 81 | /** 82 | * indexed 83 | */ 84 | _organization: Address; 85 | /** 86 | * indexed 87 | */ 88 | _proposalId: Hash; 89 | /** 90 | * indexed 91 | */ 92 | _voter: Address; 93 | } 94 | 95 | export interface NewProposalEventResult { 96 | /** 97 | * indexed 98 | */ 99 | _organization: Address; 100 | _numOfChoices: BigNumber; 101 | _paramsHash: Hash; 102 | /** 103 | * indexed 104 | */ 105 | _proposalId: Hash; 106 | _proposer: Address; 107 | } 108 | 109 | /** 110 | * fired by voting machines 111 | */ 112 | export interface ExecuteProposalEventResult { 113 | /** 114 | * indexed 115 | */ 116 | _organization: Address; 117 | /** 118 | * the vote choice that won. 119 | */ 120 | _decision: BigNumber; 121 | /** 122 | * indexed 123 | */ 124 | _proposalId: Hash; 125 | /** 126 | * The total reputation in the DAO at the time the proposal was executed 127 | */ 128 | _totalReputation: BigNumber; 129 | } 130 | 131 | export interface VoteProposalEventResult { 132 | /** 133 | * indexed 134 | */ 135 | _organization: Address; 136 | /** 137 | * indexed 138 | */ 139 | _proposalId: Hash; 140 | _reputation: BigNumber; 141 | /** 142 | * The choice of vote 143 | */ 144 | _vote: BigNumber; 145 | /** 146 | * indexed 147 | */ 148 | _voter: Address; 149 | } 150 | 151 | export interface GetAllowedRangeOfChoicesResult { 152 | minVote: number; 153 | maxVote: number; 154 | } 155 | -------------------------------------------------------------------------------- /lib/wrappers/lockingEth4Reputation.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import BigNumber from "bignumber.js"; 3 | import { ContractWrapperFactory } from "../contractWrapperFactory"; 4 | import { ArcTransactionResult, IContractWrapperFactory } from "../iContractWrapperBase"; 5 | import { TxGeneratingFunctionOptions } from "../transactionService"; 6 | import { Utils } from "../utils"; 7 | import { Web3EventService } from "../web3EventService"; 8 | import { InitializeOptions, Locking4ReputationWrapper, LockingOptions, ReleaseOptions } from "./locking4Reputation"; 9 | 10 | export class LockingEth4ReputationWrapper extends Locking4ReputationWrapper { 11 | public name: string = "LockingEth4Reputation"; 12 | public friendlyName: string = "Locking Eth For Reputation"; 13 | public factory: IContractWrapperFactory = LockingEth4ReputationFactory; 14 | 15 | public async initialize(options: InitializeOptions & TxGeneratingFunctionOptions) 16 | : Promise { 17 | 18 | await super._initialize(options); 19 | 20 | this.logContractFunctionCall("LockingEth4Reputation.initialize", options); 21 | 22 | return this.wrapTransactionInvocation("LockingEth4Reputation.initialize", 23 | options, 24 | this.contract.initialize, 25 | [options.avatarAddress, 26 | options.reputationReward, 27 | options.lockingStartTime.getTime() / 1000, 28 | options.lockingEndTime.getTime() / 1000, 29 | options.redeemEnableTime.getTime() / 1000, 30 | options.maxLockingPeriod] 31 | ); 32 | } 33 | 34 | public async release(options: ReleaseOptions & TxGeneratingFunctionOptions): Promise { 35 | 36 | await super._release(options); 37 | 38 | this.logContractFunctionCall("LockingEth4Reputation.release", options); 39 | 40 | return this.wrapTransactionInvocation("LockingEth4Reputation.release", 41 | options, 42 | this.contract.release, 43 | [options.lockerAddress, options.lockId] 44 | ); 45 | } 46 | 47 | /** 48 | * Returns reason why can't lock, else null if can lock 49 | */ 50 | public async getLockBlocker(options: LockingOptions): Promise { 51 | 52 | const msg = await super.getLockBlocker(options); 53 | if (msg) { 54 | return msg; 55 | } 56 | 57 | const balance = await Utils.getEthBalance(options.lockerAddress); 58 | const amount = new BigNumber(options.amount); 59 | 60 | if (balance.lt(amount)) { 61 | return "the account has insufficient balance"; 62 | } 63 | return null; 64 | } 65 | 66 | public async lock(options: LockingOptions & TxGeneratingFunctionOptions): Promise { 67 | 68 | const msg = await this.getLockBlocker(options); 69 | if (msg) { 70 | throw new Error(msg); 71 | } 72 | 73 | this.logContractFunctionCall("LockingEth4Reputation.lock", options); 74 | 75 | return this.wrapTransactionInvocation("LockingEth4Reputation.lock", 76 | options, 77 | this.contract.lock, 78 | [options.period], 79 | { from: options.lockerAddress, value: options.amount } 80 | ); 81 | } 82 | } 83 | 84 | export class LockingEth4ReputationType extends ContractWrapperFactory { 85 | 86 | public async deployed(): Promise { 87 | throw new Error("LockingEth4Reputation has not been deployed"); 88 | } 89 | } 90 | 91 | export const LockingEth4ReputationFactory = 92 | new LockingEth4ReputationType( 93 | "LockingEth4Reputation", 94 | LockingEth4ReputationWrapper, 95 | new Web3EventService()) as LockingEth4ReputationType; 96 | -------------------------------------------------------------------------------- /lib/wrappers/lockingToken4Reputation.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import BigNumber from "bignumber.js"; 3 | import { Address, Hash } from "../commonTypes"; 4 | import { ContractWrapperFactory } from "../contractWrapperFactory"; 5 | import { ArcTransactionResult, IContractWrapperFactory } from "../iContractWrapperBase"; 6 | import { TxGeneratingFunctionOptions } from "../transactionService"; 7 | import { Utils } from "../utils"; 8 | import { EventFetcherFactory, Web3EventService } from "../web3EventService"; 9 | import { WrapperService } from "../wrapperService"; 10 | import { InitializeOptions, Locking4ReputationWrapper, LockingOptions, ReleaseOptions } from "./locking4Reputation"; 11 | import { StandardTokenFactory, StandardTokenWrapper } from "./standardToken"; 12 | 13 | export class LockingToken4ReputationWrapper extends Locking4ReputationWrapper { 14 | public name: string = "LockingToken4Reputation"; 15 | public friendlyName: string = "Locking Token For Reputation"; 16 | public factory: IContractWrapperFactory = LockingToken4ReputationFactory; 17 | 18 | public LockToken: EventFetcherFactory; 19 | 20 | public async initialize(options: LockTokenInitializeOptions & TxGeneratingFunctionOptions) 21 | : Promise { 22 | 23 | await super._initialize(options); 24 | 25 | if (!options.priceOracleContract) { 26 | throw new Error(`priceOracleContract not supplied`); 27 | } 28 | 29 | this.logContractFunctionCall("LockingToken4Reputation.initialize", options); 30 | 31 | return this.wrapTransactionInvocation("LockingToken4Reputation.initialize", 32 | options, 33 | this.contract.initialize, 34 | [options.avatarAddress, 35 | options.reputationReward, 36 | options.lockingStartTime.getTime() / 1000, 37 | options.lockingEndTime.getTime() / 1000, 38 | options.redeemEnableTime.getTime() / 1000, 39 | options.maxLockingPeriod, 40 | options.priceOracleContract] 41 | ); 42 | } 43 | 44 | public async release(options: ReleaseOptions & TxGeneratingFunctionOptions): Promise { 45 | 46 | await super._release(options); 47 | 48 | this.logContractFunctionCall("LockingToken4Reputation.release", options); 49 | 50 | return this.wrapTransactionInvocation("LockingToken4Reputation.release", 51 | options, 52 | this.contract.release, 53 | [options.lockerAddress, options.lockId] 54 | ); 55 | } 56 | 57 | /** 58 | * Returns reason why can't lock, else null if can lock 59 | */ 60 | public async getLockBlocker(options: TokenLockingOptions): Promise { 61 | 62 | const msg = await super.getLockBlocker(options); 63 | if (msg) { 64 | return msg; 65 | } 66 | 67 | if (!options.tokenAddress) { 68 | return "tokenAddress was not supplied"; 69 | } 70 | 71 | const token = await StandardTokenFactory.at(options.tokenAddress); 72 | const balance = await Utils.getTokenBalance(options.lockerAddress, token.address); 73 | const amount = new BigNumber(options.amount); 74 | 75 | if (balance.lt(amount)) { 76 | return "the account has insufficient balance"; 77 | } 78 | 79 | return null; 80 | } 81 | 82 | public async lock(options: TokenLockingOptions & TxGeneratingFunctionOptions): Promise { 83 | 84 | const msg = await this.getLockBlocker(options); 85 | if (msg) { 86 | throw new Error(msg); 87 | } 88 | 89 | this.logContractFunctionCall("LockingToken4Reputation.lock", options); 90 | 91 | return this.wrapTransactionInvocation("LockingToken4Reputation.lock", 92 | options, 93 | this.contract.lock, 94 | [options.amount, options.period, options.tokenAddress], 95 | { from: options.lockerAddress } 96 | ); 97 | } 98 | 99 | public async getTokenForLock(lockingId: Hash): Promise { 100 | this.logContractFunctionCall("LockingToken4Reputation.lockedTokens"); 101 | const address = await this.contract.lockedTokens(lockingId); 102 | return WrapperService.factories.StandardToken.at(address); 103 | } 104 | } 105 | 106 | export class LockingToken4ReputationType extends ContractWrapperFactory { 107 | 108 | public async deployed(): Promise { 109 | throw new Error("LockingToken4Reputation has not been deployed"); 110 | } 111 | } 112 | 113 | export const LockingToken4ReputationFactory = 114 | new LockingToken4ReputationType( 115 | "LockingToken4Reputation", 116 | LockingToken4ReputationWrapper, 117 | new Web3EventService()) as LockingToken4ReputationType; 118 | 119 | export interface LockTokenInitializeOptions extends InitializeOptions { 120 | priceOracleContract: Address; 121 | } 122 | 123 | export interface LockingToken4ReputationLockEventResult { 124 | /** 125 | * indexed 126 | */ 127 | _lockingId: BigNumber; 128 | /** 129 | * indexed 130 | */ 131 | _token: Address; 132 | /** 133 | * number/denominator is the price of the token at the time the token is locked 134 | */ 135 | _numerator: BigNumber; 136 | /** 137 | * number/denominator is the price of the token at the time the token is locked 138 | */ 139 | _denominator: BigNumber; 140 | } 141 | 142 | export interface TokenLockingOptions extends LockingOptions { 143 | tokenAddress: Address; 144 | } 145 | -------------------------------------------------------------------------------- /lib/wrappers/mintableToken.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import { BigNumber } from "bignumber.js"; 3 | import { Address } from "../commonTypes"; 4 | import { ContractWrapperFactory } from "../contractWrapperFactory"; 5 | import { ArcTransactionResult, IContractWrapperFactory } from "../iContractWrapperBase"; 6 | import { LoggingService } from "../loggingService"; 7 | import { TransactionService, TxGeneratingFunctionOptions } from "../transactionService"; 8 | import { EventFetcherFactory, Web3EventService } from "../web3EventService"; 9 | import { 10 | StandardTokenApproveOptions, 11 | StandardTokenChangeApprovalOptions, 12 | StandardTokenTransferFromOptions, 13 | StandardTokenTransferOptions, 14 | StandardTokenWrapper 15 | } from "./standardToken"; 16 | 17 | export class MintableTokenWrapper extends StandardTokenWrapper { 18 | public name: string = "MintableToken"; 19 | public friendlyName: string = "Mintable Token"; 20 | public factory: IContractWrapperFactory = MintableTokenFactory; 21 | 22 | public Mint: EventFetcherFactory; 23 | public MintFinished: EventFetcherFactory; 24 | 25 | /** 26 | * Mint tokens to recipient 27 | * @param options 28 | */ 29 | public async mint(options: MintableTokenMintOptions & TxGeneratingFunctionOptions) 30 | : Promise { 31 | 32 | if (!options.recipient) { 33 | throw new Error("recipient is not defined"); 34 | } 35 | 36 | const amount = new BigNumber(options.amount); 37 | 38 | if (amount.eq(0)) { 39 | LoggingService.warn("MintableToken.mint: amount is zero. Doing nothing."); 40 | return new ArcTransactionResult(null, this.contract); 41 | } 42 | 43 | this.logContractFunctionCall("MintableToken.mint", options); 44 | 45 | return this.wrapTransactionInvocation("MintableToken.mint", 46 | options, 47 | this.contract.mint, 48 | [options.recipient, options.amount] 49 | ); 50 | } 51 | 52 | /** 53 | * Terminate the ability to mint tokens 54 | * @param options 55 | */ 56 | public async finishMinting(options?: TxGeneratingFunctionOptions) 57 | : Promise { 58 | 59 | this.logContractFunctionCall("MintableToken.finishMinting", options); 60 | 61 | return this.wrapTransactionInvocation("MintableToken.finishMinting", 62 | options, 63 | this.contract.finishMinting, 64 | [] 65 | ); 66 | } 67 | 68 | public async approve(options: StandardTokenApproveOptions & TxGeneratingFunctionOptions) 69 | : Promise { 70 | const functionName = "MintableToken.approve"; 71 | const payload = TransactionService.publishKickoffEvent(functionName, options, 1); 72 | const eventContext = TransactionService.newTxEventContext(functionName, payload, options); 73 | return super.approve(Object.assign(options, { txEventContext: eventContext })); 74 | } 75 | 76 | public async transfer(options: StandardTokenTransferOptions & TxGeneratingFunctionOptions) 77 | : Promise { 78 | const functionName = "MintableToken.transfer"; 79 | const payload = TransactionService.publishKickoffEvent(functionName, options, 1); 80 | const eventContext = TransactionService.newTxEventContext(functionName, payload, options); 81 | return super.transfer(Object.assign(options, { txEventContext: eventContext })); 82 | } 83 | 84 | public async transferFrom(options: StandardTokenTransferFromOptions & TxGeneratingFunctionOptions) 85 | : Promise { 86 | const functionName = "MintableToken.transferFrom"; 87 | const payload = TransactionService.publishKickoffEvent(functionName, options, 1); 88 | const eventContext = TransactionService.newTxEventContext(functionName, payload, options); 89 | return super.transferFrom(Object.assign(options, { txEventContext: eventContext })); 90 | } 91 | 92 | public async increaseApproval(options: StandardTokenChangeApprovalOptions & TxGeneratingFunctionOptions) 93 | : Promise { 94 | const functionName = "MintableToken.increaseApproval"; 95 | const payload = TransactionService.publishKickoffEvent(functionName, options, 1); 96 | const eventContext = TransactionService.newTxEventContext(functionName, payload, options); 97 | return super.increaseApproval(Object.assign(options, { txEventContext: eventContext })); 98 | } 99 | 100 | public async decreaseApproval(options: StandardTokenChangeApprovalOptions & TxGeneratingFunctionOptions) 101 | : Promise { 102 | const functionName = "MintableToken.decreaseApproval"; 103 | const payload = TransactionService.publishKickoffEvent(functionName, options, 1); 104 | const eventContext = TransactionService.newTxEventContext(functionName, payload, options); 105 | return super.decreaseApproval(Object.assign(options, { txEventContext: eventContext })); 106 | } 107 | 108 | protected hydrated(): void { 109 | super.hydrated(); 110 | /* tslint:disable:max-line-length */ 111 | this.Mint = this.createEventFetcherFactory(this.contract.Mint); 112 | this.MintFinished = this.createEventFetcherFactory(this.contract.MintFinished); 113 | /* tslint:enable:max-line-length */ 114 | } 115 | } 116 | 117 | /** 118 | * defined just to add good type checking 119 | */ 120 | export class MintableTokenFactoryType extends ContractWrapperFactory { 121 | 122 | public async deployed(): Promise { 123 | throw new Error("MintableToken has not been deployed"); 124 | } 125 | } 126 | 127 | export const MintableTokenFactory = 128 | new MintableTokenFactoryType( 129 | "MintableToken", 130 | MintableTokenWrapper, 131 | new Web3EventService()) as MintableTokenFactoryType; 132 | 133 | export interface MintableTokenMintOptions { 134 | /** 135 | * The token recipient 136 | */ 137 | recipient: Address; 138 | /** 139 | * Amount to mint 140 | */ 141 | amount: BigNumber | string; 142 | } 143 | 144 | export interface MintEventResult { 145 | /** 146 | * The token recipient 147 | * indexed 148 | */ 149 | to: Address; 150 | /** 151 | * Amount minted 152 | */ 153 | amount: BigNumber; 154 | } 155 | 156 | /* tslint:disable-next-line:no-empty-interface */ 157 | export interface MintFinishedEventResult { 158 | } 159 | -------------------------------------------------------------------------------- /lib/wrappers/redeemer.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import { BigNumber } from "bignumber.js"; 3 | import { Address, BinaryVoteResult, Hash } from "../commonTypes"; 4 | import { ContractWrapperBase } from "../contractWrapperBase"; 5 | import { ContractWrapperFactory } from "../contractWrapperFactory"; 6 | import { ArcTransactionResult, IContractWrapperFactory } from "../iContractWrapperBase"; 7 | import { TxGeneratingFunctionOptions } from "../transactionService"; 8 | import { Web3EventService } from "../web3EventService"; 9 | 10 | export class RedeemerWrapper extends ContractWrapperBase { 11 | public name: string = "Redeemer"; 12 | public friendlyName: string = "Redeemer"; 13 | public factory: IContractWrapperFactory = RedeemerFactory; 14 | 15 | /** 16 | * Redeems rewards for a ContributionReward proposal in a single transaction. 17 | * Calls execute on the proposal if it is not yet executed. 18 | * Redeems rewardable reputation and stake from the GenesisProtocol. 19 | * Redeem rewardable contribution proposal rewards. 20 | * @param options 21 | */ 22 | public async redeem(options: RedeemerOptions & TxGeneratingFunctionOptions) 23 | : Promise { 24 | 25 | if (!options.avatarAddress) { 26 | throw new Error("avatarAddress is not defined"); 27 | } 28 | 29 | if (!options.beneficiaryAddress) { 30 | throw new Error("beneficiaryAddress is not defined"); 31 | } 32 | 33 | if (!options.proposalId) { 34 | throw new Error("proposalId is not defined"); 35 | } 36 | 37 | this.logContractFunctionCall("Redeemer.redeem", options); 38 | 39 | return this.wrapTransactionInvocation("Redeemer.redeem", 40 | options, 41 | this.contract.redeem, 42 | [options.proposalId, options.avatarAddress, options.beneficiaryAddress] 43 | ); 44 | } 45 | 46 | /** 47 | * Returns the amounts that would be redeemed if `Redeemer.redeem` were invoked right now. 48 | * @param options 49 | */ 50 | public async redeemables(options: RedeemerOptions) 51 | : Promise { 52 | 53 | if (!options.avatarAddress) { 54 | throw new Error("avatarAddress is not defined"); 55 | } 56 | 57 | if (!options.beneficiaryAddress) { 58 | throw new Error("beneficiaryAddress is not defined"); 59 | } 60 | 61 | if (!options.proposalId) { 62 | throw new Error("proposalId is not defined"); 63 | } 64 | 65 | this.logContractFunctionCall("Redeemer.redeem.call", options); 66 | 67 | const result = await this.contract.redeem.call( 68 | options.proposalId, 69 | options.avatarAddress, 70 | options.beneficiaryAddress) 71 | // correct for fake truffle promises 72 | .then((r: any): any => r) 73 | .catch((ex: Error) => { 74 | throw new Error(ex.message); 75 | }); 76 | 77 | return { 78 | contributionRewardEther: result[6], 79 | contributionRewardExternalToken: result[7], 80 | contributionRewardNativeToken: result[5], 81 | contributionRewardReputation: result[4], 82 | daoStakingBountyPotentialReward: result[1][1], 83 | daoStakingBountyReward: result[1][0], 84 | proposalExecuted: result[2], 85 | proposalId: options.proposalId, 86 | proposerReputationAmount: result[0][4], 87 | stakerReputationAmount: result[0][1], 88 | stakerTokenAmount: result[0][0], 89 | voterReputationAmount: result[0][3], 90 | voterTokenAmount: result[0][2], 91 | winningVote: result[3].toNumber(), 92 | }; 93 | } 94 | } 95 | 96 | /** 97 | * defined just to add good type checking 98 | */ 99 | export class RedeemerFactoryType extends ContractWrapperFactory { 100 | 101 | public async new( 102 | contributionRewardAddress: Address, 103 | genesisProtocolAddress: Address): Promise { 104 | return super.new(contributionRewardAddress, genesisProtocolAddress); 105 | } 106 | } 107 | 108 | export const RedeemerFactory = 109 | new RedeemerFactoryType( 110 | "Redeemer", 111 | RedeemerWrapper, 112 | new Web3EventService()) as RedeemerFactoryType; 113 | 114 | export interface RedeeemableResult { 115 | contributionRewardEther: BigNumber; 116 | contributionRewardExternalToken: BigNumber; 117 | contributionRewardNativeToken: BigNumber; 118 | contributionRewardReputation: BigNumber; 119 | daoStakingBountyReward: BigNumber; 120 | daoStakingBountyPotentialReward: BigNumber; 121 | proposalExecuted: boolean; 122 | proposalId: Hash; 123 | proposerReputationAmount: BigNumber; 124 | stakerReputationAmount: BigNumber; 125 | stakerTokenAmount: BigNumber; 126 | voterReputationAmount: BigNumber; 127 | voterTokenAmount: BigNumber; 128 | winningVote: BinaryVoteResult; 129 | } 130 | 131 | export interface RedeemerOptions { 132 | avatarAddress: Address; 133 | beneficiaryAddress: Address; 134 | proposalId: Hash; 135 | } 136 | -------------------------------------------------------------------------------- /lib/wrappers/reputation.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import { Address } from "../commonTypes"; 3 | import { ArcTransactionResult, IContractWrapperFactory } from "../iContractWrapperBase"; 4 | 5 | import { BigNumber } from "bignumber.js"; 6 | import { ContractWrapperBase } from "../contractWrapperBase"; 7 | import { ContractWrapperFactory } from "../contractWrapperFactory"; 8 | import { LoggingService } from "../loggingService"; 9 | import { TxGeneratingFunctionOptions } from "../transactionService"; 10 | import { EventFetcherFactory, Web3EventService } from "../web3EventService"; 11 | 12 | export class ReputationWrapper extends ContractWrapperBase { 13 | public name: string = "Reputation"; 14 | public friendlyName: string = "Reputation"; 15 | public factory: IContractWrapperFactory = ReputationFactory; 16 | 17 | public Mint: EventFetcherFactory; 18 | public Burn: EventFetcherFactory; 19 | 20 | /** 21 | * Mint reputation to the given recipient 22 | * @param options 23 | */ 24 | public async mint(options: ReputationMintOptions & TxGeneratingFunctionOptions) 25 | : Promise { 26 | 27 | if (!options.recipient) { 28 | throw new Error("recipient is not defined"); 29 | } 30 | 31 | const amount = new BigNumber(options.amount); 32 | 33 | if (amount.eq(0)) { 34 | LoggingService.warn("Reputation.mint: amount is zero. Doing nothing."); 35 | return new ArcTransactionResult(null, this.contract); 36 | } 37 | 38 | this.logContractFunctionCall("Reputation.mint", options); 39 | 40 | return this.wrapTransactionInvocation("Reputation.mint", 41 | options, 42 | this.contract.mint, 43 | [options.recipient, options.amount] 44 | ); 45 | } 46 | 47 | /** 48 | * Remove reputation from the given account. 49 | * @param options 50 | */ 51 | public async burn(options: ReputationBurnOptions & TxGeneratingFunctionOptions) 52 | : Promise { 53 | 54 | if (!options.from) { 55 | throw new Error("'from' is not defined"); 56 | } 57 | 58 | const amount = new BigNumber(options.amount); 59 | 60 | if (amount.eq(0)) { 61 | LoggingService.warn("Reputation.burn: amount is zero. Doing nothing."); 62 | return new ArcTransactionResult(null, this.contract); 63 | } 64 | 65 | this.logContractFunctionCall("Reputation.burn", options); 66 | 67 | return this.wrapTransactionInvocation("Reputation.burn", 68 | options, 69 | this.contract.burn, 70 | [options.from, options.amount] 71 | ); 72 | } 73 | 74 | public getTotalSupply(): Promise { 75 | this.logContractFunctionCall("Reputation.totalSupply"); 76 | return this.contract.totalSupply(); 77 | } 78 | 79 | /** 80 | * Total amount of reputation at the given `blockNumber`. 81 | */ 82 | public getTotalSupplyAt(blockNumber: number): Promise { 83 | this.logContractFunctionCall("Reputation.totalSupplyAt"); 84 | return this.contract.totalSupply(); 85 | } 86 | 87 | public getBalanceOf(accountAddress: Address): Promise { 88 | 89 | if (!accountAddress) { 90 | throw new Error("accountAddress is not defined"); 91 | } 92 | 93 | this.logContractFunctionCall("Reputation.balanceOf", accountAddress); 94 | 95 | return this.contract.balanceOf(accountAddress); 96 | } 97 | 98 | /** 99 | * Queries the balance of `accountAddress` at the given `blockNumber` 100 | * @param accountAddress 101 | * @param blockNumber 102 | */ 103 | public getBalanceOfAt(accountAddress: Address, blockNumber: number): Promise { 104 | 105 | if (!accountAddress) { 106 | throw new Error("accountAddress is not defined"); 107 | } 108 | 109 | if (typeof (blockNumber) === "undefined") { 110 | throw new Error("blockNumber is not defined"); 111 | } 112 | 113 | this.logContractFunctionCall("Reputation.balanceOfAt", { accountAddress, blockNumber }); 114 | 115 | return this.contract.balanceOfAt(accountAddress, blockNumber); 116 | } 117 | 118 | protected hydrated(): void { 119 | /* tslint:disable:max-line-length */ 120 | this.Mint = this.createEventFetcherFactory(this.contract.Mint); 121 | this.Burn = this.createEventFetcherFactory(this.contract.Burn); 122 | /* tslint:enable:max-line-length */ 123 | } 124 | } 125 | 126 | export class ReputationFactoryType extends ContractWrapperFactory { 127 | 128 | public async deployed(): Promise { 129 | throw new Error("Reputation has not been deployed"); 130 | } 131 | } 132 | 133 | export const ReputationFactory = 134 | new ReputationFactoryType( 135 | "Reputation", 136 | ReputationWrapper, 137 | new Web3EventService()) as ReputationFactoryType; 138 | 139 | export interface ReputationMintOptions { 140 | /** 141 | * The token recipient 142 | */ 143 | recipient: Address; 144 | /** 145 | * Amount to mint 146 | */ 147 | amount: BigNumber; 148 | } 149 | 150 | export interface ReputationBurnOptions { 151 | from: BigNumber; 152 | /** 153 | * Amount to mint 154 | */ 155 | amount: BigNumber; 156 | } 157 | 158 | export interface ReputationMintEventResult { 159 | /** 160 | * The recipient of reputation 161 | * indexed 162 | */ 163 | _to: Address; 164 | /** 165 | * Amount minted 166 | */ 167 | _amount: BigNumber; 168 | } 169 | 170 | export interface ReputationBurnEventResult { 171 | /** 172 | * Whose reputation was burnt 173 | * indexed 174 | */ 175 | _from: Address; 176 | /** 177 | * Amount burnt 178 | */ 179 | _amount: BigNumber; 180 | } 181 | -------------------------------------------------------------------------------- /lib/wrappers/tokenCapGC.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import { Address, Hash } from "../commonTypes"; 3 | import { ContractWrapperBase } from "../contractWrapperBase"; 4 | import { ArcTransactionDataResult, IContractWrapperFactory } from "../iContractWrapperBase"; 5 | 6 | import BigNumber from "bignumber.js"; 7 | import { ContractWrapperFactory } from "../contractWrapperFactory"; 8 | import { ControllerService } from "../controllerService"; 9 | import { TxGeneratingFunctionOptions } from "../transactionService"; 10 | import { Web3EventService } from "../web3EventService"; 11 | 12 | export class TokenCapGCWrapper extends ContractWrapperBase { 13 | public name: string = "TokenCapGC"; 14 | public friendlyName: string = "Token Cap Global Constraint"; 15 | public factory: IContractWrapperFactory = TokenCapGCFactory; 16 | 17 | public getParametersHash(params: TokenCapGcParams): Promise { 18 | return this._getParametersHash( 19 | params.token, 20 | params.cap || 0 21 | ); 22 | } 23 | public setParameters( 24 | params: TokenCapGcParams & TxGeneratingFunctionOptions): Promise> { 25 | 26 | if (!params.token) { 27 | throw new Error("token must be set"); 28 | } 29 | const cap = new BigNumber(params.cap); 30 | 31 | if (cap.lt(0)) { 32 | throw new Error("cap must be greater than or equal to zero"); 33 | } 34 | 35 | return super._setParameters( 36 | "TokenCapGC.setParameters", 37 | params.txEventContext, 38 | params.token, 39 | cap); 40 | } 41 | 42 | public async getParameters(paramsHash: Hash): Promise { 43 | const params = await this.getParametersArray(paramsHash); 44 | return { 45 | cap: params[1], 46 | token: params[0], 47 | }; 48 | } 49 | 50 | public async getRegisteredParametersHash(avatarAddress: Address): Promise { 51 | const controllerService = new ControllerService(avatarAddress); 52 | const controller = await controllerService.getController(); 53 | return controller.getGlobalConstraintParameters(this.address, avatarAddress); 54 | } 55 | 56 | public async getRegisteredParameters(avatarAddress: Address): Promise { 57 | const paramsHash = await this.getRegisteredParametersHash(avatarAddress); 58 | return this.getParameters(paramsHash); 59 | } 60 | } 61 | 62 | export const TokenCapGCFactory = 63 | new ContractWrapperFactory("TokenCapGC", TokenCapGCWrapper, new Web3EventService()); 64 | 65 | export interface TokenCapGcParams { 66 | cap: BigNumber | string; 67 | token: Address; 68 | } 69 | 70 | export interface GetTokenCapGcParamsResult { 71 | cap: BigNumber; 72 | token: Address; 73 | } 74 | -------------------------------------------------------------------------------- /lib/wrappers/voteInOrganizationScheme.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import { BigNumber } from "bignumber.js"; 3 | import { Address, DefaultSchemePermissions, Hash, SchemePermissions } from "../commonTypes"; 4 | import { ContractWrapperFactory } from "../contractWrapperFactory"; 5 | import { 6 | ArcTransactionDataResult, 7 | ArcTransactionProposalResult, 8 | DecodedLogEntryEvent, 9 | IContractWrapperFactory, 10 | IUniversalSchemeWrapper, 11 | StandardSchemeParams, 12 | } from "../iContractWrapperBase"; 13 | import { ProposalGeneratorBase } from "../proposalGeneratorBase"; 14 | import { TxGeneratingFunctionOptions } from "../transactionService"; 15 | import { EntityFetcherFactory, EventFetcherFactory, Web3EventService } from "../web3EventService"; 16 | import { 17 | ProposalDeletedEventResult, 18 | SchemeProposalExecuted, 19 | SchemeProposalExecutedEventResult 20 | } from "./commonEventInterfaces"; 21 | 22 | export class VoteInOrganizationSchemeWrapper extends ProposalGeneratorBase { 23 | 24 | public name: string = "VoteInOrganizationScheme"; 25 | public friendlyName: string = "Vote In Organization Scheme"; 26 | public factory: IContractWrapperFactory = VoteInOrganizationSchemeFactory; 27 | /** 28 | * Events 29 | */ 30 | 31 | public NewVoteProposal: EventFetcherFactory; 32 | public ProposalExecuted: EventFetcherFactory; 33 | public ProposalDeleted: EventFetcherFactory; 34 | public VoteOnBehalf: EventFetcherFactory; 35 | 36 | /** 37 | * Submit a proposal to vote on a proposal in another DAO. 38 | * @param options 39 | */ 40 | public async proposeVoteInOrganization( 41 | options: VoteInOrganizationProposeVoteConfig = 42 | {} as VoteInOrganizationProposeVoteConfig & TxGeneratingFunctionOptions) 43 | : Promise { 44 | 45 | if (!options.avatar) { 46 | throw new Error("avatar is not defined"); 47 | } 48 | 49 | if (!options.originalVotingMachineAddress) { 50 | throw new Error("originalVotingMachineAddress is not defined"); 51 | } 52 | 53 | if (!options.originalProposalId) { 54 | throw new Error("originalProposalId is not defined"); 55 | } 56 | 57 | this.logContractFunctionCall("VoteInOrganizationScheme.proposeVote", options); 58 | 59 | const txResult = await this.wrapTransactionInvocation("VoteInOrganizationScheme.proposeVote", 60 | options, 61 | this.contract.proposeVote, 62 | [options.avatar, 63 | options.originalVotingMachineAddress, 64 | options.originalProposalId] 65 | ); 66 | 67 | return new ArcTransactionProposalResult(txResult.tx, this.contract, await this.getVotingMachine(options.avatar)); 68 | } 69 | 70 | /** 71 | * EntityFetcherFactory for votable VoteInOrganizationProposal. 72 | * @param avatarAddress 73 | */ 74 | public async getVotableProposals(avatarAddress: Address): 75 | Promise> { 76 | 77 | return this.proposalService.getProposalEvents( 78 | { 79 | baseArgFilter: { _avatar: avatarAddress }, 80 | proposalsEventFetcher: this.NewVoteProposal, 81 | transformEventCallback: 82 | async (event: DecodedLogEntryEvent) 83 | : Promise => { 84 | return this.getVotableProposal(event.args._avatar, event.args._proposalId); 85 | }, 86 | votableOnly: true, 87 | votingMachine: await this.getVotingMachine(avatarAddress), 88 | }); 89 | } 90 | 91 | /** 92 | * EntityFetcherFactory for executed proposals. 93 | * @param avatarAddress 94 | */ 95 | public getExecutedProposals(avatarAddress: Address): 96 | EntityFetcherFactory { 97 | 98 | return this.proposalService.getProposalEvents( 99 | { 100 | baseArgFilter: { _avatar: avatarAddress }, 101 | proposalsEventFetcher: this.ProposalExecuted, 102 | transformEventCallback: 103 | (event: DecodedLogEntryEvent): Promise => { 104 | return Promise.resolve({ 105 | avatarAddress: event.args._avatar, 106 | proposalId: event.args._proposalId, 107 | winningVote: event.args._param, 108 | }); 109 | }, 110 | }); 111 | } 112 | 113 | public async getVotableProposal( 114 | avatarAddress: Address, 115 | proposalId: Hash): Promise { 116 | 117 | const proposalParams = await this.contract.organizationsProposals(avatarAddress, proposalId); 118 | return this.convertProposalPropsArrayToObject(proposalParams, proposalId); 119 | } 120 | 121 | public getParametersHash(params: StandardSchemeParams): Promise { 122 | return this._getParametersHash( 123 | params.voteParametersHash, 124 | params.votingMachineAddress 125 | ); 126 | } 127 | 128 | public setParameters( 129 | params: StandardSchemeParams & TxGeneratingFunctionOptions) 130 | : Promise> { 131 | 132 | this.validateStandardSchemeParams(params); 133 | 134 | return super._setParameters( 135 | "VoteInOrganizationScheme.setParameters", 136 | params.txEventContext, 137 | params.voteParametersHash, 138 | params.votingMachineAddress 139 | ); 140 | } 141 | 142 | public getDefaultPermissions(): SchemePermissions { 143 | return DefaultSchemePermissions.VoteInOrganizationScheme as number; 144 | } 145 | 146 | public async getSchemePermissions(avatarAddress: Address): Promise { 147 | return this._getSchemePermissions(avatarAddress); 148 | } 149 | 150 | public async getSchemeParameters(avatarAddress: Address): Promise { 151 | return this._getSchemeParameters(avatarAddress); 152 | } 153 | 154 | public async getParameters(paramsHash: Hash): Promise { 155 | const params = await this.getParametersArray(paramsHash); 156 | return { 157 | voteParametersHash: params[1], 158 | votingMachineAddress: params[0], 159 | }; 160 | } 161 | 162 | protected hydrated(): void { 163 | /* tslint:disable:max-line-length */ 164 | this.NewVoteProposal = this.createEventFetcherFactory(this.contract.NewVoteProposal); 165 | this.ProposalExecuted = this.createEventFetcherFactory(this.contract.ProposalExecuted); 166 | this.ProposalDeleted = this.createEventFetcherFactory(this.contract.ProposalDeleted); 167 | this.VoteOnBehalf = this.createEventFetcherFactory(this.contract.VoteOnBehalf); 168 | /* tslint:enable:max-line-length */ 169 | } 170 | 171 | private convertProposalPropsArrayToObject( 172 | propsArray: Array, 173 | proposalId: Hash): VotableVoteInOrganizationProposal { 174 | return { 175 | originalNumOfChoices: propsArray[2].toNumber(), 176 | originalProposalId: propsArray[1], 177 | originalVotingMachineAddress: propsArray[0], 178 | proposalId, 179 | }; 180 | } 181 | } 182 | 183 | export const VoteInOrganizationSchemeFactory = 184 | new ContractWrapperFactory( 185 | "VoteInOrganizationScheme", 186 | VoteInOrganizationSchemeWrapper, 187 | new Web3EventService()); 188 | 189 | export interface VoteOnBehalfEventResult { 190 | _params: Array; 191 | } 192 | 193 | export interface VoteInOrganizationProposeVoteConfig { 194 | /** 195 | * Avatar whose voters are being given the chance to vote on the original proposal. 196 | */ 197 | avatar: Address; 198 | /** 199 | * Address of the voting machine used by the original proposal. The voting machine must 200 | * implement IntVoteInterface (as defined in Arc). 201 | */ 202 | originalVotingMachineAddress: Address; 203 | /** 204 | * Address of the "original" proposal for which the DAO's vote will cast. 205 | */ 206 | originalProposalId: string; 207 | } 208 | 209 | export interface VotableVoteInOrganizationProposal { 210 | originalVotingMachineAddress: Address; 211 | originalNumOfChoices: number; 212 | originalProposalId: Hash; 213 | proposalId: Hash; 214 | } 215 | 216 | export interface NewVoteProposalEventResult { 217 | /** 218 | * indexed 219 | */ 220 | _avatar: Address; 221 | /** 222 | * indexed 223 | */ 224 | _intVoteInterface: Address; 225 | _originalIntVote: Address; 226 | _originalProposalId: Hash; 227 | _originalNumOfChoices: BigNumber; 228 | /** 229 | * indexed 230 | */ 231 | _proposalId: Hash; 232 | } 233 | -------------------------------------------------------------------------------- /package-scripts.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable quotes */ 2 | const fs = require("fs"); 3 | const { 4 | series, 5 | rimraf, 6 | copy, 7 | mkdirp 8 | } = require("nps-utils"); 9 | const joinPath = require("path.join"); 10 | const cwd = require("cwd")(); 11 | 12 | const runningInRepo = fs.existsSync(".git"); 13 | const migrationsExist = fs.existsSync("migration.json"); 14 | const pathArcJsRoot = cwd; 15 | 16 | const pathNodeModules = runningInRepo ? joinPath(".", "node_modules") : joinPath("..", "..", "node_modules"); 17 | 18 | const pathDaostackArcRepo = runningInRepo ? 19 | joinPath(pathNodeModules, "@daostack", "arc") : 20 | joinPath("..", "arc"); 21 | 22 | const pathDaostackMigrationsRepo = runningInRepo ? 23 | joinPath(pathNodeModules, "@daostack", "migration") : 24 | joinPath("..", "migration"); 25 | 26 | const pathArcJsContracts = joinPath(".", "migrated_contracts"); 27 | const pathArcTest = joinPath(".", "test"); 28 | const pathArcTestBuild = joinPath(".", "test-build"); 29 | const pathArcDist = joinPath(".", "dist"); 30 | const pathDaostackArcGanacheDb = joinPath(".", "ganacheDb"); 31 | const pathDaostackArcGanacheDbZip = joinPath(".", "ganacheDb.zip"); 32 | const pathTypeScript = joinPath(pathNodeModules, "typescript/bin/tsc"); 33 | 34 | const ganacheGasLimit = 8000000; // something reasonably close to live 35 | const ganacheCommand = `ganache-cli -l ${ganacheGasLimit} --networkId 1512051714758 --defaultBalanceEther 999999999999999 --deterministic`; 36 | const ganacheDbCommand = `ganache-cli --db ${pathDaostackArcGanacheDb} --networkId 1512051714758 -l ${ganacheGasLimit} --defaultBalanceEther 999999999999999 --deterministic`; 37 | 38 | module.exports = { 39 | scripts: { 40 | ganache: { 41 | default: "nps ganache.run", 42 | run: ganacheCommand 43 | }, 44 | ganacheDb: { 45 | default: "nps ganacheDb.run", 46 | run: series( 47 | mkdirp(pathDaostackArcGanacheDb), 48 | ganacheDbCommand, 49 | ), 50 | clean: rimraf(pathDaostackArcGanacheDb), 51 | zip: `node ./package-scripts/archiveGanacheDb.js ${pathDaostackArcGanacheDbZip} ${pathDaostackArcGanacheDb}`, 52 | unzip: series( 53 | `node ./package-scripts/unArchiveGanacheDb.js ${pathDaostackArcGanacheDbZip} ${pathArcJsRoot}` 54 | ), 55 | restoreFromZip: series( 56 | "nps ganacheDb.clean", 57 | "nps ganacheDb.unzip" 58 | ) 59 | }, 60 | lint: { 61 | default: series( 62 | "nps lint.code", 63 | "nps lint.test" 64 | ), 65 | code: { 66 | default: `tslint ${joinPath("custom_typings", "web3.d.ts")} ${joinPath("custom_typings", "system.d.ts")} ${joinPath("lib", "**", "*.ts")}`, 67 | andFix: `nps "lint.code --fix"` 68 | }, 69 | test: { 70 | default: `tslint ${joinPath("custom_typings", "web3_global.d.ts")} ${joinPath("custom_typings", "system.d.ts")} ${joinPath("test", "**", "*.ts")}`, 71 | andFix: `nps "lint.test --fix"` 72 | }, 73 | andFix: series( 74 | "nps lint.code.andFix", 75 | "nps lint.test.andFix" 76 | ), 77 | }, 78 | test: { 79 | default: series( 80 | `nps test.build`, 81 | `nps test.runAll` 82 | ), 83 | bail: series( 84 | `nps test.build`, 85 | `nps "test.runAll --bail"` 86 | ), 87 | // coming: the ability to more easily run a single test (awaiting a forthcoming release of nps). 88 | run: `mocha --require chai --timeout 999999`, 89 | runAll: `mocha --require chai --timeout 999999 ${joinPath(pathArcTestBuild, "test")}`, 90 | build: { 91 | default: series( 92 | "nps test.build.clean", 93 | mkdirp(joinPath(pathArcTestBuild, "config")), 94 | copy(`${joinPath(".", "config", "**", "*")} ${joinPath(pathArcTestBuild, "config")}`), 95 | copy(`${joinPath(pathArcJsContracts, "**", "*")} ${joinPath(pathArcTestBuild, "migrated_contracts")}`), 96 | copy(`${joinPath(pathArcJsRoot, "migration.json")} ${pathArcTestBuild}`), 97 | mkdirp(pathArcTestBuild), 98 | `node ${pathTypeScript} --outDir ${pathArcTestBuild} --project ${pathArcTest}` 99 | ), 100 | clean: rimraf(joinPath(pathArcTestBuild, "*")) 101 | }, 102 | }, 103 | build: { 104 | default: series( 105 | "nps build.clean", 106 | mkdirp(pathArcDist), 107 | `node ${pathTypeScript} --outDir ${pathArcDist}` 108 | ), 109 | clean: rimraf(pathArcDist) 110 | }, 111 | deploy: { 112 | ensureMigrations: migrationsExist ? "" : `node ${joinPath(".", "package-scripts", "fail")} "migrations.json doesn't exist"`, 113 | pack: series( 114 | "nps deploy.ensureMigrations", 115 | "nps build", 116 | "npm pack"), 117 | publish: series( 118 | "nps deploy.ensureMigrations", 119 | "nps build", 120 | "npm publish") 121 | }, 122 | createGenesisDao: { 123 | default: `node ${joinPath(".", "package-scripts", "createGenesisDao.js")}` 124 | }, 125 | /** 126 | * See README.md for how to use these scripts in a workflow to migrate contracts 127 | */ 128 | migrateContracts: { 129 | /** 130 | * Migrate contracts. 131 | * 132 | * Truffle will merge this migration with whatever previous ones are already present in the contract json files. 133 | * 134 | * Run migrateContracts.fetchContracts first if you want to start with fresh unmigrated contracts from @daostack/arc. 135 | * 136 | * use --reset for ganacheDb if it is crashing on re-migration. 137 | */ 138 | default: series( 139 | `node ${joinPath(".", "package-scripts", "migrateContracts.js")} "${joinPath(pathArcJsRoot, "migration.json")}"` 140 | ), 141 | andCreateGenesisDao: series( 142 | `nps migrateContracts`, 143 | `nps createGenesisDao` 144 | ), 145 | /** 146 | * Clean the output contract json files, optionally andMigrate. 147 | * 148 | * IMPORTANT! Only do this if you aren't worried about losing 149 | * previously-performed migrations to other networks. By cleaning, you'll lose them, starting 150 | * from scratch. Otherwise, truffle will merge your migrations into whatever previous 151 | * ones exist. 152 | */ 153 | clean: rimraf(joinPath(pathArcJsContracts, "*")), 154 | 155 | fetchContracts: series( 156 | "nps migrateContracts.clean", 157 | "nps migrateContracts.fetchFromArc", 158 | "nps migrateContracts.fetchFromDaostack" 159 | ), 160 | /** 161 | * Fetch the unmigrated contract json files from DAOstack Arc. 162 | * Run this ONLY when you want to start with fresh UNMIGRATED contracts from DAOstack Arc. 163 | * Best to run "migrateContracts.clean" first. 164 | * If run from the context of an application, then the application must have installed 165 | * the proper version of Arc sibling to the Arc.js package. 166 | */ 167 | fetchFromArc: series( 168 | copy(`${joinPath(pathDaostackArcRepo, "build", "contracts", "*")} ${pathArcJsContracts}`) 169 | ), 170 | /** 171 | * fetch contract addresses from the DAOstack migrations package. 172 | */ 173 | fetchFromDaostack: series( 174 | copy(`${joinPath(pathDaostackMigrationsRepo, "migration.json")} ${pathArcJsRoot}`), 175 | `node ${joinPath(".", "package-scripts", "cleanMigrationJson.js")}`, 176 | ), 177 | }, 178 | docs: { 179 | api: { 180 | build: `node ${joinPath(".", "package-scripts", "typedoc.js")}`, 181 | /** 182 | * This is to create a list of all the API files for inclusion in mkdocs.yml 183 | * Whenever the set of API objects changes, you must copy the output of this 184 | * script and paste it into mkdocs.yml after the line: 185 | * `- Index : "api/README.md"` 186 | * 187 | * Easy Powershell command: nps -s docs.api.createPagesList | ac .\mkdocs.yml 188 | */ 189 | createPagesList: `node ${joinPath(".", "package-scripts", "createApiPagesList.js")} ./docs api/*/**` 190 | }, 191 | website: { 192 | build: "mkdocs build", 193 | preview: "mkdocs serve", 194 | publish: "mkdocs gh-deploy --force" 195 | }, 196 | build: { 197 | default: series( 198 | "nps docs.api.build", 199 | "nps docs.website.build", 200 | ), 201 | andPreview: series("nps docs.website.preview"), 202 | andPublish: series("nps docs.website.publish") 203 | }, 204 | clean: series( 205 | rimraf(joinPath(".", "docs", "api")), 206 | rimraf(joinPath(".", "site")) 207 | ) 208 | } 209 | } 210 | }; 211 | -------------------------------------------------------------------------------- /package-scripts/archiveGanacheDb.js: -------------------------------------------------------------------------------- 1 | const archiver = require("archiver"); 2 | const fs = require("fs"); 3 | 4 | const pathDaostackArcGanacheDbZip = process.argv[2]; 5 | const pathDaostackArcGanacheDb = process.argv[3]; 6 | const _archiver = archiver("zip", { 7 | zlib: { level: 9 } /* Sets the compression level. */ 8 | }); 9 | 10 | const stream = fs.createWriteStream(pathDaostackArcGanacheDbZip); 11 | 12 | _archiver.pipe(stream); 13 | 14 | _archiver.directory(pathDaostackArcGanacheDb, "ganacheDb").finalize(); 15 | -------------------------------------------------------------------------------- /package-scripts/cleanMigrationJson.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | const migrationJson = require("../migration.json"); 4 | 5 | /** 6 | * Remove the DAO entries, not supporting the migrations database for now. 7 | */ 8 | for (let netName in migrationJson) { 9 | delete migrationJson[netName].dao; 10 | } 11 | 12 | let data = JSON.stringify(migrationJson, null, 2); 13 | 14 | fs.writeFile("./migration.json", data, (err) => { 15 | if (err) throw err; 16 | }); 17 | -------------------------------------------------------------------------------- /package-scripts/createApiPagesList.js: -------------------------------------------------------------------------------- 1 | const glob = require("glob"); 2 | const path = require("path"); 3 | 4 | /* eslint-disable no-console */ 5 | 6 | const rootDir = process.argv[2]; 7 | const searchSpec = process.argv[3]; 8 | 9 | process.chdir(rootDir); 10 | 11 | const files = glob.sync(searchSpec, { 12 | nodir: true 13 | }); 14 | 15 | files.map((file) => { 16 | console.log(` - ${path.basename(file.replace(".md", ""))} : '${file}'`); 17 | }); 18 | -------------------------------------------------------------------------------- /package-scripts/createGenesisDao.js: -------------------------------------------------------------------------------- 1 | const GenesisDaoCreator = require("../dist/scripts/createGenesisDao.js").GenesisDaoCreator; 2 | const Utils = require("../dist/utils.js").Utils; 3 | 4 | Utils.getWeb3() 5 | .then((web3) => { 6 | const createGenesisDao = new GenesisDaoCreator(web3); 7 | return createGenesisDao.run() 8 | .catch((ex) => { 9 | console.log(`Error forging org: ${ex}`); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /package-scripts/fail.js: -------------------------------------------------------------------------------- 1 | throw new Error(process.argv[2]); 2 | -------------------------------------------------------------------------------- /package-scripts/migrateContracts.js: -------------------------------------------------------------------------------- 1 | const DAOstackMigration = require('@daostack/migration'); 2 | 3 | const output = process.argv[2]; 4 | 5 | DAOstackMigration.migrateBase({ 6 | output: output, 7 | force: true 8 | }); 9 | -------------------------------------------------------------------------------- /package-scripts/recursiveCopy.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs-extra"); 2 | 3 | const src = process.argv[2]; 4 | const dest = process.argv[3]; 5 | 6 | fs.copySync(src, dest); 7 | -------------------------------------------------------------------------------- /package-scripts/typedoc.js: -------------------------------------------------------------------------------- 1 | const TypeDoc = require("typedoc"); 2 | const Reflection = require("typedoc").Reflection; 3 | const glob = require("glob"); 4 | require("colors"); 5 | 6 | /** 7 | * Hack typedoc to retain character case with file names. 8 | * The alternative requires forking typedoc and typedoc-plugin-markdown, 9 | * and typedoc-plugin-markdown in that case requires major onerous changes, 10 | * particularly to its test code which is doing case-sensitive assertions. 11 | */ 12 | Reflection.prototype.getAlias = function () { 13 | if (!this._alias) { 14 | let alias = this.name.replace(/[^a-z0-9]/gi, "_"); 15 | if (alias === "") { 16 | alias = "reflection-" + this.id; 17 | } 18 | let target = this; 19 | while (target.parent && !target.parent.isProject() && !target.hasOwnDocument) { 20 | target = target.parent; 21 | } 22 | if (!target._aliases) { 23 | target._aliases = []; 24 | } 25 | let suffix = "", index = 0; 26 | while (target._aliases.indexOf(alias + suffix) !== -1) { 27 | suffix = "-" + (++index).toString(); 28 | } 29 | alias += suffix; 30 | target._aliases.push(alias); 31 | this._alias = alias; 32 | } 33 | return this._alias; 34 | }; 35 | 36 | /* eslint-disable no-console */ 37 | 38 | const LogLevel = { 39 | Verbose: 0, 40 | Info: 1, 41 | Warn: 2, 42 | Error: 3, 43 | Success: 4, 44 | }; 45 | 46 | const tsFiles = glob.sync("./lib/**/*", { 47 | nodir: true, 48 | ignore: "./lib/test/**/*" 49 | }); 50 | 51 | tsFiles.unshift("./custom_typings/system.d.ts"); 52 | tsFiles.unshift("./custom_typings/web3.d.ts"); 53 | 54 | if (tsFiles.length === 0) { 55 | throw new Error("No source files found."); 56 | } 57 | 58 | const out = "./docs/api"; 59 | const json = undefined; 60 | 61 | const options = { 62 | "target": "es6", 63 | "module": "commonjs", 64 | "hideGenerator": true, 65 | "readme": "none", 66 | "LogLevel": "Success", 67 | "mode": "file", 68 | "excludeProtected": true, 69 | "excludePrivate": true, 70 | "excludeNotExported": true, 71 | "name": "API Reference", 72 | "theme": "markdown", 73 | "lib": ["lib.dom.d.ts", "lib.es2015.d.ts", "lib.es2017.d.ts"] 74 | }; 75 | 76 | options.logger = function (message, level, newLine) { 77 | switch (level) { 78 | case LogLevel.Success: 79 | console.log(message.green); 80 | break; 81 | case LogLevel.Info: 82 | case LogLevel.Warn: 83 | if (newLine) { 84 | console.log(message.yellow); 85 | } else { 86 | process.stdout.write(message.yellow); 87 | } 88 | break; 89 | case LogLevel.Error: 90 | console.log(message.red); 91 | break; 92 | default: 93 | console.log(message); 94 | break; 95 | } 96 | }; 97 | 98 | const app = new TypeDoc.Application(options); 99 | const project = app.convert(tsFiles); 100 | if (project) { 101 | if (out) { app.generateDocs(project, out); } 102 | if (json) { app.generateJson(project, json); } 103 | } 104 | -------------------------------------------------------------------------------- /package-scripts/unArchiveGanacheDb.js: -------------------------------------------------------------------------------- 1 | const decompress = require("decompress"); 2 | const pathDaostackArcGanacheDbZip = process.argv[2]; 3 | const pathDaostackArcGanacheDb = process.argv[3]; 4 | // console.log(`unArchiveGanacheDb(${pathDaostackArcGanacheDbZip}, ${pathDaostackArcGanacheDb}`); 5 | decompress(pathDaostackArcGanacheDbZip, pathDaostackArcGanacheDb); 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@daostack/arc.js", 3 | "version": "0.0.0-alpha.105", 4 | "description": "A JavaScript library for interacting with @daostack/arc ethereum smart contracts", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "files": [ 8 | "lib/", 9 | "dist/", 10 | "custom_typings/", 11 | "docs/", 12 | "test/", 13 | "migrated_contracts/", 14 | "config/", 15 | "package-scripts.js", 16 | "package-scripts/", 17 | "truffle.js", 18 | "migration.json" 19 | ], 20 | "scripts": { 21 | "start": "nps", 22 | "test": "npm start test", 23 | "buildtest": "npm start test.build", 24 | "build": "npm start build", 25 | "lint": "npm start lint", 26 | "ganache": "npm start ganache", 27 | "migrateContracts": "npm start migrateContracts", 28 | "createGenesisDao": "npm start createGenesisDao" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/daostack/arc.js.git" 33 | }, 34 | "keywords": [ 35 | "Arc", 36 | "DAOstack", 37 | "Genesis", 38 | "Ethereum", 39 | "DAO", 40 | "javascript", 41 | "smart", 42 | "contracts" 43 | ], 44 | "author": "DAOstack", 45 | "license": "GPL-3.0", 46 | "engines": { 47 | "node": ">= 9.4.0" 48 | }, 49 | "bugs": { 50 | "url": "https://github.com/daostack/arc.js/issues" 51 | }, 52 | "homepage": "https://github.com/daostack/arc.js#readme", 53 | "peerDependencies": { 54 | "ganache-cli": "^6.2.3" 55 | }, 56 | "dependencies": { 57 | "@daostack/migration": "0.0.0-alpha.58-v6", 58 | "archiver": "^2.1.0", 59 | "bignumber.js": "^5.0.0", 60 | "circular-json": "^0.5.4", 61 | "color-convert": "^1.9.1", 62 | "cwd": "^0.10.0", 63 | "decompress": "^4.2.0", 64 | "env-variable": "0.0.3", 65 | "es6-promisify": "^6.0.0", 66 | "ethereumjs-abi": "^0.6.5", 67 | "ethjs-abi": "0.1.8", 68 | "fs-extra": "^5.0.0", 69 | "ganache-cli": "^6.2.3", 70 | "node-glob": "^1.2.0", 71 | "nps": "^5.9.3", 72 | "nps-utils": "^1.7.0", 73 | "path.join": "^1.0.0", 74 | "pubsub-js": "^1.6.0", 75 | "truffle-contract": "^3.0.1", 76 | "tslib": "^1.9.0", 77 | "web3": "^0.20.7", 78 | "websocket": "^1.0.26" 79 | }, 80 | "devDependencies": { 81 | "@daostack/arc": "0.0.0-alpha.58", 82 | "@types/chai": "^4.1.2", 83 | "@types/circular-json": "^0.4.0", 84 | "@types/es6-promisify": "^6.0.0", 85 | "@types/mocha": "^5.1.0", 86 | "@types/pubsub-js": "^1.5.18", 87 | "chai": "^4.1.2", 88 | "colors": "^1.3.2", 89 | "mocha": "^4.0.1", 90 | "truffle-hdwallet-provider": "0.0.6", 91 | "tslint": "^5.11.0", 92 | "typedoc": "^0.11.1", 93 | "typedoc-plugin-markdown": "^1.0.14", 94 | "typescript": "^3.0.3" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/absoluteVote.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import { InitializeArcJs } from "../lib"; 3 | import { 4 | AbsoluteVoteFactory, 5 | } from "../lib/wrappers/absoluteVote"; 6 | import * as helpers from "./helpers"; 7 | 8 | beforeEach(async () => { 9 | await InitializeArcJs(); 10 | }); 11 | 12 | describe("AbsoluteVote", () => { 13 | 14 | it("can get params hash", async () => { 15 | 16 | const absoluteVote = await AbsoluteVoteFactory.new(); 17 | 18 | const params = await { 19 | ownerVote: true, 20 | votePerc: 50, 21 | }; 22 | 23 | const paramsHashSet = (await absoluteVote.setParameters(params)).result; 24 | 25 | const paramsHashGet = await absoluteVote.getParametersHash(params); 26 | 27 | assert.equal(paramsHashGet, paramsHashSet, "Hashes are not the same"); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/accountService.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import { AccountService } from "../lib/accountService"; 3 | import { Address, fnVoid } from "../lib/commonTypes"; 4 | import { InitializeArcJs } from "../lib/index"; 5 | import { Utils } from "../lib/utils"; 6 | import * as helpers from "./helpers"; 7 | 8 | describe("AccountService", async () => { 9 | 10 | it("watch callback detects account change", async () => { 11 | 12 | await InitializeArcJs({ 13 | watchForAccountChanges: true, 14 | }); 15 | 16 | let fired = false; 17 | 18 | /* tslint:disable:no-unused-expression */ 19 | new Promise((resolve: fnVoid): void => { 20 | AccountService.subscribeToAccountChanges((account: Address) => { fired = true; resolve(); }); 21 | }); 22 | 23 | const saveGetDefaultAccount = Utils.getDefaultAccount; 24 | Utils.getDefaultAccount = (): Promise => Promise.resolve(helpers.SOME_ADDRESS); 25 | 26 | await helpers.sleep(2000); 27 | 28 | Utils.getDefaultAccount = saveGetDefaultAccount; 29 | AccountService.endAccountWatch(); 30 | assert(fired, "event was not fired"); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/config.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import BigNumber from "bignumber.js"; 3 | import { assert } from "chai"; 4 | import { ConfigService } from "../lib/configService"; 5 | import { TestWrapperFactory } from "../lib/test/wrappers/testWrapper"; 6 | import { AbsoluteVoteParams } from "../lib/wrappers/absoluteVote"; 7 | import "./helpers"; 8 | 9 | describe("ConfigService", () => { 10 | it("can get and set configuration values", () => { 11 | assert.equal(ConfigService.get("providerUrl"), "127.0.0.1"); 12 | ConfigService.set("providerUrl", "localhost"); 13 | assert.equal(ConfigService.get("providerUrl"), "localhost"); 14 | }); 15 | 16 | it("doesn't reload default values when imported again", () => { 17 | const newConfigService = require("../lib/configService").ConfigService; 18 | assert.equal(newConfigService.get("providerUrl"), "localhost"); 19 | ConfigService.set("providerUrl", "127.0.0.1"); 20 | }); 21 | 22 | it("can specify gasPrice", async () => { 23 | let gasPrice = new BigNumber(web3.toWei(40, "gwei")); 24 | 25 | ConfigService.set("gasPriceAdjustment", (): Promise => Promise.resolve(gasPrice)); 26 | 27 | const testWrapper = await TestWrapperFactory.new(); 28 | 29 | let txResult = await testWrapper.setParameters({} as AbsoluteVoteParams); 30 | 31 | let txInfo = await web3.eth.getTransaction(txResult.tx); 32 | 33 | assert(txInfo.gasPrice.eq(gasPrice)); 34 | 35 | ConfigService.set("gasPriceAdjustment", 36 | (defaultGasPrice: BigNumber): Promise => Promise.resolve( 37 | gasPrice = defaultGasPrice.mul(1.25).add(web3.toWei(2, "gwei")))); 38 | 39 | txResult = await testWrapper.setParameters({} as AbsoluteVoteParams); 40 | 41 | txInfo = await web3.eth.getTransaction(txResult.tx); 42 | 43 | assert(txInfo.gasPrice.eq(gasPrice)); 44 | 45 | ConfigService.set("gasPriceAdjustment", null); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/daoCreator.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import { DaoCreatorFactory } from "../lib/wrappers/daoCreator"; 3 | import "./helpers"; 4 | 5 | describe("DaoCreator", () => { 6 | 7 | it("can call new with no controllerCreatorAddress", async () => { 8 | 9 | const daoCreator = await DaoCreatorFactory.new(); 10 | assert.isOk(daoCreator, "daoCreator is not set"); 11 | assert(daoCreator.address, "daoCreator has no address"); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/estimateGas.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "bignumber.js"; 2 | import { assert } from "chai"; 3 | import { BinaryVoteResult, Hash } from "../lib/commonTypes"; 4 | import { DAO } from "../lib/dao"; 5 | import { LoggingService } from "../lib/loggingService"; 6 | import { TransactionReceiptTruffle, TransactionService } from "../lib/transactionService"; 7 | import { Utils, Web3 } from "../lib/utils"; 8 | import { ContributionRewardFactory, ContributionRewardWrapper } from "../lib/wrappers/contributionReward"; 9 | import { GenesisProtocolWrapper } from "../lib/wrappers/genesisProtocol"; 10 | import { WrapperService } from "../lib/wrapperService"; 11 | import * as helpers from "./helpers"; 12 | 13 | describe("estimate gas", () => { 14 | 15 | let dao: DAO; 16 | let proposalId: Hash; 17 | let scheme: ContributionRewardWrapper; 18 | let network; 19 | let web3: Web3; 20 | let stakingAmount: BigNumber | string; 21 | let votingMachine: GenesisProtocolWrapper; 22 | 23 | const setup = async (): Promise => { 24 | if (!network) { 25 | network = await Utils.getNetworkName(); 26 | LoggingService.info(`running against network: ${network}`); 27 | } 28 | 29 | web3 = await Utils.getWeb3(); 30 | stakingAmount = web3.toWei("10"); 31 | 32 | if (!dao) { 33 | if (network !== "Ganache") { 34 | // keep our costs down by reusing a DAO that we know exists 35 | // This is the "Native Staking Token" DAO. 36 | // It uses the DAO's native token as the staking token so founders will be assured to have some. 37 | // our accounts[0] needs to be a founder 38 | const daoAddress = network === "Kovan" ? "0xae6ecbf473e550cca70d348c334ff6191d5bfab3" : "???"; 39 | dao = await DAO.at(daoAddress); 40 | } else { 41 | // this will use the ganache GEN token as the staking token 42 | // helpers assures on test startup that the founders (accounts) have some 43 | dao = await helpers.forgeDao({ 44 | schemes: [ 45 | { 46 | name: "ContributionReward", 47 | votingMachineParams: { 48 | votingMachineName: "GenesisProtocol", 49 | }, 50 | }, 51 | ], 52 | }); 53 | } 54 | assert.isOk(dao); 55 | LoggingService.info(`DAO at: ${dao.avatar.address}`); 56 | } 57 | 58 | if (!scheme) { 59 | scheme = await helpers.getDaoScheme( 60 | dao, 61 | "ContributionReward", 62 | ContributionRewardFactory) as ContributionRewardWrapper; 63 | 64 | assert.isOk(scheme); 65 | 66 | votingMachine = await WrapperService.factories.GenesisProtocol.at( 67 | (await scheme.getSchemeParameters(dao.avatar.address)).votingMachineAddress); 68 | 69 | assert.isOk(votingMachine); 70 | } 71 | }; 72 | 73 | beforeEach(async () => { 74 | await setup(); 75 | }); 76 | 77 | it("can create a proposal", async () => { 78 | // 280000 79 | 80 | const gas = await scheme.estimateGas( 81 | scheme.contract.proposeContributionReward, 82 | [ 83 | dao.avatar.address, 84 | helpers.SOME_HASH, 85 | "0", 86 | ["1", "0", "0", 1, 1], 87 | helpers.SOME_ADDRESS, 88 | accounts[0]], 89 | { from: accounts[0] }); 90 | 91 | LoggingService.info(`estimated gas for creating a proposal: ${gas}`); 92 | 93 | // assert(gas >= 280000, `insufficient gas: ${gas}, should be 280000`); 94 | 95 | // if (network === "Ganache") { 96 | (scheme.constructor as any).synchronization_timeout = 0; 97 | 98 | const result = await scheme.contract.proposeContributionReward( 99 | dao.avatar.address, 100 | helpers.SOME_HASH, 101 | "0", 102 | ["1", "0", "0", 1, 1], 103 | helpers.SOME_ADDRESS, 104 | accounts[0], 105 | { gas, from: accounts[0] } 106 | ) as TransactionReceiptTruffle; 107 | 108 | proposalId = TransactionService.getValueFromLogs(result, "_proposalId"); 109 | 110 | LoggingService.info(`Created proposal Id: ${proposalId}`); 111 | }); 112 | 113 | it("can preapprove a stake transaction", async () => { 114 | // 45204 115 | 116 | LoggingService.info(`accounts[0]: ${accounts[0]}`); 117 | 118 | const stakingToken = await votingMachine.getStakingToken(); 119 | 120 | const gas = await scheme.estimateGas( 121 | stakingToken.contract.approve, 122 | [votingMachine.address, stakingAmount], 123 | { from: accounts[0] }); 124 | 125 | LoggingService.info(`estimated gas for preapproving token transfer: ${gas}`); 126 | 127 | // assert(gas >= 45204, `insufficient gas: ${gas}, should be 45204`); 128 | 129 | (scheme.constructor as any).synchronization_timeout = 0; 130 | 131 | await stakingToken.contract.approve(votingMachine.address, stakingAmount, { gas, from: accounts[0] }); 132 | }); 133 | 134 | it("can stake on a proposal", async () => { 135 | // 298327 136 | 137 | const gas = await scheme.estimateGas( 138 | votingMachine.contract.stake, 139 | [proposalId, 1, stakingAmount], 140 | { from: accounts[0] }); 141 | 142 | LoggingService.info(`estimated gas for staking: ${gas}`); 143 | 144 | // assert(gas >= 235626, `insufficient gas: ${gas}, should be 235626`); 145 | 146 | (scheme.constructor as any).synchronization_timeout = 0; 147 | 148 | await votingMachine.contract.stake(proposalId, 1, stakingAmount, { gas, from: accounts[0] }); 149 | }); 150 | 151 | it("can vote on a proposal", async () => { 152 | // 235626 153 | 154 | const gas = await scheme.estimateGas( 155 | votingMachine.contract.vote, 156 | [proposalId, 1, helpers.NULL_ADDRESS], 157 | { from: accounts[0] }); 158 | 159 | LoggingService.info(`estimated gas for voting: ${gas}`); 160 | 161 | // assert(gas >= 235626, `insufficient gas: ${gas}, should be 235626`); 162 | 163 | (scheme.constructor as any).synchronization_timeout = 0; 164 | 165 | await votingMachine.contract.vote(proposalId, 1, helpers.NULL_ADDRESS, { gas, from: accounts[0] }); 166 | }); 167 | 168 | it("can execute proposal", async () => { 169 | 170 | const gas = await scheme.estimateGas( 171 | votingMachine.contract.execute, 172 | [proposalId], 173 | { from: accounts[0] }); 174 | 175 | LoggingService.info(`estimated gas for executing proposal: ${gas}`); 176 | 177 | // assert(gas >= 235626, `insufficient gas: ${gas}, should be 235626`); 178 | 179 | (scheme.constructor as any).synchronization_timeout = 0; 180 | 181 | await votingMachine.contract.execute(proposalId, { gas, from: accounts[0] }); 182 | }); 183 | 184 | it("can redeem proposal", async () => { 185 | 186 | if (network === "Ganache") { 187 | await helpers.vote(votingMachine, proposalId, BinaryVoteResult.Yes, accounts[1]); 188 | } 189 | 190 | // assert(await helpers.voteWasExecuted(votingMachine.contract, proposalId), "vote was not executed"); 191 | 192 | if (network === "Ganache") { 193 | await helpers.increaseTime(1); 194 | } else { 195 | await helpers.waitForBlocks(2); 196 | } 197 | 198 | const gas = await scheme.estimateGas( 199 | scheme.contract.redeemNativeToken, 200 | [proposalId, dao.avatar.address, helpers.NULL_ADDRESS], 201 | { from: accounts[0] }); 202 | 203 | LoggingService.info(`estimated gas for redeeming native token: ${gas}`); 204 | 205 | // assert(gas >= 235626, `insufficient gas: ${gas}, should be 235626`); 206 | 207 | (scheme.constructor as any).synchronization_timeout = 0; 208 | 209 | await scheme.contract.redeemNativeToken(proposalId, dao.avatar.address, { gas, from: accounts[0] }); 210 | }); 211 | }); 212 | -------------------------------------------------------------------------------- /test/redeemer.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import { assert } from "chai"; 3 | import { BinaryVoteResult } from "../lib/commonTypes"; 4 | import { UtilsInternal } from "../lib/utilsInternal"; 5 | import { ContributionRewardFactory, ContributionRewardWrapper } from "../lib/wrappers/contributionReward"; 6 | import { GenesisProtocolWrapper } from "../lib/wrappers/genesisProtocol"; 7 | import { WrapperService } from "../lib/wrapperService"; 8 | import * as helpers from "./helpers"; 9 | 10 | describe("Redeemer", () => { 11 | 12 | it("can redeem", async () => { 13 | 14 | const dao = await helpers.forgeDao({ 15 | founders: [{ 16 | address: accounts[0], 17 | reputation: web3.toWei(3000), 18 | tokens: web3.toWei(3000), 19 | }, 20 | { 21 | address: accounts[1], 22 | reputation: web3.toWei(1000), 23 | tokens: web3.toWei(1000), 24 | }, 25 | { 26 | address: accounts[2], 27 | reputation: web3.toWei(500), 28 | tokens: web3.toWei(500), 29 | }, 30 | { 31 | address: accounts[3], 32 | reputation: web3.toWei(500), 33 | tokens: web3.toWei(500), 34 | }], 35 | schemes: [ 36 | { 37 | name: "ContributionReward", 38 | votingMachineParams: { 39 | votingMachineName: "GenesisProtocol", 40 | }, 41 | }, 42 | ], 43 | }); 44 | 45 | const scheme = await helpers.getDaoScheme( 46 | dao, 47 | "ContributionReward", 48 | ContributionRewardFactory) as ContributionRewardWrapper; 49 | 50 | const ethAmount = 0.100000001; 51 | const repAmount = 1; 52 | const nativeTokenAmount = 0; 53 | const externalTokenAmount = 3; 54 | const externalToken = await dao.token; 55 | 56 | const result = await scheme.proposeContributionReward(Object.assign({ 57 | avatar: dao.avatar.address, 58 | beneficiaryAddress: accounts[1], 59 | description: "A new contribution", 60 | ethReward: web3.toWei(ethAmount), 61 | externalToken: externalToken.address, 62 | externalTokenReward: web3.toWei(externalTokenAmount), 63 | nativeTokenReward: web3.toWei(nativeTokenAmount), 64 | numberOfPeriods: 1, 65 | periodLength: 0, 66 | reputationChange: web3.toWei(repAmount), 67 | })); 68 | 69 | const proposalId = await result.getProposalIdFromMinedTx(); 70 | 71 | assert.isOk(proposalId); 72 | assert.isOk(result.votingMachine); 73 | 74 | const gpAddress = await scheme.getVotingMachineAddress(dao.avatar.address); 75 | const gp = await WrapperService.factories.GenesisProtocol.at(gpAddress); 76 | await helpers.vote(result.votingMachine, proposalId, BinaryVoteResult.No, accounts[2]); 77 | await helpers.vote(result.votingMachine, proposalId, BinaryVoteResult.Yes, accounts[3]); 78 | 79 | const stakeAmount = (await gp.getThresholdFromProposal(proposalId)).add(web3.toWei(10)); 80 | 81 | await (await gp.stakeWithApproval({ amount: stakeAmount, vote: 1, proposalId })).getTxMined(); 82 | 83 | await helpers.vote(result.votingMachine, proposalId, BinaryVoteResult.Yes, accounts[1]); 84 | 85 | /** 86 | * expire out of the GP boosting phase. 259200 is the default boosting phase length. 87 | */ 88 | await helpers.increaseTime(259200); 89 | 90 | // give the avatar some eth to pay out 91 | await helpers.transferEthToDao(dao, ethAmount); 92 | await helpers.transferTokensToDao(dao, externalTokenAmount, accounts[1], externalToken); 93 | 94 | const redeemer = WrapperService.wrappers.Redeemer; 95 | 96 | /** 97 | * staker 98 | */ 99 | let redeemable = (await redeemer.redeemables( 100 | { 101 | avatarAddress: dao.avatar.address, 102 | beneficiaryAddress: accounts[0], 103 | proposalId, 104 | } 105 | )); 106 | 107 | assert.equal(web3.fromWei(redeemable.contributionRewardEther).toNumber(), ethAmount); 108 | assert.equal(web3.fromWei(redeemable.contributionRewardNativeToken).toNumber(), nativeTokenAmount); 109 | assert.equal(web3.fromWei(redeemable.contributionRewardExternalToken).toNumber(), externalTokenAmount); 110 | assert.equal(web3.fromWei(redeemable.contributionRewardReputation).toNumber(), repAmount); 111 | /** staking-related token results are broken until issue is worked out wirh Infra 112 | * https://github.com/daostack/infra/issues/15 113 | */ 114 | // assert(redeemable.stakerTokenAmount.eq(stakeAmount.mul(.5)), `wrong stakerTokenAmount`); 115 | assert.equal(web3.fromWei(redeemable.stakerReputationAmount).toNumber(), 1); 116 | // assert(redeemable.daoStakingBountyPotentialReward.eq(stakeAmount.mul(.75)), 117 | // `wrong daoStakingBountyPotentialReward`); 118 | assert.equal(web3.fromWei(redeemable.voterReputationAmount).toNumber(), 0); 119 | assert.equal(web3.fromWei(redeemable.proposerReputationAmount).toNumber(), 15); 120 | 121 | /** 122 | * winning boosted voter 123 | */ 124 | redeemable = (await redeemer.redeemables( 125 | { 126 | avatarAddress: dao.avatar.address, 127 | beneficiaryAddress: accounts[1], 128 | proposalId, 129 | } 130 | )); 131 | 132 | assert.equal(web3.fromWei(redeemable.contributionRewardEther).toNumber(), ethAmount); 133 | assert.equal(web3.fromWei(redeemable.contributionRewardNativeToken).toNumber(), nativeTokenAmount); 134 | assert.equal(web3.fromWei(redeemable.contributionRewardExternalToken).toNumber(), externalTokenAmount); 135 | assert.equal(web3.fromWei(redeemable.contributionRewardReputation).toNumber(), repAmount); 136 | assert.equal(web3.fromWei(redeemable.stakerTokenAmount).toNumber(), 0); 137 | assert.equal(web3.fromWei(redeemable.stakerReputationAmount).toNumber(), 0); 138 | assert.equal(web3.fromWei(redeemable.voterReputationAmount).toNumber(), 0); 139 | assert.equal(web3.fromWei(redeemable.voterTokenAmount).toNumber(), 0); 140 | 141 | /** 142 | * losing preboosted voter 143 | */ 144 | redeemable = (await redeemer.redeemables( 145 | { 146 | avatarAddress: dao.avatar.address, 147 | beneficiaryAddress: accounts[2], 148 | proposalId, 149 | } 150 | )); 151 | 152 | assert.equal(web3.fromWei(redeemable.contributionRewardEther).toNumber(), ethAmount); 153 | assert.equal(web3.fromWei(redeemable.contributionRewardNativeToken).toNumber(), nativeTokenAmount); 154 | assert.equal(web3.fromWei(redeemable.contributionRewardExternalToken).toNumber(), externalTokenAmount); 155 | assert.equal(web3.fromWei(redeemable.contributionRewardReputation).toNumber(), repAmount); 156 | // assert(redeemable.voterTokenAmount.eq(stakeAmount.mul(.25)), `wrong voterTokenAmount`); 157 | assert.equal(web3.fromWei(redeemable.voterReputationAmount).toNumber(), 0); 158 | 159 | /** 160 | * winning preboosted voter 161 | */ 162 | redeemable = (await redeemer.redeemables( 163 | { 164 | avatarAddress: dao.avatar.address, 165 | beneficiaryAddress: accounts[3], 166 | proposalId, 167 | } 168 | )); 169 | 170 | assert.equal(web3.fromWei(redeemable.contributionRewardEther).toNumber(), ethAmount); 171 | assert.equal(web3.fromWei(redeemable.contributionRewardNativeToken).toNumber(), nativeTokenAmount); 172 | assert.equal(web3.fromWei(redeemable.contributionRewardExternalToken).toNumber(), externalTokenAmount); 173 | assert.equal(web3.fromWei(redeemable.contributionRewardReputation).toNumber(), repAmount); 174 | // assert(redeemable.voterTokenAmount.eq(stakeAmount.mul(.25)), `wrong voterTokenAmount`); 175 | assert.equal(web3.fromWei(redeemable.voterReputationAmount).toNumber(), 7); 176 | 177 | const latestBlock = await UtilsInternal.lastBlockNumber(); 178 | 179 | const redeemed = (await redeemer.redeem({ 180 | avatarAddress: dao.avatar.address, 181 | beneficiaryAddress: accounts[0], 182 | proposalId, 183 | })).getTxMined(); 184 | 185 | assert(redeemed); 186 | 187 | const fetcher = gp.Redeem( 188 | { _beneficiary: accounts[0], _proposalId: proposalId, _avatar: dao.avatar.address }, 189 | { fromBlock: latestBlock }); 190 | 191 | const events = await fetcher.get(); 192 | 193 | assert.equal(events.length, 1); 194 | // assert(events[0].args._amount.eq(stakeAmount.mul(.5)), `wrong gp _beneficiary redeemed tokens`); 195 | }); 196 | }); 197 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "preserveConstEnums": true, 7 | "sourceMap": true, 8 | "importHelpers": true, 9 | "lib": [ 10 | "es2015", 11 | "es2017", 12 | "dom", 13 | ], 14 | "downlevelIteration": true 15 | }, 16 | "include": [ 17 | "../custom_typings/web3_global.d.ts", 18 | "../custom_typings/system.d.ts", 19 | "./**/*", 20 | ], 21 | "exclude": [ 22 | "../node_modules", 23 | "../dist", 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /test/voteInOrganizationScheme.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import { 3 | Address, 4 | BinaryVoteResult, 5 | fnVoid, 6 | Hash 7 | } from "../lib/commonTypes"; 8 | import { DecodedLogEntryEvent } from "../lib/iContractWrapperBase"; 9 | import { AbsoluteVoteWrapper } from "../lib/wrappers/absoluteVote"; 10 | import { VoteProposalEventResult } from "../lib/wrappers/iIntVoteInterface"; 11 | import { IntVoteInterfaceWrapper } from "../lib/wrappers/intVoteInterface"; 12 | import { SchemeRegistrarFactory, SchemeRegistrarWrapper } from "../lib/wrappers/schemeRegistrar"; 13 | import { 14 | VotableVoteInOrganizationProposal, 15 | VoteInOrganizationSchemeFactory, 16 | VoteInOrganizationSchemeWrapper 17 | } from "../lib/wrappers/voteInOrganizationScheme"; 18 | import * as helpers from "./helpers"; 19 | 20 | const createProposal = 21 | async (): Promise<{ proposalId: Hash, votingMachine: IntVoteInterfaceWrapper, scheme: SchemeRegistrarWrapper }> => { 22 | 23 | const originalDao = await helpers.forgeDao({ 24 | founders: [{ 25 | address: accounts[0], 26 | reputation: web3.toWei(30), 27 | tokens: web3.toWei(100), 28 | }, 29 | { 30 | address: accounts[1], 31 | reputation: web3.toWei(30), 32 | tokens: web3.toWei(100), 33 | }], 34 | name: "Original", 35 | schemes: [ 36 | { name: "ContributionReward" } 37 | , { 38 | name: "SchemeRegistrar", 39 | votingMachineParams: { 40 | ownerVote: false, 41 | }, 42 | }, 43 | ], 44 | tokenName: "Tokens of Original", 45 | tokenSymbol: "ORG", 46 | }); 47 | 48 | const schemeToDelete = (await originalDao.getSchemes("ContributionReward"))[0].address; 49 | assert.isOk(schemeToDelete); 50 | 51 | const schemeRegistrar = 52 | await helpers.getDaoScheme(originalDao, "SchemeRegistrar", SchemeRegistrarFactory) as SchemeRegistrarWrapper; 53 | assert.isOk(schemeRegistrar); 54 | /** 55 | * propose to remove ContributionReward. It should get the ownerVote, then requiring just 30 more reps to execute. 56 | */ 57 | const result = await schemeRegistrar.proposeToRemoveScheme( 58 | { 59 | avatar: originalDao.avatar.address, 60 | schemeAddress: schemeToDelete, 61 | }); 62 | 63 | const proposalId = await result.getProposalIdFromMinedTx(); 64 | assert.isOk(proposalId); 65 | 66 | /** 67 | * get the voting machine that will be used to vote for this proposal 68 | */ 69 | const votingMachine = await helpers.getSchemeVotingMachine(originalDao, schemeRegistrar); 70 | 71 | assert.isOk(votingMachine); 72 | assert.isFalse(await helpers.voteWasExecuted(votingMachine, proposalId)); 73 | 74 | return { proposalId, votingMachine, scheme: schemeRegistrar }; 75 | }; 76 | 77 | describe("VoteInOrganizationScheme", () => { 78 | let dao; 79 | let voteInOrganizationScheme: VoteInOrganizationSchemeWrapper; 80 | beforeEach(async () => { 81 | 82 | dao = await helpers.forgeDao({ 83 | founders: [{ 84 | address: accounts[0], 85 | reputation: web3.toWei(30), 86 | tokens: web3.toWei(100), 87 | }, 88 | { 89 | address: accounts[1], 90 | reputation: web3.toWei(30), 91 | tokens: web3.toWei(100), 92 | }, 93 | { 94 | address: accounts[2], 95 | reputation: web3.toWei(30), 96 | tokens: web3.toWei(100), 97 | }, 98 | ], 99 | schemes: [ 100 | { 101 | name: "VoteInOrganizationScheme", 102 | votingMachineParams: { 103 | ownerVote: false, 104 | }, 105 | }, 106 | ], 107 | }); 108 | 109 | voteInOrganizationScheme = await helpers.getDaoScheme( 110 | dao, 111 | "VoteInOrganizationScheme", 112 | VoteInOrganizationSchemeFactory) as VoteInOrganizationSchemeWrapper; 113 | 114 | assert.isOk(voteInOrganizationScheme); 115 | }); 116 | 117 | it("can get proposed votes", async () => { 118 | 119 | /** 120 | * this is the proposal we'll vote on remotely 121 | */ 122 | const originalProposalInfo = await createProposal(); 123 | 124 | const options = { 125 | avatar: dao.avatar.address, 126 | originalProposalId: originalProposalInfo.proposalId, 127 | originalVotingMachineAddress: originalProposalInfo.votingMachine.address, 128 | }; 129 | 130 | const proposalInfo = await voteInOrganizationScheme.proposeVoteInOrganization(options); 131 | const proposalInfo2 = await voteInOrganizationScheme.proposeVoteInOrganization(options); 132 | 133 | const proposals = await ( 134 | await voteInOrganizationScheme.getVotableProposals(dao.avatar.address))( 135 | {}, 136 | { fromBlock: 0 } 137 | ).get(); 138 | 139 | assert.equal(proposals.length, 2); 140 | 141 | const proposalId1 = await proposalInfo.getProposalIdFromMinedTx(); 142 | const proposalId2 = await proposalInfo2.getProposalIdFromMinedTx(); 143 | 144 | assert.equal( 145 | proposals.filter( 146 | /* tslint:disable-next-line:max-line-length */ 147 | (p: VotableVoteInOrganizationProposal) => p.proposalId === proposalId1).length, 148 | 1, 149 | "first proposal not found"); 150 | 151 | assert.equal( 152 | proposals.filter( 153 | /* tslint:disable-next-line:max-line-length */ 154 | (p: VotableVoteInOrganizationProposal) => p.proposalId === proposalId2).length, 155 | 1, 156 | "second proposal not found"); 157 | }); 158 | 159 | it("proposeVoteInOrganization organization vote", async () => { 160 | 161 | /** 162 | * this is the proposal we'll vote on remotely 163 | */ 164 | const proposalInfo = await createProposal(); 165 | 166 | const options = { 167 | avatar: dao.avatar.address, 168 | originalProposalId: proposalInfo.proposalId, 169 | originalVotingMachineAddress: proposalInfo.votingMachine.address, 170 | }; 171 | 172 | const result = await voteInOrganizationScheme.proposeVoteInOrganization(options); 173 | 174 | assert.isOk(result); 175 | assert.isOk(result.tx); 176 | 177 | const proposalId = await result.getProposalIdFromMinedTx(); 178 | 179 | assert.isOk(proposalId); 180 | 181 | const tx = await result.watchForTxMined(); 182 | 183 | assert.equal(tx.logs.length, 1); // no other event 184 | // TODO: restore this: assert.equal(tx.logs[0].event, "NewVoteProposal"); 185 | 186 | const votingMachine = await helpers.getSchemeVotingMachine(dao, voteInOrganizationScheme); 187 | 188 | assert.isOk(votingMachine); 189 | 190 | /** 191 | * cast a vote using voteInOrganizationScheme's voting machine. 192 | */ 193 | await helpers.vote(votingMachine, proposalId, BinaryVoteResult.Yes, accounts[1]); 194 | await helpers.vote(votingMachine, proposalId, BinaryVoteResult.Yes, accounts[2]); 195 | /** 196 | * confirm that a vote was cast by the original DAO's scheme 197 | */ 198 | const originalVoteEvent = votingMachine.VoteProposal({}, { fromBlock: 0 }); 199 | 200 | await new Promise(async (resolve: fnVoid): Promise => { 201 | originalVoteEvent.get((err: Error, eventsArray: Array>) => { 202 | 203 | const foundVoteProposalEvent = eventsArray.filter((e: DecodedLogEntryEvent) => { 204 | return e.args._proposalId === proposalInfo.proposalId; 205 | }); 206 | 207 | if (foundVoteProposalEvent.length === 1) { 208 | const event = foundVoteProposalEvent[0]; 209 | /** 210 | * expect a vote 'for' 211 | */ 212 | assert.equal(event.args._vote.toNumber(), 1); 213 | /** 214 | * expect the vote to have been cast on behalf of the DAO 215 | */ 216 | assert.equal(event.args._voter, dao.avatar.address, "wrong user voted"); 217 | } else { 218 | assert(false, "proposal vote not found in original scheme"); 219 | } 220 | resolve(); 221 | }); 222 | }); 223 | }); 224 | }); 225 | -------------------------------------------------------------------------------- /test/web3EventService.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import { assert } from "chai"; 3 | import { 4 | DecodedLogEntryEvent, 5 | } from "web3"; 6 | import { Utils } from "../lib/utils"; 7 | import { UtilsInternal } from "../lib/utilsInternal"; 8 | import { 9 | Web3EventService 10 | } from "../lib/web3EventService"; 11 | import { 12 | ApprovalEventResult, 13 | StandardTokenFactory, 14 | } from "../lib/wrappers/standardToken"; 15 | import "./helpers"; 16 | 17 | describe("Web3EventService", () => { 18 | 19 | interface EntityType { blockNumber: number; } 20 | 21 | const makeTransactions = async (count: number = 1): Promise => { 22 | while (count--) { 23 | await web3.eth.sendTransaction({ 24 | from: accounts[0], 25 | to: accounts[3], 26 | value: web3.toWei(0.00001, "ether"), 27 | }); 28 | } 29 | }; 30 | 31 | it("can get entity with requiredDepth", async () => { 32 | 33 | const tokenAddress = await Utils.getGenTokenAddress(); 34 | assert.isOk(tokenAddress); 35 | const token = await StandardTokenFactory.at(tokenAddress); 36 | assert.isOk(token); 37 | 38 | const initialBlockNumber = await UtilsInternal.lastBlockNumber(); 39 | let currentBlockNumber = initialBlockNumber; 40 | let eventBlockNumber = currentBlockNumber; 41 | 42 | const web3EventService = new Web3EventService(); 43 | 44 | const fetcher = web3EventService.createEntityFetcherFactory( 45 | token.Approval, 46 | async (event: DecodedLogEntryEvent): Promise => { 47 | return Promise.resolve({ blockNumber: event.blockNumber }); 48 | })({ spender: accounts[0], owner: accounts[4] }, { fromBlock: initialBlockNumber }); 49 | 50 | const amount = web3.toWei(1); 51 | const result = await token.approve({ 52 | amount, 53 | owner: accounts[4], 54 | spender: accounts[0], 55 | }); 56 | 57 | await result.getTxConfirmed(); 58 | 59 | await makeTransactions(2); 60 | 61 | await new Promise(async ( 62 | resolve: () => void, 63 | reject: () => void): Promise => { 64 | fetcher.get(async (error: Error, entitiesPromise: Promise>) => { 65 | const entities = await entitiesPromise; 66 | for (const entity of entities) { 67 | currentBlockNumber = await UtilsInternal.lastBlockNumber(); 68 | eventBlockNumber = entity.blockNumber; 69 | } 70 | resolve(); 71 | }, 2); 72 | }); 73 | 74 | assert.equal(eventBlockNumber, currentBlockNumber - 2); 75 | }); 76 | 77 | it("can watch entity with requiredDepth", async () => { 78 | 79 | const tokenAddress = await Utils.getGenTokenAddress(); 80 | assert.isOk(tokenAddress); 81 | const token = await StandardTokenFactory.at(tokenAddress); 82 | assert.isOk(token); 83 | 84 | const initialBlockNumber = await UtilsInternal.lastBlockNumber(); 85 | let currentBlockNumber = initialBlockNumber; 86 | let eventBlockNumber = currentBlockNumber; 87 | 88 | const web3EventService = new Web3EventService(); 89 | 90 | const fetcher = web3EventService.createEntityFetcherFactory( 91 | token.Approval, 92 | async (event: DecodedLogEntryEvent): Promise => { 93 | return { blockNumber: event.blockNumber }; 94 | })({ spender: accounts[0], owner: accounts[4] }, { fromBlock: initialBlockNumber }); 95 | 96 | let done = false; 97 | const promise = new Promise(async ( 98 | resolve: () => void, 99 | reject: () => void): Promise => { 100 | fetcher.watch(async (error: Error, entity: EntityType) => { 101 | 102 | currentBlockNumber = await UtilsInternal.lastBlockNumber(); 103 | eventBlockNumber = entity.blockNumber; 104 | done = true; 105 | resolve(); 106 | }, 2); 107 | }); 108 | 109 | const amount = web3.toWei(1); 110 | const result = await token.approve({ 111 | amount, 112 | owner: accounts[4], 113 | spender: accounts[0], 114 | }); 115 | 116 | await result.getTxConfirmed(); 117 | 118 | await makeTransactions(2); 119 | 120 | const timeoutPromise = new Promise( 121 | async ( 122 | resolve: () => void, 123 | reject: () => void): Promise => { 124 | // give the watch two seconds to find the tx 125 | setTimeout(() => { assert(done, "didn't find tx of required depth"); resolve(); }, 2000); 126 | }); 127 | 128 | await Promise.all([timeoutPromise, promise]); 129 | 130 | fetcher.stopWatching(); 131 | 132 | assert.equal(eventBlockNumber, currentBlockNumber - 2); 133 | }); 134 | 135 | it("can watch event with requiredDepth", async () => { 136 | 137 | const tokenAddress = await Utils.getGenTokenAddress(); 138 | assert.isOk(tokenAddress); 139 | const token = await StandardTokenFactory.at(tokenAddress); 140 | assert.isOk(token); 141 | 142 | const initialBlockNumber = await UtilsInternal.lastBlockNumber(); 143 | let currentBlockNumber = initialBlockNumber; 144 | let eventBlockNumber = currentBlockNumber; 145 | let done = false; 146 | 147 | const fetcher = token.Approval({ spender: accounts[0], owner: accounts[4] }, { fromBlock: initialBlockNumber }); 148 | 149 | const promise = new Promise(async ( 150 | resolve: () => void, 151 | reject: () => void): Promise => { 152 | fetcher.watch(async (error: Error, event: DecodedLogEntryEvent) => { 153 | currentBlockNumber = await UtilsInternal.lastBlockNumber(); 154 | eventBlockNumber = event.blockNumber; 155 | done = true; 156 | resolve(); 157 | }, 2); 158 | }); 159 | 160 | const amount = web3.toWei(1); 161 | const result = await token.approve({ 162 | amount, 163 | owner: accounts[4], 164 | spender: accounts[0], 165 | }); 166 | 167 | await result.getTxConfirmed(); 168 | 169 | await makeTransactions(2); 170 | 171 | const timeoutPromise = new Promise( 172 | async ( 173 | resolve: () => void, 174 | reject: () => void): Promise => { 175 | // give the watch two seconds to find the tx 176 | setTimeout(() => { assert(done, "didn't find tx of required depth"); resolve(); }, 2000); 177 | }); 178 | 179 | await Promise.all([timeoutPromise, promise]); 180 | 181 | fetcher.stopWatching(); 182 | 183 | assert.equal(eventBlockNumber, currentBlockNumber - 2); 184 | }); 185 | 186 | it("will wait for event with requiredDepth", async () => { 187 | 188 | const tokenAddress = await Utils.getGenTokenAddress(); 189 | assert.isOk(tokenAddress); 190 | const token = await StandardTokenFactory.at(tokenAddress); 191 | assert.isOk(token); 192 | 193 | let found = false; 194 | 195 | const fetcher = token.Approval({ spender: accounts[0], owner: accounts[4] }, { fromBlock: "latest" }); 196 | 197 | fetcher.watch(async (error: Error, event: DecodedLogEntryEvent) => { 198 | found = true; 199 | }, 4); 200 | 201 | const result = await token.approve({ 202 | amount: web3.toWei(1), 203 | owner: accounts[4], 204 | spender: accounts[0], 205 | }); 206 | 207 | await result.getTxConfirmed(); 208 | 209 | await makeTransactions(2); 210 | 211 | // give the watch three seconds to not find the tx 212 | await UtilsInternal.sleep(3000); 213 | 214 | /** 215 | * there is no way to shut down the fetcher if it is still watching for the transaction to 216 | * appear at the given depth -- there is an inner watch. So jump 217 | * through some hoops here. 218 | */ 219 | const wasFound = found; 220 | 221 | // enable the watch to shut down 222 | await makeTransactions(2); 223 | 224 | fetcher.stopWatching(); 225 | 226 | assert(!wasFound, "didn't wait for tx of required depth"); 227 | 228 | }); 229 | }); 230 | -------------------------------------------------------------------------------- /test/wrapperService.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import { 3 | IContractWrapper 4 | } from "../lib/iContractWrapperBase"; 5 | import { LoggingService, LogLevel } from "../lib/loggingService"; 6 | import { GenesisProtocolWrapper } from "../lib/wrappers/genesisProtocol"; 7 | import { UpgradeSchemeWrapper } from "../lib/wrappers/upgradeScheme"; 8 | import { 9 | ContractWrapperFactories, 10 | ContractWrappers, 11 | ContractWrappersByAddress, 12 | ContractWrappersByType 13 | } from "../lib/wrapperService"; 14 | import { WrapperService } from "../lib/wrapperService"; 15 | import { DefaultLogLevel, SOME_ADDRESS } from "./helpers"; 16 | 17 | describe("WrapperService", () => { 18 | 19 | it("can filter loading of contracts", async () => { 20 | 21 | // const savedWrappers = WrapperService.wrappers; 22 | 23 | // WrapperService.wrappers = {}; 24 | 25 | await WrapperService.initialize({ 26 | filter: { 27 | ContributionReward: true, 28 | GenesisProtocol: true, 29 | }, 30 | }); 31 | 32 | assert.equal(WrapperService.wrappers.GlobalConstraintRegistrar, null); 33 | assert.isOk(WrapperService.wrappers.GenesisProtocol); 34 | assert(WrapperService.wrappers.GenesisProtocol instanceof GenesisProtocolWrapper); 35 | 36 | await WrapperService.initialize(); 37 | }); 38 | 39 | it("Can enumerate wrappers", () => { 40 | for (const wrapperName in ContractWrappers) { 41 | if (ContractWrappers.hasOwnProperty(wrapperName)) { 42 | const wrapper = ContractWrappers[wrapperName]; 43 | assert.isOk(wrapper); 44 | assert(wrapper.name.length > 0); 45 | } 46 | } 47 | }); 48 | 49 | it("Can enumerate allWrappers", () => { 50 | ContractWrappersByType.allWrappers.forEach((wrapper: IContractWrapper) => { 51 | assert.isOk(wrapper); 52 | assert(wrapper.name.length > 0); 53 | }); 54 | }); 55 | 56 | it("can import quick-access types", async () => { 57 | assert.isOk(ContractWrappers); 58 | assert.isOk(ContractWrappers.UpgradeScheme); 59 | assert.isOk(ContractWrapperFactories); 60 | assert.isOk(ContractWrapperFactories.UpgradeScheme); 61 | assert.isOk(ContractWrappersByType); 62 | assert.isOk(ContractWrappersByType.universalSchemes); 63 | assert.isOk(ContractWrappersByType.nonUniversalSchemes); 64 | assert.isOk(ContractWrappersByAddress); 65 | assert.isOk(ContractWrappersByAddress.get(ContractWrappers.UpgradeScheme.address)); 66 | }); 67 | 68 | it("has a working getContractWrapper() function", async () => { 69 | const wrapper = await WrapperService.getContractWrapper("UpgradeScheme"); 70 | assert.isOk(wrapper); 71 | assert(wrapper instanceof UpgradeSchemeWrapper); 72 | }); 73 | 74 | it("getContractWrapper() function handles bad wrapper name", async () => { 75 | const wrapper = await WrapperService.getContractWrapper("NoSuchScheme"); 76 | assert.equal(wrapper, undefined); 77 | }); 78 | 79 | it("getContractWrapper() function handles bad address", async () => { 80 | LoggingService.logLevel = LogLevel.none; 81 | const wrapper = await WrapperService.getContractWrapper("UpgradeScheme", SOME_ADDRESS); 82 | LoggingService.logLevel = DefaultLogLevel; 83 | assert.equal(wrapper, undefined); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | const env = require("env-variable")(); 2 | /** 3 | * `truffle migrate` will lock and use this account. 4 | * 5 | * Must look like this: 6 | * { 7 | * "mnemonic" : "an account mnemonic", 8 | * "providerUrl" : "like http://127.0.0.1:8545 or https://[net].infura.io/[token]" 9 | * } 10 | */ 11 | let providerConfig; 12 | let provider; 13 | 14 | if (env.arcjs_providerConfig) { 15 | console.log(`providerConfig at: ${env.arcjs_providerConfig}`); 16 | providerConfig = require(env.arcjs_providerConfig); 17 | 18 | if (providerConfig) { 19 | const HDWalletProvider = require("truffle-hdwallet-provider"); 20 | console.log(`Provider: '${providerConfig.providerUrl}'`); 21 | console.log(`Account: '${providerConfig.mnemonic}'`); 22 | global.provider = provider = new HDWalletProvider(providerConfig.mnemonic, providerConfig.providerUrl); 23 | } 24 | } 25 | 26 | module.exports = { 27 | networks: { 28 | live: { 29 | provider: function () { 30 | return provider; 31 | }, 32 | gas: 4543760, 33 | gasPrice: 10000000000, 34 | network_id: 1 35 | }, 36 | ganache: { 37 | host: "127.0.0.1", 38 | port: 8545, 39 | network_id: "*", 40 | gas: 4543760 41 | }, 42 | ropsten: { 43 | provider: function () { 44 | return provider; 45 | }, 46 | gas: 4543760, 47 | network_id: 3 48 | }, 49 | kovan: { 50 | provider: function () { 51 | return provider; 52 | }, 53 | gas: 4543760, 54 | network_id: 42 55 | } 56 | }, 57 | rpc: { 58 | host: "127.0.0.1", 59 | port: 8545 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "preserveConstEnums": true, 7 | "sourceMap": true, 8 | "declaration": true, 9 | "importHelpers": true, 10 | "lib": [ 11 | "es2015", 12 | "es2017", 13 | "dom" 14 | ] 15 | }, 16 | "include": [ 17 | "lib/**/*", 18 | "custom_typings/web3.d.ts", 19 | "custom_typings/system.d.ts" 20 | ], 21 | "exclude": [ 22 | "node_modules", 23 | "dist" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "interface-name": [ 9 | false, 10 | "always-prefix" 11 | ], 12 | "max-classes-per-file": [ 13 | false, 14 | 1 15 | ], 16 | "indent": [ 17 | true, 18 | "spaces", 19 | 2 20 | ], 21 | "no-unused-expression": [ 22 | true, 23 | "allow-fast-null-checks" 24 | ], 25 | "trailing-comma": [ 26 | true, 27 | { 28 | "multiline": { 29 | "objects": "always", 30 | "arrays": "always", 31 | "functions": "never", 32 | "typeLiterals": "ignore" 33 | }, 34 | "singleline": "never", 35 | "esSpecCompliant": true 36 | } 37 | ], 38 | "array-type": [ 39 | true, 40 | "generic" 41 | ], 42 | "no-misused-new": false, 43 | "typedef": [ 44 | true, 45 | "call-signature", 46 | "arrow-call-signature", 47 | "parameter", 48 | "arrow-parameter", 49 | "property-declaration", 50 | "member-variable-declaration", 51 | "object-destructuring", 52 | "array-destructuring" 53 | ], 54 | "no-inferrable-types": false 55 | }, 56 | "rulesDirectory": [] 57 | } 58 | --------------------------------------------------------------------------------