diff --git a/lib/utilsInternal.ts b/lib/utilsInternal.ts index b95256e00..6cedfd507 100644 --- a/lib/utilsInternal.ts +++ b/lib/utilsInternal.ts @@ -1,6 +1,6 @@ import { promisify } from "es6-promisify"; import { BlockWithoutTransactionData, FilterResult } from "web3"; -import { Address, fnVoid } from "./commonTypes"; +import { Address, fnVoid, Hash } from "./commonTypes"; import { Utils, Web3 } from "./utils"; /** @@ -65,6 +65,10 @@ export class UtilsInternal { return !address || !Number.parseInt(address, 16); } + public static isNullHash(hash: Hash): boolean { + return !hash || !Number.parseInt(hash, 16); + } + /** * Returns promise of the maximum gasLimit that we dare to ever use, given the * current state of the chain. diff --git a/lib/wrappers/genesisProtocol.ts b/lib/wrappers/genesisProtocol.ts index 36efc2937..81bea15b4 100644 --- a/lib/wrappers/genesisProtocol.ts +++ b/lib/wrappers/genesisProtocol.ts @@ -15,7 +15,8 @@ import { ArcTransactionResult, DecodedLogEntryEvent, IContractWrapperFactory, - IVotingMachineWrapper + IVotingMachineWrapper, + StandardSchemeParams, } from "../iContractWrapperBase"; import { ProposalService } from "../proposalService"; import { TransactionService, TxGeneratingFunctionOptions } from "../transactionService"; @@ -332,13 +333,71 @@ export class GenesisProtocolWrapper extends IntVoteInterfaceWrapper } /** - * Return the threshold that is required by a proposal to it shift it into boosted state. - * The computation depends on the current number of boosted proposals created for - * this voting machine by the DAO or other contract, as well as the GenesisProtocol - * parameters thresholdConstA and thresholdConstB. - * @param {GetThresholdConfig} options + * Return the threshold that is required for a proposal to be shifted into the boosted state. + * The computation depends on the current number of boosted proposals that were created for + * this voting machine by the scheme at `schemeInfo.address` for the given `avatarAddress` + * (`avatarAddress` is the `organizationAddress` given to the `propose` method), + * and the GenesisProtocol parameters `thresholdConstA` and `thresholdConstB` which + * are registered on behalf of the given scheme with an avatar's Controller, + * keyed by the given `avatarAddress`. + * + * Thus `avatarAddress` must be an avatar address and this function will not work if + * you pass for `avatarAddress` any other contract address than an avatar, even though + * GenesisProtocol is capable of using those non-avatar contract addresses. + * In those cases, when the organization is not an avatar, you must use `getThresholdFromProposal` + * to obtain the threshold. + * + * Note that there will be a separately-scoped threshold for proposals created for this + * voting machine by any other scheme than the one given. + * + * Note that for Arc.js schemes you can just pass an Arc.js contract wrapper for `schemeInfo`. + */ + public async getThresholdForSchemeAndCreator( + schemeInfo: ThresholdSchemeInfo, + avatarAddress: Address): Promise { + + if (!schemeInfo) { + throw new Error("schemeAddress is not defined"); + } + + if (!avatarAddress) { + throw new Error("avatarAddress is not defined"); + } + + const organizationId = this.organizationIdFromProposalCreator(schemeInfo.address, avatarAddress); + + const votingMachineInfo = await schemeInfo.getSchemeParameters(avatarAddress); + + if (!votingMachineInfo) { + throw new Error("votingMachineInfo is null, avatarAddress may not be an avatar"); + } + + if (votingMachineInfo.votingMachineAddress !== this.address) { + throw new Error("the scheme is not using this voting machine"); + } + + const votingMachineParamsHash = votingMachineInfo.voteParametersHash; + + if (UtilsInternal.isNullHash(votingMachineParamsHash)) { + throw new Error("avatarAddress does not yield a parameters hash, may not be an avatar"); + } + + this.logContractFunctionCall("GenesisProtocol.threshold", + { schemeInfo, avatarAddress, votingMachineParamsHash, organizationId }); + + return this.contract.threshold(votingMachineParamsHash, organizationId); + } + /** + * Return the threshold that is required for a proposal to be shifted into the boosted state. + * The computation depends on the current number of boosted proposals that were created for + * this voting machine by the scheme that created the given proposal, + * and the GenesisProtocol parameters `thresholdConstA` and `thresholdConstB` which + * are stored with the proposal. + * + * Note that there will be a separately-scoped threshold for proposals created for this + * voting machine by any other scheme than the one that created the given proposal. */ - public async getThreshold(proposalId: Hash): Promise { + public async getThresholdFromProposal(proposalId: Hash): Promise { if (!proposalId) { throw new Error("proposalId is not defined"); @@ -373,7 +432,8 @@ export class GenesisProtocolWrapper extends IntVoteInterfaceWrapper const organizationId = this.organizationIdFromProposalCreator(schemeAddress, creatorAddress); - this.logContractFunctionCall("GenesisProtocol.getBoostedProposalsCount", creatorAddress); + this.logContractFunctionCall("GenesisProtocol.getBoostedProposalsCount", + { schemeAddress, creatorAddress, organizationId }); return this.contract.getBoostedProposalsCount(organizationId); } @@ -1352,6 +1412,12 @@ export interface ExecutedGenesisProposal extends GenesisProtocolProposal { } export interface GenesisProtocolProposal { + /** + * This is what will appear as `_organization` in voting machine events. + * It is the value passed to `propose` as the optional argument: `organizationAddress`. + * If this argument is set to zero in `propose` then the value will be the msg.sender. + * With proposals created by Arc universal schemes (like GenesisProtocol), this is the avatar address. + */ creatorAddress: Address; /** * in seconds @@ -1420,3 +1486,20 @@ export interface GetNumberOfChoicesConfig { */ proposalId: string; } + +/** + * Input to `GenesisProtocolWrapper.getThresholdForSchemeAndCreator`. Provides info about a + * proposal-creating scheme that is generating proposals using GenesisProtocol. + */ +export interface ThresholdSchemeInfo { + /** + * address of the scheme + */ + address: Address; + /** + * Returns promise of a `StandardSchemeParams` as the referenced scheme's parameters, as registered + * with the given avatar address. + * @param avatarAddress + */ + getSchemeParameters(avatarAddress: Address): Promise; +} diff --git a/mkdocs.yml b/mkdocs.yml index 79e2e30d9..b40cf5b76 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -286,6 +286,7 @@ nav: - StandardTokenChangeApprovalOptions : 'api/interfaces/StandardTokenChangeApprovalOptions.md' - StandardTokenTransferFromOptions : 'api/interfaces/StandardTokenTransferFromOptions.md' - StandardTokenTransferOptions : 'api/interfaces/StandardTokenTransferOptions.md' + - ThresholdSchemeInfo : 'api/interfaces/ThresholdSchemeInfo.md' - TokenCapGcParams : 'api/interfaces/TokenCapGcParams.md' - TransactionReceiptsEventInfo : 'api/interfaces/TransactionReceiptsEventInfo.md' - TransactionReceiptTruffle : 'api/interfaces/TransactionReceiptTruffle.md' diff --git a/package.json b/package.json index 5d4e3a65e..346501e2b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@daostack/arc.js", - "version": "0.0.0-alpha.87", + "version": "0.0.0-alpha.88", "description": "A JavaScript library for interacting with @daostack/arc ethereum smart contracts", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/test/genesisProtocol.ts b/test/genesisProtocol.ts index e147f0f00..1a0939855 100644 --- a/test/genesisProtocol.ts +++ b/test/genesisProtocol.ts @@ -30,7 +30,7 @@ describe("GenesisProtocol", () => { let contributionReward: ContributionRewardWrapper; const sufficientStake = async (proposalId: Hash, amount: number): Promise => { - return (await genesisProtocol.getThreshold(proposalId)).add(web3.toWei(amount)); + return (await genesisProtocol.getThresholdFromProposal(proposalId)).add(web3.toWei(amount)); }; const createProposal = async (): Promise => { @@ -542,9 +542,16 @@ describe("GenesisProtocol", () => { assert(typeof result !== "undefined"); }); - it("can call getThreshold", async () => { + it("can call getThresholdFromProposal", async () => { const proposalId = await createProposal(); - const result = await genesisProtocol.getThreshold(proposalId); + const result = await genesisProtocol.getThresholdFromProposal(proposalId); + assert(typeof result !== "undefined"); + }); + + it("can call getThresholdForSchemeAndCreator", async () => { + const result = await genesisProtocol.getThresholdForSchemeAndCreator( + contributionReward, + dao.avatar.address); assert(typeof result !== "undefined"); }); diff --git a/test/redeemer.ts b/test/redeemer.ts index d59cdc56d..b9fbb2666 100644 --- a/test/redeemer.ts +++ b/test/redeemer.ts @@ -76,7 +76,7 @@ describe("Redeemer", () => { await helpers.vote(result.votingMachine, proposalId, BinaryVoteResult.No, accounts[2]); await helpers.vote(result.votingMachine, proposalId, BinaryVoteResult.Yes, accounts[3]); - const stakeAmount = (await gp.getThreshold(proposalId)).add(web3.toWei(10)); + const stakeAmount = (await gp.getThresholdFromProposal(proposalId)).add(web3.toWei(10)); await (await gp.stakeWithApproval({ amount: stakeAmount, vote: 1, proposalId })).getTxMined();