├── package-scripts
├── fail.js
├── recursiveCopy.js
├── migrateContracts.js
├── unArchiveGanacheDb.js
├── createGenesisDao.js
├── createApiPagesList.js
├── cleanMigrationJson.js
├── archiveGanacheDb.js
└── typedoc.js
├── lib
├── iConfigService.ts
├── wrappers
│ ├── iBurnableToken.ts
│ ├── commonEventInterfaces.ts
│ ├── tokenCapGC.ts
│ ├── iErc827Token.ts
│ ├── lockingEth4Reputation.ts
│ ├── iIntVoteInterface.ts
│ ├── redeemer.ts
│ ├── lockingToken4Reputation.ts
│ ├── reputation.ts
│ ├── mintableToken.ts
│ ├── absoluteVote.ts
│ ├── externalLocking4Reputation.ts
│ └── voteInOrganizationScheme.ts
├── promiseEventService.ts
├── schemeWrapperBase.ts
├── test
│ └── wrappers
│ │ └── testWrapper.ts
├── proposalGeneratorBase.ts
├── configService.ts
├── utilsInternal.ts
├── uSchemeWrapperBase.ts
├── scripts
│ └── createGenesisDao.ts
├── commonTypes.ts
├── controllerService.ts
├── loggingService.ts
├── avatarService.ts
├── accountService.ts
├── contractWrapperFactory.ts
├── proposalService.ts
├── iContractWrapperBase.ts
└── pubSubEventService.ts
├── docs
├── index.html
├── README.md
├── Scripts.md
├── GanacheDb.md
├── DeveloperDocs.md
└── Wrappers.md
├── custom_typings
├── web3_global.d.ts
└── system.d.ts
├── .gitignore
├── test
├── daoCreator.ts
├── tsconfig.json
├── absoluteVote.ts
├── accountService.ts
├── config.ts
├── wrapperService.ts
├── estimateGas.ts
├── web3EventService.ts
├── voteInOrganizationScheme.ts
└── redeemer.ts
├── tsconfig.json
├── .travis.yml
├── config
└── default.json
├── tslint.json
├── README.md
├── truffle.js
├── CONTRIBUTING.md
├── package.json
└── package-scripts.js
/package-scripts/fail.js:
--------------------------------------------------------------------------------
1 | throw new Error(process.argv[2]);
2 |
--------------------------------------------------------------------------------
/lib/iConfigService.ts:
--------------------------------------------------------------------------------
1 | export interface IConfigService {
2 | get(setting: string): any;
3 | set(setting: string, value: any): void;
4 | }
5 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/daostack/arc.js)
2 | [](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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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