diff --git a/lib/contractWrapperBase.ts b/lib/contractWrapperBase.ts index 5e6d5bb72..93e6d4fd8 100644 --- a/lib/contractWrapperBase.ts +++ b/lib/contractWrapperBase.ts @@ -1,6 +1,6 @@ import { BigNumber } from "bignumber.js"; import { promisify } from "es6-promisify"; -import { Address, Hash, SchemePermissions } from "./commonTypes"; +import { Address, DefaultSchemePermissions, Hash, SchemePermissions } from "./commonTypes"; import { ConfigService } from "./configService"; import { ControllerService } from "./controllerService"; import { @@ -193,6 +193,20 @@ export abstract class ContractWrapperBase implements IContractWrapper { return Math.max(Math.min((await func.estimateGas(...params)), maxGasLimit), 21000); } + /** + * TODO: getDefaultPermissions should be moved to a new subclass `SchemeWrapper` + * which itself would be a base class for a new class `UniversalScheme` + */ + + /** + * Any scheme that needs greater permissions should override this + * @hidden - for internal use only. + * This method will eventually be moved (see comment above) + */ + public getDefaultPermissions(): SchemePermissions { + return DefaultSchemePermissions.MinimumPermissions as number; + } + /** * invoked to let base classes know that the `contract` is available. */ @@ -325,7 +339,7 @@ export abstract class ContractWrapperBase implements IContractWrapper { } if (ConfigService.get("estimateGas") && !web3Params.gas) { - await this.estimateGas(func, params) + await this.estimateGas(func, params, web3Params) .then((gas: number) => { // side-effect of altering web3Params allows caller to know what we used Object.assign(web3Params, { gas }); diff --git a/lib/contractWrapperFactory.ts b/lib/contractWrapperFactory.ts index 56c68d0dc..c91b9719c 100644 --- a/lib/contractWrapperFactory.ts +++ b/lib/contractWrapperFactory.ts @@ -98,6 +98,13 @@ export class ContractWrapperFactory return this.getHydratedWrapper(getWrapper); } + public async ensureSolidityContract(): Promise { + if (!this.solidityContract) { + this.solidityContract = await Utils.requireContract(this.solidityContractName); + } + return this.solidityContract; + } + protected async estimateConstructorGas(...params: Array): Promise { const web3 = await Utils.getWeb3(); await this.ensureSolidityContract(); @@ -177,10 +184,4 @@ export class ContractWrapperFactory addressMap.set(wrapper.address, wrapper); } } - - private async ensureSolidityContract(): Promise { - if (!this.solidityContract) { - this.solidityContract = await Utils.requireContract(this.solidityContractName); - } - } } diff --git a/lib/controllerService.ts b/lib/controllerService.ts index e0373bf02..78c918108 100644 --- a/lib/controllerService.ts +++ b/lib/controllerService.ts @@ -59,13 +59,16 @@ export class ControllerService { * TODO: check for previous and future versions of UController here */ const UControllerContract = await Utils.requireContract("UController"); - const ControllerContract = await Utils.requireContract("Controller"); const uControllerAddress = (await UControllerContract.deployed()).address; this.isUController = uControllerAddress === controllerAddress; - this.controller = this.isUController ? - await UControllerContract.at(controllerAddress) : - await ControllerContract.at(controllerAddress); + + if (this.isUController) { + this.controller = await UControllerContract.at(controllerAddress); + } else { + const ControllerContract = await Utils.requireContract("Controller"); + this.controller = await ControllerContract.at(controllerAddress); + } } } return this.controller; diff --git a/lib/iContractWrapperBase.ts b/lib/iContractWrapperBase.ts index c93cc4927..178b3e70a 100644 --- a/lib/iContractWrapperBase.ts +++ b/lib/iContractWrapperBase.ts @@ -50,6 +50,7 @@ export interface IContractWrapperFactory { new: (...rest: Array) => Promise; at: (address: string) => Promise; deployed: () => Promise; + ensureSolidityContract(): Promise; } export class ArcTransactionResult { diff --git a/lib/index.ts b/lib/index.ts index 2c7154c72..bc13f743b 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -17,6 +17,12 @@ export * from "./wrappers/commonEventInterfaces"; export * from "./wrappers/contributionReward"; export * from "./wrappers/daoCreator"; export * from "./wrappers/daoToken"; +export * from "./wrappers/locking4Reputation"; +export * from "./wrappers/lockingEth4Reputation"; +export * from "./wrappers/lockingToken4Reputation"; +export * from "./wrappers/fixedReputationAllocation"; +export * from "./wrappers/externalLocking4Reputation"; +export * from "./wrappers/auction4Reputation"; export * from "./wrappers/genesisProtocol"; export * from "./wrappers/globalConstraintRegistrar"; export * from "./wrappers/iBurnableToken"; diff --git a/lib/proposalService.ts b/lib/proposalService.ts index 70cf5350f..0c0efc68d 100644 --- a/lib/proposalService.ts +++ b/lib/proposalService.ts @@ -98,7 +98,7 @@ export class ProposalService { (event: DecodedLogEntryEvent): Promise => { return Promise.resolve( { - avatarAddress: event.args._avatar, + avatarAddress: event.args._organization, numOfChoices: event.args._numOfChoices.toNumber(), paramsHash: event.args._paramsHash, proposalId: event.args._proposalId, diff --git a/lib/transactionService.ts b/lib/transactionService.ts index cd415d638..d44968546 100644 --- a/lib/transactionService.ts +++ b/lib/transactionService.ts @@ -281,7 +281,7 @@ export class TransactionService extends PubSubEventService { public static async getTransactionDepth(tx: Hash | TransactionReceipt): Promise { const web3 = await Utils.getWeb3(); - const lastBlockNum = await UtilsInternal.lastBlock(); + const lastBlockNum = await UtilsInternal.lastBlockNumber(); let receipt: TransactionReceipt; if (typeof tx === "string") { diff --git a/lib/utils.ts b/lib/utils.ts index cd3d1c17d..6b011f3bf 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -15,6 +15,7 @@ export class Utils { static get NULL_ADDRESS(): Address { return "0x0000000000000000000000000000000000000000"; } static get NULL_HASH(): Hash { return "0x0000000000000000000000000000000000000000000000000000000000000000"; } + public static contractCache: Map = new Map(); /** * Returns Truffle contract wrapper given the name of the contract (like "SchemeRegistrar"). * Optimized for synchronicity issues encountered with MetaMask. @@ -24,8 +25,14 @@ export class Utils { */ public static async requireContract(contractName: string): Promise { try { + let contract = Utils.contractCache.get(contractName); + if (contract) { + LoggingService.debug(`requireContract: loaded from cache ${contractName}`); + return contract; + } + const artifact = require(`../migrated_contracts/${contractName}.json`); - const contract = new Contract(artifact); + contract = new Contract(artifact); const myWeb3 = await Utils.getWeb3(); contract.setProvider(myWeb3.currentProvider); @@ -34,6 +41,7 @@ export class Utils { from: await Utils.getDefaultAccount(), gas: ConfigService.get("defaultGasLimit"), }); + Utils.contractCache.set(contractName, contract); LoggingService.debug(`requireContract: loaded ${contractName}`); return contract; } catch (ex) { diff --git a/lib/utilsInternal.ts b/lib/utilsInternal.ts index 9710fe4e8..b95256e00 100644 --- a/lib/utilsInternal.ts +++ b/lib/utilsInternal.ts @@ -1,6 +1,6 @@ import { promisify } from "es6-promisify"; -import { FilterResult } from "web3"; -import { fnVoid } from "./commonTypes"; +import { BlockWithoutTransactionData, FilterResult } from "web3"; +import { Address, fnVoid } from "./commonTypes"; import { Utils, Web3 } from "./utils"; /** @@ -22,7 +22,25 @@ export class UtilsInternal { /** * Returns the last mined block in the chain. */ - public static async lastBlock(): Promise { + public static async lastBlock(): Promise { + const web3 = await Utils.getWeb3(); + return promisify((callback: any): any => web3.eth.getBlock("latest", callback))() as any; + } + + /** + * Returns the date of the last mined block in the chain. + */ + public static async lastBlockDate(): Promise { + const web3 = await Utils.getWeb3(); + const block = await promisify((callback: any): any => + web3.eth.getBlock("latest", callback))() as BlockWithoutTransactionData; + return new Date(block.timestamp * 1000); + } + + /** + * Returns the last mined block in the chain. + */ + public static async lastBlockNumber(): Promise { const web3 = await Utils.getWeb3(); return promisify(web3.eth.getBlockNumber)(); } @@ -43,6 +61,10 @@ export class UtilsInternal { return (Utils as any).web3; } + public static isNullAddress(address: Address): boolean { + return !address || !Number.parseInt(address, 16); + } + /** * Returns promise of the maximum gasLimit that we dare to ever use, given the * current state of the chain. diff --git a/lib/web3EventService.ts b/lib/web3EventService.ts index 0676495de..038bed40e 100644 --- a/lib/web3EventService.ts +++ b/lib/web3EventService.ts @@ -183,10 +183,10 @@ export class Web3EventService { // handler that takes the events and issues givenCallback appropriately const handleEvent = - (error: Error, - log: DecodedLogEntryEvent | Array>, + async (error: Error, + log: DecodedLogEntryEvent | Array>, // singly true to issue callback on every arg rather than on the array - singly: boolean, + singly: boolean, /* * invoke this callback on every event (watch) * or on the array of events (get), depending on the value of singly. @@ -195,7 +195,7 @@ export class Web3EventService { * when not singly, callback gets a promise of the array of entities. * get is not singly. so get gets a promise of an array. */ - callback?: (error: Error, args: TEntity | Promise>) => void): + callback?: (error: Error, args: TEntity | Promise>) => void): Promise> => { const promiseOfEntities: Promise> = @@ -217,7 +217,7 @@ export class Web3EventService { const transformedEntity = await transformEventCallback(event); if (typeof transformedEntity !== "undefined") { if (callback && singly) { - callback(error, transformedEntity); + await callback(error, transformedEntity); } entities.push(transformedEntity); } @@ -226,7 +226,7 @@ export class Web3EventService { }); // invoke the given callback with the promise of an array of entities if (callback && !singly) { - callback(error, promiseOfEntities); + await callback(error, promiseOfEntities); } return promiseOfEntities; }; diff --git a/lib/wrapperService.ts b/lib/wrapperService.ts index f8685f738..fd0d260ca 100644 --- a/lib/wrapperService.ts +++ b/lib/wrapperService.ts @@ -7,6 +7,10 @@ import { AbsoluteVoteFactory, AbsoluteVoteWrapper } from "./wrappers/absoluteVote"; +import { + Auction4ReputationFactory, + Auction4ReputationWrapper +} from "./wrappers/auction4Reputation"; import { ContributionRewardFactory, ContributionRewardWrapper @@ -19,6 +23,14 @@ import { DaoTokenFactory, DaoTokenWrapper } from "./wrappers/daoToken"; +import { + ExternalLocking4ReputationFactory, + ExternalLocking4ReputationWrapper +} from "./wrappers/externalLocking4Reputation"; +import { + FixedReputationAllocationFactory, + FixedReputationAllocationWrapper +} from "./wrappers/fixedReputationAllocation"; import { GenesisProtocolFactory, GenesisProtocolWrapper @@ -31,6 +43,14 @@ import { IntVoteInterfaceFactory, IntVoteInterfaceWrapper } from "./wrappers/intVoteInterface"; +import { + LockingEth4ReputationFactory, + LockingEth4ReputationWrapper +} from "./wrappers/lockingEth4Reputation"; +import { + LockingToken4ReputationFactory, + LockingToken4ReputationWrapper +} from "./wrappers/lockingToken4Reputation"; import { MintableTokenFactory, MintableTokenWrapper @@ -68,6 +88,8 @@ import { VoteInOrganizationSchemeWrapper } from "./wrappers/voteInOrganizationScheme"; +/* tslint:disable:max-line-length */ + /** * An object with property names being a contract key and property value as the * corresponding wrapper factory (IContractWrapperFactory; GlobalConstraintRegistrar: IContractWrapperFactory; IntVoteInterface: IContractWrapperFactory; + LockingEth4Reputation: IContractWrapperFactory; + LockingToken4Reputation: IContractWrapperFactory; + Auction4Reputation: IContractWrapperFactory; + FixedReputationAllocation: IContractWrapperFactory; + ExternalLocking4Reputation: IContractWrapperFactory; MintableToken: IContractWrapperFactory; Redeemer: IContractWrapperFactory; Reputation: IContractWrapperFactory; @@ -92,9 +119,9 @@ export interface ArcWrapperFactories { } /** - * An object with property names being a contract key and property value as the + * An object with property names being an Arc contract name and then property value being the * corresponding wrapper. Only deployed wrappers are included here. Other wrappers - * may be obtained via their factory. + * may be obtained via their factory. See also ArcSchemeWrappers. */ export interface ArcWrappers { AbsoluteVote: AbsoluteVoteWrapper; @@ -110,6 +137,39 @@ export interface ArcWrappers { VoteInOrganizationScheme: VoteInOrganizationSchemeWrapper; } +/** + * An object with property names being an Arc contract name and the property value being the + * corresponding contract non-universal contract wrapper factory. + */ +export interface ArcNonUniversalSchemeWrapperFactories { + LockingEth4Reputation: IContractWrapperFactory; + LockingToken4Reputation: IContractWrapperFactory; + Auction4Reputation: IContractWrapperFactory; + FixedReputationAllocation: IContractWrapperFactory; + ExternalLocking4Reputation: IContractWrapperFactory; +} + +/** + * An object with property names being an Arc contract name and the property value being the + * corresponding universal contract wrapper factory. + */ +export interface ArcUniversalSchemeWrapperFactories { + ContributionReward: IContractWrapperFactory; + GlobalConstraintRegistrar: IContractWrapperFactory; + SchemeRegistrar: IContractWrapperFactory; + UpgradeScheme: IContractWrapperFactory; + VestingScheme: IContractWrapperFactory; + VoteInOrganizationScheme: IContractWrapperFactory; +} + +/** + * An object with property names being an Arc contract name and the property value being the + * corresponding contract wrapper factory. Includes both universal and non-universal factories. + */ +export interface ArcSchemeWrapperFactories + extends ArcNonUniversalSchemeWrapperFactories, ArcUniversalSchemeWrapperFactories { +} + /** * Arc.js wrapper factories grouped by type. */ @@ -159,6 +219,18 @@ export class WrapperService { * `wrappers` and `wrappersByType` properties. */ public static factories: ArcWrapperFactories = {} as ArcWrapperFactories; + /** + * Universal scheme contract wrapper factories by name. + */ + public static universalSchemeFactories: ArcUniversalSchemeWrapperFactories = {} as ArcUniversalSchemeWrapperFactories; + /** + * Non-universal scheme contract wrapper factories by name. + */ + public static nonUniversalSchemeFactories: ArcNonUniversalSchemeWrapperFactories = {} as ArcNonUniversalSchemeWrapperFactories; + /** + * All scheme contract wrapper factories by name. + */ + public static schemeFactories: ArcSchemeWrapperFactories = {} as ArcSchemeWrapperFactories; /** * Map of contract wrappers keyed by address. For example: @@ -185,7 +257,6 @@ export class WrapperService { Object.assign({}, WrapperService.noWrappersFilter, options.filter) : WrapperService.allWrappersFilter; - /* tslint:disable:max-line-length */ WrapperService.wrappers.AbsoluteVote = filter.AbsoluteVote ? await AbsoluteVoteFactory.deployed() : null; WrapperService.wrappers.ContributionReward = filter.ContributionReward ? await ContributionRewardFactory.deployed() : null; WrapperService.wrappers.DaoCreator = filter.DaoCreator ? await DaoCreatorFactory.deployed() : null; @@ -197,7 +268,6 @@ export class WrapperService { WrapperService.wrappers.UpgradeScheme = filter.UpgradeScheme ? await UpgradeSchemeFactory.deployed() : null; WrapperService.wrappers.VestingScheme = filter.VestingScheme ? await VestingSchemeFactory.deployed() : null; WrapperService.wrappers.VoteInOrganizationScheme = filter.VoteInOrganizationScheme ? await VoteInOrganizationSchemeFactory.deployed() : null; - /* tslint:enable:max-line-length */ /** * Contract wrappers grouped by type @@ -232,28 +302,42 @@ export class WrapperService { * unless we initialize it this way (otherwise it shows up in the "Object Literal" section). */ WrapperService.factories.AbsoluteVote = AbsoluteVoteFactory as IContractWrapperFactory; - WrapperService.factories.ContributionReward = ContributionRewardFactory as - IContractWrapperFactory; + WrapperService.factories.ContributionReward = ContributionRewardFactory as IContractWrapperFactory; WrapperService.factories.DaoCreator = DaoCreatorFactory as IContractWrapperFactory; WrapperService.factories.DaoToken = DaoTokenFactory as IContractWrapperFactory; - WrapperService.factories.GenesisProtocol = - GenesisProtocolFactory as IContractWrapperFactory; - WrapperService.factories.GlobalConstraintRegistrar = GlobalConstraintRegistrarFactory as - IContractWrapperFactory; - WrapperService.factories.IntVoteInterface = IntVoteInterfaceFactory as - IContractWrapperFactory; + WrapperService.factories.GenesisProtocol = GenesisProtocolFactory as IContractWrapperFactory; + WrapperService.factories.GlobalConstraintRegistrar = GlobalConstraintRegistrarFactory as IContractWrapperFactory; + WrapperService.factories.IntVoteInterface = IntVoteInterfaceFactory as IContractWrapperFactory; + WrapperService.factories.LockingEth4Reputation = LockingEth4ReputationFactory as IContractWrapperFactory; + WrapperService.factories.LockingToken4Reputation = LockingToken4ReputationFactory as IContractWrapperFactory; + WrapperService.factories.Auction4Reputation = Auction4ReputationFactory as IContractWrapperFactory; + WrapperService.factories.FixedReputationAllocation = FixedReputationAllocationFactory as IContractWrapperFactory; + WrapperService.factories.ExternalLocking4Reputation = ExternalLocking4ReputationFactory as IContractWrapperFactory; WrapperService.factories.MintableToken = MintableTokenFactory as IContractWrapperFactory; - WrapperService.factories.Redeemer = - RedeemerFactory as IContractWrapperFactory; + WrapperService.factories.Redeemer = RedeemerFactory as IContractWrapperFactory; WrapperService.factories.Reputation = ReputationFactory as IContractWrapperFactory; - WrapperService.factories.SchemeRegistrar = - SchemeRegistrarFactory as IContractWrapperFactory; + WrapperService.factories.SchemeRegistrar = SchemeRegistrarFactory as IContractWrapperFactory; WrapperService.factories.StandardToken = StandardTokenFactory as IContractWrapperFactory; WrapperService.factories.TokenCapGC = TokenCapGCFactory as IContractWrapperFactory; WrapperService.factories.UpgradeScheme = UpgradeSchemeFactory as IContractWrapperFactory; WrapperService.factories.VestingScheme = VestingSchemeFactory as IContractWrapperFactory; - WrapperService.factories.VoteInOrganizationScheme = VoteInOrganizationSchemeFactory as - IContractWrapperFactory; + WrapperService.factories.VoteInOrganizationScheme = VoteInOrganizationSchemeFactory as IContractWrapperFactory; + + WrapperService.nonUniversalSchemeFactories.LockingEth4Reputation = WrapperService.factories.LockingEth4Reputation; + WrapperService.nonUniversalSchemeFactories.LockingToken4Reputation = WrapperService.factories.LockingToken4Reputation; + WrapperService.nonUniversalSchemeFactories.Auction4Reputation = WrapperService.factories.Auction4Reputation; + WrapperService.nonUniversalSchemeFactories.FixedReputationAllocation = WrapperService.factories.FixedReputationAllocation; + WrapperService.nonUniversalSchemeFactories.ExternalLocking4Reputation = WrapperService.factories.ExternalLocking4Reputation; + + WrapperService.universalSchemeFactories.ContributionReward = WrapperService.factories.ContributionReward; + WrapperService.universalSchemeFactories.GlobalConstraintRegistrar = WrapperService.factories.GlobalConstraintRegistrar; + WrapperService.universalSchemeFactories.SchemeRegistrar = WrapperService.factories.SchemeRegistrar; + WrapperService.universalSchemeFactories.UpgradeScheme = WrapperService.factories.UpgradeScheme; + WrapperService.universalSchemeFactories.VestingScheme = WrapperService.factories.VestingScheme; + WrapperService.universalSchemeFactories.VoteInOrganizationScheme = WrapperService.factories.VoteInOrganizationScheme; + + WrapperService.schemeFactories = Object.assign({}, WrapperService.nonUniversalSchemeFactories, WrapperService.universalSchemeFactories); + /** * TODO: this should be made aware of previously-deployed GCs */ @@ -381,7 +465,12 @@ export const ContractWrapperFactories: ArcWrapperFactories = WrapperService.fact * for quicker access to the contract wrapper types */ export const ContractWrappersByType: ArcWrappersByType = WrapperService.wrappersByType; + /** * for quicker access to the contract wrappers by address */ export const ContractWrappersByAddress: Map = WrapperService.wrappersByAddress; + +export const UniversalSchemeFactories: ArcUniversalSchemeWrapperFactories = WrapperService.universalSchemeFactories; +export const NonUniversalSchemeFactories: ArcNonUniversalSchemeWrapperFactories = WrapperService.nonUniversalSchemeFactories; +export const SchemeFactories: ArcSchemeWrapperFactories = WrapperService.schemeFactories; diff --git a/lib/wrappers/absoluteVote.ts b/lib/wrappers/absoluteVote.ts index 2d711c01f..45a4247cc 100644 --- a/lib/wrappers/absoluteVote.ts +++ b/lib/wrappers/absoluteVote.ts @@ -51,7 +51,7 @@ export class AbsoluteVoteWrapper extends IntVoteInterfaceWrapper proposalsEventFetcher: this.NewProposal, transformEventCallback: async (event: DecodedLogEntryEvent): Promise => { return { - avatarAddress: event.args._avatar, + avatarAddress: event.args._organization, numOfChoices: event.args._numOfChoices.toNumber(), paramsHash: event.args._paramsHash, proposalId: event.args._proposalId, @@ -187,7 +187,7 @@ export interface RefreshReputationEventResult { /** * indexed */ - _avatar: Address; + _organization: Address; /** * indexed */ diff --git a/lib/wrappers/auction4Reputation.ts b/lib/wrappers/auction4Reputation.ts new file mode 100644 index 000000000..7dc422ad5 --- /dev/null +++ b/lib/wrappers/auction4Reputation.ts @@ -0,0 +1,401 @@ +"use strict"; +import BigNumber from "bignumber.js"; +import { Address, Hash } from "../commonTypes"; +import { ContractWrapperBase } from "../contractWrapperBase"; +import { ContractWrapperFactory } from "../contractWrapperFactory"; +import { ArcTransactionResult, IContractWrapperFactory } from "../iContractWrapperBase"; +import { TxGeneratingFunctionOptions } from "../transactionService"; +import { UtilsInternal } from "../utilsInternal"; +import { EventFetcherFactory, Web3EventService } from "../web3EventService"; +import { WrapperService } from "../wrapperService"; +import { StandardTokenWrapper } from "./standardToken"; + +export class Auction4ReputationWrapper extends ContractWrapperBase { + public name: string = "Auction4Reputation"; + public friendlyName: string = "Auction For Reputation"; + public factory: IContractWrapperFactory = Auction4ReputationFactory; + + public Bid: EventFetcherFactory; + public Redeem: EventFetcherFactory; + + public async initialize(options: Auction4ReputationInitializeOptions): Promise { + + const avatar = await this.getAvatar(); + + if (!UtilsInternal.isNullAddress(avatar)) { + throw new Error("you can only call initialize once"); + } + + if (!options.avatarAddress) { + throw new Error("avatarAddress is not defined"); + } + + if (!options.tokenAddress) { + throw new Error("tokenAddress is not defined"); + } + + if (!options.walletAddress) { + throw new Error("walletAddress is not defined"); + } + + if (!options.auctionsEndTime) { + throw new Error("auctionsEndTime is not defined"); + } + + if (!options.auctionsStartTime) { + throw new Error("auctionsStartTime is not defined"); + } + + if (options.auctionsEndTime <= options.auctionsStartTime) { + throw new Error("auctionsEndTime must be greater than auctionsStartTime"); + } + + if (!Number.isInteger(options.numberOfAuctions)) { + throw new Error("numberOfAuctions is not an integer"); + } + + if (options.numberOfAuctions <= 0) { + throw new Error("maxLockingPeriod must be greater than zero"); + } + + if (!options.reputationReward) { + throw new Error("reputationReward is not defined"); + } + + if (!options.redeemEnableTime) { + throw new Error("redeemEnableTime is not defined"); + } + + const reputationReward = new BigNumber(options.reputationReward); + + if (reputationReward.lte(0)) { + throw new Error("reputationReward must be greater than zero"); + } + + this.logContractFunctionCall("Auction4Reputation.initialize", options); + + return this.wrapTransactionInvocation("Auction4Reputation.initialize", + options, + this.contract.initialize, + [ + options.avatarAddress, + options.reputationReward, + options.auctionsStartTime.getTime() / 1000, + options.auctionsEndTime.getTime() / 1000, + options.numberOfAuctions, + options.redeemEnableTime.getTime() / 1000, + options.tokenAddress, + options.walletAddress, + ] + ); + } + + public async redeem(options: Auction4ReputationRedeemOptions & TxGeneratingFunctionOptions) + : Promise { + if (!options.beneficiaryAddress) { + throw new Error("beneficiaryAddress is not defined"); + } + + if (!Number.isInteger(options.auctionId)) { + throw new Error("auctionId is not an integer"); + } + + if (options.auctionId < 0) { + throw new Error("auctionId must be greater than or equal to zero"); + } + + const redeemEnableTime = await this.getRedeemEnableTime(); + const now = await UtilsInternal.lastBlockDate(); + + if (now <= redeemEnableTime) { + throw new Error(`nothing can be redeemed until after ${redeemEnableTime}`); + } + + const endTime = await this.getAuctionsEndTime(); + + if (now <= endTime) { + throw new Error("the auction period has not passed"); + } + + this.logContractFunctionCall("Auction4Reputation.redeem", options); + + return this.wrapTransactionInvocation("Auction4Reputation.redeem", + options, + this.contract.redeem, + [options.beneficiaryAddress, options.auctionId] + ); + } + + /** + * Returns reason why can't bid, else null if can bid + */ + public async getBidBlocker(options: Auction4ReputationBidOptions): Promise { + const amount = new BigNumber(options.amount); + + if (amount.lte(0)) { + return "amount must be greater than zero"; + } + + const startTime = await this.getAuctionsStartTime(); + const endTime = await this.getAuctionsEndTime(); + const now = await UtilsInternal.lastBlockDate(); + + if ((now > endTime) || (now < startTime)) { + return "bidding is not within the allowed bidding period"; + } + return null; + } + + /** + * Bid on behalf of the current account + * @param options + */ + public async bid(options: Auction4ReputationBidOptions & TxGeneratingFunctionOptions) + : Promise { + + const msg = await this.getBidBlocker(options); + if (msg) { + throw new Error(msg); + } + + this.logContractFunctionCall("Auction4Reputation.bid", options); + + return this.wrapTransactionInvocation("Auction4Reputation.bid", + options, + this.contract.bid, + [options.amount] + ); + } + + /** + * transfer bidded tokens to the wallet + * @param options + */ + public async transferToWallet(options: TxGeneratingFunctionOptions = {}) + : Promise { + + const endTime = await this.getAuctionsEndTime(); + const now = await UtilsInternal.lastBlockDate(); + + if (now <= endTime) { + throw new Error("the bidding period has not yet expired"); + } + + this.logContractFunctionCall("Auction4Reputation.transferToWallet", options); + + return this.wrapTransactionInvocation("Auction4Reputation.transferToWallet", + options, + this.contract.transferToWallet, + [] + ); + } + + /** + * get promise of the amount bid on the given auction. + * @param bidderAddress + * @param auctionId + */ + public getBid(bidderAddress: Address, auctionId: number): Promise { + + if (!bidderAddress) { + throw new Error("bidderAddress is not defined"); + } + + this.validateAuctionId(auctionId); + + this.logContractFunctionCall("Auction4Reputation.getBid", { bidderAddress, auctionId }); + return this.contract.getBid(bidderAddress, auctionId); + } + + /** + * Get a promise of the first date/time when anything can be redeemed + */ + public async getRedeemEnableTime(): Promise { + this.logContractFunctionCall("Auction4Reputation.redeemEnableTime"); + const seconds = await this.contract.redeemEnableTime(); + return new Date(seconds.toNumber() * 1000); + } + /** + * Get a promise of the total reputation rewardable across all the auctions + */ + public async getReputationReward(): Promise { + this.logContractFunctionCall("Auction4Reputation.reputationReward"); + const auctionReputationReward = await this.contract.auctionReputationReward(); + const numAuctions = await this.getNumberOfAuctions(); + return auctionReputationReward.mul(numAuctions); + } + public getReputationRewardLeft(): Promise { + this.logContractFunctionCall("Auction4Reputation.reputationRewardLeft"); + return this.contract.reputationRewardLeft(); + } + public async getAuctionsEndTime(): Promise { + this.logContractFunctionCall("Auction4Reputation.auctionsEndTime"); + const dt = await this.contract.auctionsEndTime(); + return new Date(dt.toNumber() * 1000); + } + public async getAuctionsStartTime(): Promise { + this.logContractFunctionCall("Auction4Reputation.auctionsStartTime"); + const dt = await this.contract.auctionsStartTime(); + return new Date(dt.toNumber() * 1000); + } + public async getNumberOfAuctions(): Promise { + this.logContractFunctionCall("Auction4Reputation.numberOfAuctions"); + return (await this.contract.numberOfAuctions()).toNumber(); + } + /** + * Get a promise of the reputation reward of a single auction + */ + public auctionReputationReward(): Promise { + this.logContractFunctionCall("Auction4Reputation.auctionReputationReward"); + return this.contract.auctionReputationReward(); + } + /** + * Get a promise of the number of seconds in a single auction + */ + public async getAuctionPeriod(): Promise { + this.logContractFunctionCall("Auction4Reputation.auctionPeriod"); + return (await this.contract.auctionPeriod()).toNumber(); + } + public async getToken(): Promise { + this.logContractFunctionCall("Auction4Reputation.token"); + const address = await this.contract.token(); + return WrapperService.factories.StandardToken.at(address); + } + public async getWallet(): Promise
{ + this.logContractFunctionCall("Auction4Reputation.wallet"); + return await this.contract.wallet(); + } + public getAvatar(): Promise
{ + this.logContractFunctionCall("Auction4Reputation.avatar"); + return this.contract.avatar(); + } + + /** + * Get a promise of the total amount bid for a given auction. + */ + public async getAuctionTotalBid(auctionId: number): Promise { + this.validateAuctionId(auctionId); + this.logContractFunctionCall("Auction4Reputation.auctions", { auctionId }); + const result = (await this.contract.auctions(new BigNumber(auctionId))) as + { totalBid: BigNumber }; + return result.totalBid; + } + + protected hydrated(): void { + super.hydrated(); + /* tslint:disable:max-line-length */ + this.Bid = this.createEventFetcherFactory(this.contract.Bid); + this.Redeem = this.createEventFetcherFactory(this.contract.Redeem); + /* tslint:enable:max-line-length */ + } + + private validateAuctionId(auctionId: number): void { + + if (typeof (auctionId) === "undefined") { + throw new Error("auctionId is not defined"); + } + + if (auctionId < 0) { + throw new Error("auctionId must be greater or equal to 0"); + } + + if (!Number.isInteger(auctionId)) { + throw new Error("auctionId must be an integer number"); + } + } +} + +export class Auction4ReputationType extends ContractWrapperFactory { + + public async deployed(): Promise { + throw new Error("Auction4Reputation has not been deployed"); + } +} + +export const Auction4ReputationFactory = + new Auction4ReputationType( + "Auction4Reputation", + Auction4ReputationWrapper, + new Web3EventService()) as Auction4ReputationType; + +export interface Auction4ReputationRedeemEventResult { + /** + * indexed + */ + _lockingId: Hash; + /** + * indexed + */ + _beneficiary: Address; + /** + * in Wei + */ + _amount: BigNumber; +} + +export interface Auction4ReputationReleaseEventResult { + /** + * indexed + */ + _lockingId: Hash; + /** + * indexed + */ + _beneficiary: Address; + /** + * in Wei + */ + _amount: BigNumber; +} + +export interface Auction4ReputationInitializeOptions { + avatarAddress: Address; + auctionsEndTime: Date; + auctionsStartTime: Date; + numberOfAuctions: number; + /** + * Reputation cannot be redeemed until after this time, even if redeeming has been enabled. + */ + redeemEnableTime: Date; + reputationReward: BigNumber | string; + tokenAddress: Address; + walletAddress: Address; +} + +export interface Auction4ReputationRedeemOptions { + /** + * 0-based index of the auction. + */ + auctionId: number; + beneficiaryAddress: Address; +} + +export interface Auction4ReputationBidOptions { + amount: BigNumber | string; +} + +export interface Auction4ReputationBidEventResult { + _amount: BigNumber; + /** + * 0-based index of the auction. + * indexed + */ + _auctionId: BigNumber; + /** + * indexed + */ + _bidder: Address; +} + +export interface Auction4ReputationRedeemEventResult { + _amount: BigNumber; + /** + * 0-based index of the auction. + * indexed + */ + _auctionId: BigNumber; + /** + * indexed + */ + _beneficiary: Address; +} diff --git a/lib/wrappers/externalLocking4Reputation.ts b/lib/wrappers/externalLocking4Reputation.ts new file mode 100644 index 000000000..ff8335368 --- /dev/null +++ b/lib/wrappers/externalLocking4Reputation.ts @@ -0,0 +1,131 @@ +"use strict"; +import BigNumber from "bignumber.js"; +import { Address } from "../commonTypes"; +import { ContractWrapperFactory } from "../contractWrapperFactory"; +import { ArcTransactionResult, IContractWrapperFactory } from "../iContractWrapperBase"; +import { TxGeneratingFunctionOptions } from "../transactionService"; +import { Web3EventService } from "../web3EventService"; +import { Locking4ReputationWrapper } from "./locking4Reputation"; + +export class ExternalLocking4ReputationWrapper extends Locking4ReputationWrapper { + public name: string = "ExternalLocking4Reputation"; + public friendlyName: string = "External Locking For Reputation"; + public factory: IContractWrapperFactory = ExternalLocking4ReputationFactory; + + public async initialize(options: ExternalLockingInitializeOptions & TxGeneratingFunctionOptions) + : Promise { + + await super._initialize(options, false); + + if (!options.externalLockingContract) { + throw new Error("externalLockingContract is not defined"); + } + if (!options.getBalanceFuncSignature) { + throw new Error("getBalanceFuncSignature is not defined"); + } + + this.logContractFunctionCall("ExternalLocking4Reputation.initialize", options); + + return this.wrapTransactionInvocation("ExternalLocking4Reputation.initialize", + options, + this.contract.initialize, + [options.avatarAddress, + options.reputationReward, + options.lockingStartTime.getTime() / 1000, + options.lockingEndTime.getTime() / 1000, + options.redeemEnableTime.getTime() / 1000, + options.externalLockingContract, + options.getBalanceFuncSignature] + ); + } + + public async getLockBlocker(options: ExternalLockingLockOptions): Promise { + + /** + * stub out amount and period -- they aren't relevant to external locking validation. + */ + const msg = await super.getLockBlocker(Object.assign({}, options, { amount: "1", period: 1 })); + if (msg) { + return msg; + } + + if (!options.lockerAddress) { + return "lockerAddress is not defined"; + } + + const alreadyLocked = await this.getAccountHasLocked(options.lockerAddress); + if (alreadyLocked) { + return "this account has already executed a lock"; + } + } + + public async lock(options: ExternalLockingLockOptions & TxGeneratingFunctionOptions): Promise { + + const msg = await this.getLockBlocker(options); + if (msg) { + throw new Error(msg); + } + + this.logContractFunctionCall("ExternalLocking4Reputation.lock", options); + + return this.wrapTransactionInvocation("ExternalLocking4Reputation.lock", + options, + this.contract.lock, + [], + { from: options.lockerAddress } + ); + } + + public getExternalLockingContract(): Promise
{ + this.logContractFunctionCall("ExternalLocking4Reputation.externalLockingContract"); + return this.contract.externalLockingContract(); + + } + + public getGetBalanceFuncSignature(): Promise { + this.logContractFunctionCall("ExternalLocking4Reputation.getBalanceFuncSignature"); + return this.contract.getBalanceFuncSignature(); + } + + /** + * Promise of `true` if the given account has already executed a lock + */ + public getAccountHasLocked(lockerAddress: Address): Promise { + if (!lockerAddress) { + throw new Error("lockerAddress is not defined"); + } + this.logContractFunctionCall("ExternalLocking4Reputation.externalLockers"); + return this.contract.externalLockers(lockerAddress); + } + +} + +export class ExternalLocking4ReputationType extends ContractWrapperFactory { + + public async deployed(): Promise { + throw new Error("ExternalLocking4Reputation has not been deployed"); + } +} + +export const ExternalLocking4ReputationFactory = + new ExternalLocking4ReputationType( + "ExternalLocking4Reputation", + ExternalLocking4ReputationWrapper, + new Web3EventService()) as ExternalLocking4ReputationType; + +export interface ExternalLockingInitializeOptions { + avatarAddress: Address; + externalLockingContract: Address; + getBalanceFuncSignature: string; + lockingEndTime: Date; + lockingStartTime: Date; + /** + * Reputation cannot be redeemed until after this time, even if redeeming has been enabled. + */ + redeemEnableTime: Date; + reputationReward: BigNumber | string; +} + +export interface ExternalLockingLockOptions { + lockerAddress: Address; +} diff --git a/lib/wrappers/fixedReputationAllocation.ts b/lib/wrappers/fixedReputationAllocation.ts new file mode 100644 index 000000000..f65005023 --- /dev/null +++ b/lib/wrappers/fixedReputationAllocation.ts @@ -0,0 +1,243 @@ +"use strict"; +import BigNumber from "bignumber.js"; +import { Address } from "../commonTypes"; +import { ContractWrapperBase } from "../contractWrapperBase"; +import { ContractWrapperFactory } from "../contractWrapperFactory"; +import { ArcTransactionResult, IContractWrapperFactory } from "../iContractWrapperBase"; +import { TxGeneratingFunctionOptions } from "../transactionService"; +import { UtilsInternal } from "../utilsInternal"; +import { EventFetcherFactory, Web3EventService } from "../web3EventService"; + +export class FixedReputationAllocationWrapper extends ContractWrapperBase { + public name: string = "FixedReputationAllocation"; + public friendlyName: string = "Fixed Reputation Allocation"; + public factory: IContractWrapperFactory = FixedReputationAllocationFactory; + + public Redeem: EventFetcherFactory; + public BeneficiaryAddressAdded: EventFetcherFactory; + + public async initialize(options: FixedReputationAllocationInitializeOptions): Promise { + + const avatar = await this.getAvatar(); + + if (!UtilsInternal.isNullAddress(avatar)) { + throw new Error("you can only call initialize once"); + } + + if (!options.avatarAddress) { + throw new Error("avatarAddress is not defined"); + } + + if (!options.redeemEnableTime) { + throw new Error("redeemEnableTime is not defined"); + } + + if (!options.reputationReward) { + throw new Error("reputationReward is not defined"); + } + + const reputationReward = new BigNumber(options.reputationReward); + + if (reputationReward.lte(0)) { + throw new Error("reputationReward must be greater than zero"); + } + + this.logContractFunctionCall("FixedReputationAllocation.initialize", options); + + return this.wrapTransactionInvocation("FixedReputationAllocation.initialize", + options, + this.contract.initialize, + [ + options.avatarAddress, + options.reputationReward, + options.redeemEnableTime.getTime() / 1000, + ] + ); + } + + public async redeem(options: FixedReputationAllocationRedeemOptions & TxGeneratingFunctionOptions) + : Promise { + + await this.validateEnabled(true); + + const now = await UtilsInternal.lastBlockDate(); + + const redeemEnableTime = await this.getRedeemEnableTime(); + + if (now <= redeemEnableTime) { + throw new Error(`nothing can be redeemed until after ${redeemEnableTime}`); + } + + if (!options.beneficiaryAddress) { + throw new Error("beneficiaryAddress is not defined"); + } + + this.logContractFunctionCall("FixedReputationAllocation.redeem", options); + + return this.wrapTransactionInvocation("FixedReputationAllocation.redeem", + options, + this.contract.redeem, + [options.beneficiaryAddress] + ); + } + + public async addBeneficiary(options: AddBeneficiaryOptions & TxGeneratingFunctionOptions) + : Promise { + + if (!options.beneficiaryAddress) { + throw new Error("beneficiaryAddress is not defined"); + } + + await this.validateEnabled(false); + + this.logContractFunctionCall("FixedReputationAllocation.addBeneficiary", options); + + return this.wrapTransactionInvocation("FixedReputationAllocation.addBeneficiary", + options, + this.contract.addBeneficiary, + [options.beneficiaryAddress] + ); + } + + public async addBeneficiaries(options: AddBeneficiariesOptions & TxGeneratingFunctionOptions) + : Promise { + + if (!options.beneficiaryAddresses || !options.beneficiaryAddresses.length) { + throw new Error("beneficiaryAddresses is empty or not defined"); + } + + await this.validateEnabled(false); + + this.logContractFunctionCall("FixedReputationAllocation.addBeneficiaries", options); + + return this.wrapTransactionInvocation("FixedReputationAllocation.addBeneficiaries", + options, + this.contract.addBeneficiaries, + [options.beneficiaryAddresses] + ); + } + + public setEnabled(options: TxGeneratingFunctionOptions = {}): Promise { + + this.logContractFunctionCall("FixedReputationAllocation.enable", options); + + return this.wrapTransactionInvocation("FixedReputationAllocation.enable", + options, + this.contract.enable, + [] + ); + } + + /** + * Get a promise of the first date/time when anything can be redeemed + */ + public async getRedeemEnableTime(): Promise { + this.logContractFunctionCall("FixedReputationAllocation.redeemEnableTime"); + const seconds = await this.contract.redeemEnableTime(); + return new Date(seconds.toNumber() * 1000); + } + + /** + * Get a promise of the total reputation potentially redeemable + */ + public getReputationReward(): Promise { + this.logContractFunctionCall("FixedReputationAllocation.reputationReward"); + return this.contract.reputationReward(); + } + public getIsEnable(): Promise { + this.logContractFunctionCall("FixedReputationAllocation.isEnable"); + return this.contract.isEnable(); + } + public async getNumberOfBeneficiaries(): Promise { + this.logContractFunctionCall("FixedReputationAllocation.numberOfBeneficiaries"); + const count = await this.contract.numberOfBeneficiaries(); + return count.toNumber(); + } + + /** + * Get a promise of reputation rewardable per beneficiary + */ + public async getBeneficiaryReward(): Promise { + this.logContractFunctionCall("FixedReputationAllocation.beneficiaryReward"); + return this.contract.beneficiaryReward(); + } + public getAvatar(): Promise
{ + this.logContractFunctionCall("FixedReputationAllocation.avatar"); + return this.contract.avatar(); + } + + /** + * Get a promise of boolean indicating whether the given beneficiary has been added. + * + * @param beneficiaryAddress + */ + public async getBeneficiaryAdded(beneficiaryAddress: Address): Promise { + this.logContractFunctionCall("FixedReputationAllocation.beneficiaries", { beneficiaryAddress }); + return this.contract.beneficiaries(beneficiaryAddress); + } + + protected hydrated(): void { + super.hydrated(); + /* tslint:disable:max-line-length */ + this.BeneficiaryAddressAdded = this.createEventFetcherFactory(this.contract.BeneficiaryAddressAdded); + this.Redeem = this.createEventFetcherFactory(this.contract.Redeem); + /* tslint:enable:max-line-length */ + } + + private async validateEnabled(mustBeEnabled: boolean): Promise { + const enabled = await this.getIsEnable(); + + if (enabled !== mustBeEnabled) { + throw new Error(`${mustBeEnabled} ? "The contract is not enabled" : The contract is enabled`); + } + } +} + +export class FixedReputationAllocationType extends ContractWrapperFactory { + + public async deployed(): Promise { + throw new Error("FixedReputationAllocation has not been deployed"); + } +} + +export const FixedReputationAllocationFactory = + new FixedReputationAllocationType( + "FixedReputationAllocation", + FixedReputationAllocationWrapper, + new Web3EventService()) as FixedReputationAllocationType; + +export interface FixedReputationAllocationInitializeOptions { + avatarAddress: Address; + /** + * Reputation cannot be redeemed until after this time, even if redeeming has been enabled. + */ + redeemEnableTime: Date; + reputationReward: BigNumber | string; +} + +export interface FixedReputationAllocationRedeemOptions { + beneficiaryAddress: Address; +} + +export interface AddBeneficiaryOptions { + beneficiaryAddress: Address; +} + +export interface AddBeneficiariesOptions { + beneficiaryAddresses: Array
; +} + +export interface BeneficiaryAddressAddedEventResult { + /** + * indexed + */ + _beneficiary: Address; +} + +export interface FixedReputationAllocationRedeemEventResult { + _amount: BigNumber; + /** + * indexed + */ + _beneficiary: Address; +} diff --git a/lib/wrappers/genesisProtocol.ts b/lib/wrappers/genesisProtocol.ts index 98fd99000..36efc2937 100644 --- a/lib/wrappers/genesisProtocol.ts +++ b/lib/wrappers/genesisProtocol.ts @@ -333,8 +333,9 @@ 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 in the DAO - * as well as the GenesisProtocol parameters thresholdConstA and thresholdConstB. + * 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 */ public async getThreshold(proposalId: Hash): Promise { @@ -344,27 +345,37 @@ export class GenesisProtocolWrapper extends IntVoteInterfaceWrapper } const proposal = await this.getProposal(proposalId); - const creator = await this.getProposalCreator(proposalId); this.logContractFunctionCall("GenesisProtocol.threshold", - { proposalId, parametersHash: proposal.paramsHash, organization: creator }); + { proposalId, parametersHash: proposal.paramsHash, organizationId: proposal.organizationId }); - return this.contract.threshold(proposal.paramsHash, creator); + return this.contract.threshold(proposal.paramsHash, proposal.organizationId); } /** - * Returns a promise of the number of boosted proposals by the given creatorAddress, - * not including those that have expired but have not yet been executed to update their status. + * Returns a promise of the number of boosted proposals by the address of the scheme + * that created the proposal and the "creator address" which is typically the avatar for proposals + * created by the Arc universal schemes. + * The count does not include those that have expired but have not yet + * been executed to update their status. */ - public async getBoostedProposalsCount(creatorAddress: Address): Promise { + public async getBoostedProposalsCount( + schemeAddress: Address, + creatorAddress: Address): Promise { + + if (!schemeAddress) { + throw new Error("schemeAddress is not defined"); + } if (!creatorAddress) { throw new Error("creatorAddress is not defined"); } + const organizationId = this.organizationIdFromProposalCreator(schemeAddress, creatorAddress); + this.logContractFunctionCall("GenesisProtocol.getBoostedProposalsCount", creatorAddress); - return this.contract.getBoostedProposalsCount(creatorAddress); + return this.contract.getBoostedProposalsCount(organizationId); } /** @@ -508,7 +519,9 @@ export class GenesisProtocolWrapper extends IntVoteInterfaceWrapper } /** - * Returns a promise of the address of the contract that created the given proposal. + * For proposals created by Arc universal schemes, returns a promise of the address of the avatar + * on whose behalf the proposal was created. For proposals created by other schemes, the value + * is defined by those contracts, but is expected to be an address. * @returns Promise
*/ public async getProposalCreator(proposalId: Hash): Promise
{ @@ -519,7 +532,13 @@ export class GenesisProtocolWrapper extends IntVoteInterfaceWrapper this.logContractFunctionCall("GenesisProtocol.getProposalOrganization", proposalId); - return this.contract.getProposalOrganization(proposalId); + const organizationId = await this.getProposalOrganizationId(proposalId); + + if (!organizationId) { + throw new Error("the proposal is not found"); + } + + return this.contract.organizations(organizationId); } /** @@ -665,7 +684,8 @@ export class GenesisProtocolWrapper extends IntVoteInterfaceWrapper */ public async getProposal(proposalId: Hash): Promise { const proposalParams = await this.contract.proposals(proposalId); - return this.convertProposalPropsArrayToObject(proposalParams, proposalId); + const creatorAddress = await this.getProposalCreator(proposalId); + return this.convertProposalPropsArrayToObject(proposalParams, proposalId, creatorAddress); } public async getParametersHash(params: GenesisProtocolParams): Promise { @@ -889,20 +909,42 @@ export class GenesisProtocolWrapper extends IntVoteInterfaceWrapper /* tslint:enable:max-line-length */ } - private convertProposalPropsArrayToObject(proposalArray: Array, proposalId: Hash): GenesisProtocolProposal { + /** + * Returns a promise of the `organizationId`of the proposal. This id is unique to the + * contract that created the proposal and, in the case of Arc schemes, the avatar in which + * the scheme is registered. The value is included in `GenesisProtocolProposal` + * and is returned by all of the voting machine events. So you could use this value when filtering + * events from a single avatar and contract registered with the avatar. + */ + private async getProposalOrganizationId(proposalId: Hash): Promise { + + if (!proposalId) { + throw new Error("proposalId is not defined"); + } + + this.logContractFunctionCall("GenesisProtocol.getProposalOrganization", proposalId); + + return this.contract.getProposalOrganization(proposalId); + } + + private convertProposalPropsArrayToObject( + proposalArray: Array, + proposalId: Hash, + creatorAddress: Address): GenesisProtocolProposal { return { - boostedPhaseTime: proposalArray[4].toNumber(), - creatorAddress: proposalArray[0], - currentBoostedVotePeriodLimit: proposalArray[8].toNumber(), - daoBountyRemain: proposalArray[10], - numOfChoices: proposalArray[1].toNumber(), - paramsHash: proposalArray[9], + boostedPhaseTime: proposalArray[5].toNumber(), + creatorAddress, + currentBoostedVotePeriodLimit: proposalArray[9].toNumber(), + daoBountyRemain: proposalArray[11], + numOfChoices: proposalArray[2].toNumber(), + organizationId: proposalArray[0], + paramsHash: proposalArray[10], proposalId, - proposer: proposalArray[7], - state: proposalArray[5].toNumber(), - submittedTime: proposalArray[3].toNumber(), - votersStakes: proposalArray[2], - winningVote: proposalArray[6].toNumber(), + proposer: proposalArray[8], + state: proposalArray[6].toNumber(), + submittedTime: proposalArray[4].toNumber(), + votersStakes: proposalArray[3], + winningVote: proposalArray[7].toNumber(), }; } } @@ -1316,19 +1358,43 @@ export interface GenesisProtocolProposal { */ boostedPhaseTime: number; /** - * in seconds + * The amount of time, in seconds, that the proposal can remain in the boosted phase + * (assuming the proposal has reached the boosted phase). */ currentBoostedVotePeriodLimit: number; + /** + * The amount of bounty remaining that can be redeemed on the given proposal. + */ daoBountyRemain: BigNumber; + /** + * The number of voting choices. For GenesisProtocol this is always 2 (YES and NO). + */ numOfChoices: number; + /** + * A hash is unique to the contract that created the proposal and, in the case of Arc schemes, + * the avatar in which the scheme is registered. + */ + organizationId: Hash; + /** + * The parameters of the registered `GenesisProtocol` used to create the proposal. + */ paramsHash: Hash; proposalId: Hash; + /** + * address of the account the submitted the proposal + */ proposer: Address; + /** + * current phase in a proposal life-cycle + */ state: ProposalState; /** * in seconds */ submittedTime: number; + /** + * the amount of staked GEN receivable by preboosted voters + */ votersStakes: BigNumber; winningVote: number; } diff --git a/lib/wrappers/iIntVoteInterface.ts b/lib/wrappers/iIntVoteInterface.ts index 74a27210c..b2cb8f18b 100644 --- a/lib/wrappers/iIntVoteInterface.ts +++ b/lib/wrappers/iIntVoteInterface.ts @@ -32,6 +32,12 @@ export interface IIntVoteInterface { export interface ProposeOptions { numOfChoices: number; + /** + * Typically this is the avatar address, but you can pass any address here, + * or null, This argument is used to link a proposal-creating scheme with an organisation. + * If it is not given then it will be set to the `msg.sender`. + */ + organizationAddress?: Address; proposerAddress?: Address; proposalParameters: Hash; } @@ -64,7 +70,7 @@ export interface CancelProposalEventResult { /** * indexed */ - _avatar: Address; + _organization: Address; /** * indexed */ @@ -75,7 +81,7 @@ export interface CancelVotingEventResult { /** * indexed */ - _avatar: Address; + _organization: Address; /** * indexed */ @@ -90,7 +96,7 @@ export interface NewProposalEventResult { /** * indexed */ - _avatar: Address; + _organization: Address; _numOfChoices: BigNumber; _paramsHash: Hash; /** @@ -107,7 +113,7 @@ export interface ExecuteProposalEventResult { /** * indexed */ - _avatar: Address; + _organization: Address; /** * the vote choice that won. */ @@ -126,7 +132,7 @@ export interface VoteProposalEventResult { /** * indexed */ - _avatar: Address; + _organization: Address; /** * indexed */ diff --git a/lib/wrappers/intVoteInterface.ts b/lib/wrappers/intVoteInterface.ts index d6364cbcc..38e81ad35 100644 --- a/lib/wrappers/intVoteInterface.ts +++ b/lib/wrappers/intVoteInterface.ts @@ -8,6 +8,7 @@ import { DecodedLogEntryEvent, IContractWrapperFactory } from "../iContractWrapperBase"; +import { LoggingService } from "../loggingService"; import { TxGeneratingFunctionOptions } from "../transactionService"; import { Utils } from "../utils"; import { EventFetcherFactory, Web3EventService } from "../web3EventService"; @@ -84,6 +85,10 @@ export class IntVoteInterfaceWrapper extends ContractWrapperBase implements IInt */ public async propose(options: ProposeOptions & TxGeneratingFunctionOptions): Promise { + if (!options.organizationAddress) { + LoggingService.warn(`IntVoteInterface.propose: organizationAddress is not set, will be set to msg.sender`); + } + const numChoiceBounds = await this.getAllowedRangeOfChoices(); if (!Number.isInteger(options.numOfChoices)) { @@ -360,6 +365,10 @@ export class IntVoteInterfaceWrapper extends ContractWrapperBase implements IInt throw new Error(`vote must be a number greater than or equal to zero and less than or equal to ${numChoices}`); } } + + protected organizationIdFromProposalCreator(schemeAddress: Address, creator: Address): Hash { + return Utils.keccak256(["address", "address"], [schemeAddress, creator]); + } } export class IntVoteInterfaceFactoryType extends ContractWrapperFactory { diff --git a/lib/wrappers/locking4Reputation.ts b/lib/wrappers/locking4Reputation.ts new file mode 100644 index 000000000..316145810 --- /dev/null +++ b/lib/wrappers/locking4Reputation.ts @@ -0,0 +1,461 @@ +"use strict"; +import BigNumber from "bignumber.js"; +import { Address, Hash } from "../commonTypes"; +import { ContractWrapperBase } from "../contractWrapperBase"; +import { ContractWrapperFactory } from "../contractWrapperFactory"; +import { ArcTransactionResult, DecodedLogEntryEvent, IContractWrapperFactory } from "../iContractWrapperBase"; +import { TxGeneratingFunctionOptions } from "../transactionService"; +import { UtilsInternal } from "../utilsInternal"; +import { + EntityFetcherFactory, + EventFetcherFactory, + EventFetcherFilterObject, + Web3EventService +} from "../web3EventService"; + +export abstract class Locking4ReputationWrapper extends ContractWrapperBase { + public name: string = "Locking4Reputation"; + public friendlyName: string = "Locking For Reputation"; + public factory: IContractWrapperFactory = null; + + public Redeem: EventFetcherFactory; + public Release: EventFetcherFactory; + public Lock: EventFetcherFactory; + + public async redeem(options: RedeemOptions & TxGeneratingFunctionOptions): Promise { + if (!options.lockerAddress) { + throw new Error("lockerAddress is not defined"); + } + + const errMsg = await this.getRedeemBlocker(options.lockerAddress); + + if (errMsg) { + throw new Error(errMsg); + } + + this.logContractFunctionCall("Locking4Reputation.redeem", options); + + return this.wrapTransactionInvocation("Locking4Reputation.redeem", + options, + this.contract.redeem, + [options.lockerAddress] + ); + } + + /** + * Returns reason why can't redeem, or else null if can redeem + * @param lockerAddress + */ + public async getRedeemBlocker(lockerAddress: Address): Promise { + const lockingEndTime = await this.getLockingEndTime(); + const now = await UtilsInternal.lastBlockDate(); + if (now <= lockingEndTime) { + return "the locking period has not ended"; + } + + const redeemEnableTime = await this.getRedeemEnableTime(); + + if (now <= redeemEnableTime) { + throw new Error(`nothing can be redeemed until after ${redeemEnableTime}`); + } + + const lockerInfo = await this.getLockerInfo(lockerAddress); + if (lockerInfo.score.eq(0)) { + return "the reputation has already been redeemed"; + } + + return null; + } + + /** + * Returns error message else null if can release + * @param lockerAddress + * @param lockId + */ + public async getReleaseBlocker(lockerAddress: Address, lockId: Hash): Promise { + const lockInfo = await this.getLockInfo(lockerAddress, lockId); + const now = await UtilsInternal.lastBlockDate(); + const amount = new BigNumber(lockInfo.amount); + + if (amount.lte(0)) { + return "current locked amount must be greater than zero"; + } + + if (now <= lockInfo.releaseTime) { + return "the lock period has not ended"; + } + + return null; + } + + /** + * Returns reason why can't lock, else null if can lock + */ + public async getLockBlocker(options: LockingOptions): Promise { + + if (!options.lockerAddress) { + return "lockerAddress is not defined"; + } + + const amount = new BigNumber(options.amount); + + if (amount.lte(0)) { + return "amount must be greater than zero"; + } + + if (!Number.isInteger(options.period)) { + return "period is not an integer"; + } + + if (options.period <= 0) { + return "period must be greater than zero"; + } + + const now = await UtilsInternal.lastBlockDate(); + + const maxLockingPeriod = await this.getMaxLockingPeriod(); + + if (options.period > maxLockingPeriod) { + return "the locking period exceeds the maxLockingPeriod"; + } + + const lockingStartTime = await this.getLockingStartTime(); + const lockingEndTime = await this.getLockingEndTime(); + + if ((now < lockingStartTime) || (now > lockingEndTime)) { + return "the locking period has not started or has expired"; + } + + return null; + } + + /** + * Get a promise of the first date/time when anything can be redeemed + */ + public async getRedeemEnableTime(): Promise { + this.logContractFunctionCall("Locking4Reputation.redeemEnableTime"); + const seconds = await this.contract.redeemEnableTime(); + return new Date(seconds.toNumber() * 1000); + } + + public getTotalLocked(): Promise { + this.logContractFunctionCall("Locking4Reputation.totalLocked"); + return this.contract.totalLocked(); + } + public getTotalLockedLeft(): Promise { + this.logContractFunctionCall("Locking4Reputation.totalLockedLeft"); + return this.contract.totalLockedLeft(); + } + public getTotalScore(): Promise { + this.logContractFunctionCall("Locking4Reputation.totalScore"); + return this.contract.totalScore(); + } + /** + * get total number of locks + */ + public async getLockCount(): Promise { + this.logContractFunctionCall("Locking4Reputation.lockingsCounter"); + return (await this.contract.lockingsCounter()).toNumber(); + } + + /** + * get the total reputation this contract will reward + */ + public getReputationReward(): Promise { + this.logContractFunctionCall("Locking4Reputation.reputationReward"); + return this.contract.reputationReward(); + } + /** + * get the total reputation this contract has not yet rewarded + */ + public getReputationRewardLeft(): Promise { + this.logContractFunctionCall("Locking4Reputation.reputationRewardLeft"); + return this.contract.reputationRewardLeft(); + } + public async getLockingEndTime(): Promise { + this.logContractFunctionCall("Locking4Reputation.lockingEndTime"); + const dt = await this.contract.lockingEndTime(); + return new Date(dt.toNumber() * 1000); + } + public async getLockingStartTime(): Promise { + this.logContractFunctionCall("Locking4Reputation.lockingStartTime"); + const dt = await this.contract.lockingStartTime(); + return new Date(dt.toNumber() * 1000); + } + public async getMaxLockingPeriod(): Promise { + this.logContractFunctionCall("Locking4Reputation.maxLockingPeriod"); + // returns seconds + return (await this.contract.maxLockingPeriod()).toNumber(); + } + public getAvatar(): Promise
{ + this.logContractFunctionCall("Locking4Reputation.avatar"); + return this.contract.avatar(); + } + + /** + * Returns promise of information about a locked amount for the given locker and lockerId. + * @param lockerAddress + * @param lockId + */ + public async getLockInfo(lockerAddress: Address, lockId: Hash): Promise { + this.logContractFunctionCall("Locking4Reputation.lockers", { lockerAddress, lockId }); + const lockInfo = await this.contract.lockers(lockerAddress, lockId); + return { + amount: lockInfo[0], + lockId, + lockerAddress, + releaseTime: new Date(lockInfo[1].toNumber() * 1000), + }; + } + + /** + * Returns promise of information about the given locker, including the locker's score. + * Score determines the proportion of total reputation that can be redeemed by the locker. + * + * @param lockerAddress + */ + public async getLockerInfo(lockerAddress: Address): Promise { + this.logContractFunctionCall("Locking4Reputation.scores", { lockerAddress }); + const score = await this.contract.scores(lockerAddress); + return { + lockerAddress, + score, + }; + } + + /** + * Returns EntityEventFetcher that returns `LockInfo` for each `Lock` event. + */ + public async getLocks(): + Promise> { + + const web3EventService = new Web3EventService(); + return web3EventService.createEntityFetcherFactory( + this.Lock, + async (event: DecodedLogEntryEvent): Promise => { + const lockInfo = await this.getLockInfo(event.args._locker, event.args._lockingId); + return Promise.resolve({ + amount: lockInfo.amount, + lockId: event.args._lockingId, + lockerAddress: event.args._locker, + releaseTime: lockInfo.releaseTime, + }); + }); + } + + /** + * Returns EntityEventFetcher that returns `LockerInfo` for each account that has created a lock. + * It is fired for an account whenever a `Lock`, `Redeem` or `Release` event is emitted. + */ + public async getLockers(options: GetLockersOptions = {}): Promise> { + + const filter: any = {}; + + if (options.lockerAddress) { + filter._locker = options.lockerAddress; + } + + if (options.lockingId) { + filter._lockingId = options.lockingId; + } + + const fetcher = (await this.getLocks())(filter, options.filterObject); + + const lockInfos = await fetcher.get(); + const foundAddresses = new Set
(); + + const lockers = new Array(); + + for (const lockInfo of lockInfos) { + if (!foundAddresses.has(lockInfo.lockerAddress)) { + foundAddresses.add(lockInfo.lockerAddress); + const lockerInfo = await this.getLockerInfo(lockInfo.lockerAddress); + lockers.push(lockerInfo); + } + } + return lockers; + } + + protected async _initialize(options: Partial, checkmaxLockingPeriod: boolean = true) + : Promise { + + const avatar = await this.getAvatar(); + + if (!UtilsInternal.isNullAddress(avatar)) { + throw new Error("you can only call initialize once"); + } + + if (!options.avatarAddress) { + throw new Error("avatarAddress is not defined"); + } + + if (!options.lockingEndTime) { + throw new Error("lockingEndTime is not defined"); + } + + if (!options.lockingStartTime) { + throw new Error("lockingStartTime is not defined"); + } + + if (!options.redeemEnableTime) { + throw new Error("redeemEnableTime is not defined"); + } + + if (options.lockingEndTime <= options.lockingStartTime) { + throw new Error("lockingEndTime must be greater than lockingStartTime"); + } + + if (checkmaxLockingPeriod) { + if (!Number.isInteger(options.maxLockingPeriod)) { + throw new Error("maxLockingPeriod is not an integer"); + } + + if (options.maxLockingPeriod <= 0) { + throw new Error("maxLockingPeriod must be greater than zero"); + } + } + + if (!options.reputationReward) { + throw new Error("reputationReward is not defined"); + } + + const reputationReward = new BigNumber(options.reputationReward); + + if (reputationReward.lte(0)) { + throw new Error("reputationReward must be greater than zero"); + } + } + + protected async _release(options: ReleaseOptions): Promise { + + if (!options.lockerAddress) { + throw new Error("lockerAddress is not defined"); + } + + if (!options.lockId) { + throw new Error("lockId is not defined"); + } + + const errMsg = await this.getReleaseBlocker(options.lockerAddress, options.lockId); + + if (errMsg) { + throw new Error(errMsg); + } + } + + protected hydrated(): void { + super.hydrated(); + /* tslint:disable:max-line-length */ + this.Redeem = this.createEventFetcherFactory(this.contract.Redeem); + this.Release = this.createEventFetcherFactory(this.contract.Release); + this.Lock = this.createEventFetcherFactory(this.contract.Lock); + /* tslint:enable:max-line-length */ + } +} + +export interface Locking4ReputationRedeemEventResult { + /** + * indexed + */ + _lockingId: Hash; + /** + * indexed + */ + _beneficiary: Address; + /** + * in Wei + */ + _amount: BigNumber; +} + +export interface Locking4ReputationReleaseEventResult { + /** + * indexed + */ + _lockingId: Hash; + /** + * indexed + */ + _beneficiary: Address; + /** + * in Wei + */ + _amount: BigNumber; +} + +export interface Locking4ReputationLockEventResult { + /** + * indexed + */ + _lockingId: Hash; + /** + * in Wei + */ + _amount: BigNumber; + _period: BigNumber; + /** + * indexed + */ + _locker: Address; +} + +export interface InitializeOptions { + avatarAddress: Address; + lockingEndTime: Date; + lockingStartTime: Date; + /** + * maximum period length in seconds + */ + maxLockingPeriod: number; + /** + * Reputation cannot be redeemed until after this time, even if redeeming has been enabled. + */ + redeemEnableTime: Date; + reputationReward: BigNumber | string; +} + +export interface LockInfo { + lockerAddress: Address; + /** + * in Wei + */ + amount: BigNumber | string; + lockId: Hash; + releaseTime: Date; +} + +export interface LockerInfo { + lockerAddress: Address; + score: BigNumber; +} + +export interface RedeemOptions { + lockerAddress: Address; +} + +export interface ReleaseOptions { + lockerAddress: Address; + lockId: Hash; +} + +export interface LockingOptions { + /** + * in Wei + */ + amount: BigNumber | string; + /** + * the number of seconds the amount should be locked + */ + period: number; + lockerAddress: Address; +} + +export interface GetLockersOptions { + lockerAddress?: Address; + lockingId?: Hash; + /** + * Web3 event filter options. Typically something like `{ fromBlock: 0 }`. + * Note if you don't want Arc.js to suppress duplicate events, set `suppressDups` to false. + */ + filterObject?: EventFetcherFilterObject; +} diff --git a/lib/wrappers/lockingEth4Reputation.ts b/lib/wrappers/lockingEth4Reputation.ts new file mode 100644 index 000000000..19cdfa072 --- /dev/null +++ b/lib/wrappers/lockingEth4Reputation.ts @@ -0,0 +1,95 @@ +"use strict"; +import BigNumber from "bignumber.js"; +import { ContractWrapperFactory } from "../contractWrapperFactory"; +import { ArcTransactionResult, IContractWrapperFactory } from "../iContractWrapperBase"; +import { TxGeneratingFunctionOptions } from "../transactionService"; +import { Utils } from "../utils"; +import { Web3EventService } from "../web3EventService"; +import { InitializeOptions, Locking4ReputationWrapper, LockingOptions, ReleaseOptions } from "./locking4Reputation"; + +export class LockingEth4ReputationWrapper extends Locking4ReputationWrapper { + public name: string = "LockingEth4Reputation"; + public friendlyName: string = "Locking Eth For Reputation"; + public factory: IContractWrapperFactory = LockingEth4ReputationFactory; + + public async initialize(options: InitializeOptions & TxGeneratingFunctionOptions) + : Promise { + + await super._initialize(options); + + this.logContractFunctionCall("LockingEth4Reputation.initialize", options); + + return this.wrapTransactionInvocation("LockingEth4Reputation.initialize", + options, + this.contract.initialize, + [options.avatarAddress, + options.reputationReward, + options.lockingStartTime.getTime() / 1000, + options.lockingEndTime.getTime() / 1000, + options.redeemEnableTime.getTime() / 1000, + options.maxLockingPeriod] + ); + } + + public async release(options: ReleaseOptions & TxGeneratingFunctionOptions): Promise { + + await super._release(options); + + this.logContractFunctionCall("LockingEth4Reputation.release", options); + + return this.wrapTransactionInvocation("LockingEth4Reputation.release", + options, + this.contract.release, + [options.lockerAddress, options.lockId] + ); + } + + /** + * Returns reason why can't lock, else null if can lock + */ + public async getLockBlocker(options: LockingOptions): Promise { + + const msg = await super.getLockBlocker(options); + if (msg) { + return msg; + } + + const balance = await Utils.getEthBalance(options.lockerAddress); + const amount = new BigNumber(options.amount); + + if (balance.lt(amount)) { + return "the account has insufficient balance to lock the requested amount"; + } + return null; + } + + public async lock(options: LockingOptions & TxGeneratingFunctionOptions): Promise { + + const msg = await this.getLockBlocker(options); + if (msg) { + throw new Error(msg); + } + + this.logContractFunctionCall("LockingEth4Reputation.lock", options); + + return this.wrapTransactionInvocation("LockingEth4Reputation.lock", + options, + this.contract.lock, + [options.period], + { from: options.lockerAddress, value: options.amount } + ); + } +} + +export class LockingEth4ReputationType extends ContractWrapperFactory { + + public async deployed(): Promise { + throw new Error("LockingEth4Reputation has not been deployed"); + } +} + +export const LockingEth4ReputationFactory = + new LockingEth4ReputationType( + "LockingEth4Reputation", + LockingEth4ReputationWrapper, + new Web3EventService()) as LockingEth4ReputationType; diff --git a/lib/wrappers/lockingToken4Reputation.ts b/lib/wrappers/lockingToken4Reputation.ts new file mode 100644 index 000000000..8c8291693 --- /dev/null +++ b/lib/wrappers/lockingToken4Reputation.ts @@ -0,0 +1,115 @@ +"use strict"; +import BigNumber from "bignumber.js"; +import { Address } from "../commonTypes"; +import { ContractWrapperFactory } from "../contractWrapperFactory"; +import { ArcTransactionResult, IContractWrapperFactory } from "../iContractWrapperBase"; +import { TxGeneratingFunctionOptions } from "../transactionService"; +import { Utils } from "../utils"; +import { Web3EventService } from "../web3EventService"; +import { WrapperService } from "../wrapperService"; +import { InitializeOptions, Locking4ReputationWrapper, LockingOptions, ReleaseOptions } from "./locking4Reputation"; +import { StandardTokenWrapper } from "./standardToken"; + +export class LockingToken4ReputationWrapper extends Locking4ReputationWrapper { + public name: string = "LockingToken4Reputation"; + public friendlyName: string = "Locking Token For Reputation"; + public factory: IContractWrapperFactory = LockingToken4ReputationFactory; + + public async initialize(options: LockTokenInitializeOptions & TxGeneratingFunctionOptions) + : Promise { + + await super._initialize(options); + + if (!options.tokenAddress) { + throw new Error(`tokenAddress not supplied`); + } + + this.logContractFunctionCall("LockingToken4Reputation.initialize", options); + + return this.wrapTransactionInvocation("LockingToken4Reputation.initialize", + options, + this.contract.initialize, + [options.avatarAddress, + options.reputationReward, + options.lockingStartTime.getTime() / 1000, + options.lockingEndTime.getTime() / 1000, + options.redeemEnableTime.getTime() / 1000, + options.maxLockingPeriod, + options.tokenAddress] + ); + } + + public async release(options: ReleaseOptions & TxGeneratingFunctionOptions): Promise { + + await super._release(options); + + this.logContractFunctionCall("LockingToken4Reputation.release", options); + + return this.wrapTransactionInvocation("LockingToken4Reputation.release", + options, + this.contract.release, + [options.lockerAddress, options.lockId] + ); + } + + /** + * Returns reason why can't lock, else null if can lock + */ + public async getLockBlocker(options: LockingOptions): Promise { + + const msg = await super.getLockBlocker(options); + if (msg) { + return msg; + } + + const token = await this.getToken(); + const balance = await Utils.getTokenBalance(options.lockerAddress, token.address); + const amount = new BigNumber(options.amount); + + if (balance.lt(amount)) { + return "the account has insufficient balance to lock the requested amount"; + } + + return null; + } + + public async lock(options: LockingOptions & TxGeneratingFunctionOptions): Promise { + + const msg = await this.getLockBlocker(options); + if (msg) { + throw new Error(msg); + } + + this.logContractFunctionCall("LockingToken4Reputation.lock", options); + + return this.wrapTransactionInvocation("LockingToken4Reputation.lock", + options, + this.contract.lock, + [options.amount, options.period], + { from: options.lockerAddress } + ); + } + + public async getToken(): Promise { + this.logContractFunctionCall("LockingToken4Reputation.token"); + const address = await this.contract.token(); + return WrapperService.factories.StandardToken.at(address); + } +} + +export class LockingToken4ReputationType extends ContractWrapperFactory { + + public async deployed(): Promise { + throw new Error("LockingToken4Reputation has not been deployed"); + } +} + +export const LockingToken4ReputationFactory = + new LockingToken4ReputationType( + "LockingToken4Reputation", + LockingToken4ReputationWrapper, + new Web3EventService()) as LockingToken4ReputationType; + +export interface LockTokenInitializeOptions extends InitializeOptions { + tokenAddress: Address; +} diff --git a/lib/wrappers/vestingScheme.ts b/lib/wrappers/vestingScheme.ts index 4b114c13c..26a25cd31 100644 --- a/lib/wrappers/vestingScheme.ts +++ b/lib/wrappers/vestingScheme.ts @@ -399,7 +399,7 @@ export class VestingSchemeWrapper extends ProposalGeneratorBase implements IUniv } if ((typeof options.startingBlock === "undefined") || (options.startingBlock === null)) { - options.startingBlock = await UtilsInternal.lastBlock(); + options.startingBlock = await UtilsInternal.lastBlockNumber(); } if (!Number.isInteger(options.startingBlock) || (options.startingBlock < 0)) { diff --git a/package-lock.json b/package-lock.json index f0a6d8d59..26f717391 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,20 +5,20 @@ "requires": true, "dependencies": { "@daostack/arc": { - "version": "0.0.0-alpha.53", - "resolved": "https://registry.npmjs.org/@daostack/arc/-/arc-0.0.0-alpha.53.tgz", - "integrity": "sha512-6lrzdWH1kzsITdesJcJGAoMH89uxnmv8q5yNX9CyR3Q6JkPoQ79RmLhz07c7qZ2rpsi9igrRYoZ8npFLMAKAYQ==", + "version": "0.0.0-alpha.56", + "resolved": "https://registry.npmjs.org/@daostack/arc/-/arc-0.0.0-alpha.56.tgz", + "integrity": "sha512-dye4G91QU0jHj26Z46zsWzVgGSX44fo2LYz4jD2frB8lKVJrJMKa2NVFOq7+AHXNjCVCRY+0x8FbG9teDleIkA==", "dev": true, "requires": { - "@daostack/infra": "^0.0.0-alpha.06", + "@daostack/infra": "0.0.0-alpha.09", "ethereumjs-abi": "^0.6.5", "openzeppelin-solidity": "1.12.0" } }, "@daostack/infra": { - "version": "0.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/@daostack/infra/-/infra-0.0.0-alpha.6.tgz", - "integrity": "sha512-9XqySiviym4qn2MSpN9rlgLdYoW7B0xCLPI+pfW5qCQoWUJbJOPzhPO9qtEkbBGctB2Yy3WRpGlBP5H0e6G8kA==", + "version": "0.0.0-alpha.9", + "resolved": "https://registry.npmjs.org/@daostack/infra/-/infra-0.0.0-alpha.9.tgz", + "integrity": "sha512-zLh+eQU1wnXyEieJCWu/vnp7quh1/k/Ls93twy+fEykYPBX5VBQz0I0Y8gEJokAp8GegZVmHJ2oc9IonxknqGw==", "dev": true, "requires": { "ethereumjs-abi": "^0.6.5", @@ -711,12 +711,12 @@ }, "babel-plugin-syntax-async-functions": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=" }, "babel-plugin-syntax-exponentiation-operator": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=" }, "babel-plugin-syntax-trailing-function-commas": { @@ -1151,7 +1151,7 @@ }, "babelify": { "version": "7.3.0", - "resolved": "http://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", + "resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", "requires": { "babel-core": "^6.0.14", @@ -1356,7 +1356,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "requires": { "bn.js": "^4.1.0", @@ -5593,7 +5593,7 @@ }, "readable-stream": { "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { "core-util-is": "~1.0.0", @@ -5630,7 +5630,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "requires": { "core-util-is": "~1.0.0", @@ -6134,7 +6134,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "mixin-deep": { @@ -6303,7 +6303,7 @@ }, "node-fetch": { "version": "2.1.2", - "resolved": "http://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" }, "node-glob": { @@ -10349,7 +10349,7 @@ }, "whatwg-fetch": { "version": "2.0.4", - "resolved": "http://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" }, "whatwg-url": { diff --git a/package.json b/package.json index 03ca27294..c7cb54d36 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "tslib": "^1.9.0" }, "devDependencies": { - "@daostack/arc": "0.0.0-alpha.53", + "@daostack/arc": "0.0.0-alpha.56", "@types/chai": "^4.1.2", "@types/circular-json": "^0.4.0", "@types/es6-promisify": "^6.0.0", diff --git a/test/dao.ts b/test/dao.ts index fb178e833..0df815e8f 100644 --- a/test/dao.ts +++ b/test/dao.ts @@ -58,6 +58,40 @@ describe("DAO", () => { it("can register non-wrapped, non-deployed scheme", async () => { + const contractName = "OrganizationRegister"; + + let truffleContract; + try { + truffleContract = await Utils.requireContract(contractName); + } catch (ex) { + throw new Error(`can't find '${contractName}': ${ex.message ? ex.message : ex}`); + } + + const newContract = await truffleContract.new(); + + const dao = await getNewDao({ + founders: [ + { + address: accounts[0], + reputation: web3.toWei(10), + tokens: web3.toWei(100), + }, + ], + schemes: [ + { + address: newContract.address, + name: contractName, + }, + ], + }); + + const schemes = await dao.getSchemes(); + assert.equal(schemes.length, 1); + assert.equal(schemes[0].address, newContract.address); + }); + + it("can register non-universal scheme", async () => { + const contractName = "Auction4Reputation"; let truffleContract; @@ -67,15 +101,7 @@ describe("DAO", () => { throw new Error(`can't find '${contractName}': ${ex.message ? ex.message : ex}`); } - const newContract = await truffleContract.new( - helpers.SOME_HASH, - 10, - 1000, - 2000, - 100, - "0x4d50fa58b754fdc70feafc8b9dba0bfb27079922", - "0xb0c908140fe6fd6fbd4990a5c2e35ca6dc12bfb2" - ); + const newContract = await truffleContract.new(); const dao = await getNewDao({ founders: [ diff --git a/test/genesisProtocol.ts b/test/genesisProtocol.ts index c3bf85d71..e147f0f00 100644 --- a/test/genesisProtocol.ts +++ b/test/genesisProtocol.ts @@ -202,7 +202,7 @@ describe("GenesisProtocol", () => { it("can get votable proposals", async () => { - const startingBlock = await UtilsInternal.lastBlock(); + const startingBlock = await UtilsInternal.lastBlockNumber(); const proposalId1 = await createProposal(); @@ -224,7 +224,7 @@ describe("GenesisProtocol", () => { it("can get executed proposals", async () => { - const startingBlock = await UtilsInternal.lastBlock(); + const startingBlock = await UtilsInternal.lastBlockNumber(); const proposalId1 = await createProposal(); await voteProposal(proposalId1, 1); @@ -512,7 +512,11 @@ describe("GenesisProtocol", () => { it("can call getBoostedProposalsCount", async () => { - const count1 = await genesisProtocol.getBoostedProposalsCount(contributionReward.address); + const count1 = await genesisProtocol.getBoostedProposalsCount( + contributionReward.address, + dao.avatar.address + ); + assert(typeof count1 !== "undefined"); const proposalId = await createProposal(); @@ -521,7 +525,10 @@ describe("GenesisProtocol", () => { await stakeProposalVote(proposalId, BinaryVoteResult.Yes, amountStaked); - const count2 = await genesisProtocol.getBoostedProposalsCount(contributionReward.address); + const count2 = await genesisProtocol.getBoostedProposalsCount( + contributionReward.address, + dao.avatar.address); + assert(typeof count2 !== "undefined"); assert((count2.sub(count1)).eq(1)); @@ -557,10 +564,10 @@ describe("GenesisProtocol", () => { assert.equal(result, true); }); - it("can call getProposalOrganization", async () => { + it("can call getProposalCreator", async () => { const proposalId = await createProposal(); const result = await genesisProtocol.getProposalCreator(proposalId); - assert.equal(result, contributionReward.address); + assert.equal(result, dao.avatar.address); }); it("can call getWinningVote", async () => { @@ -611,6 +618,7 @@ describe("GenesisProtocol", () => { try { await genesisProtocol.propose({ numOfChoices: 13, + organizationAddress: helpers.SOME_ADDRESS, proposalParameters: gpParamsHash, proposerAddress: helpers.SOME_ADDRESS, }); diff --git a/test/helpers.ts b/test/helpers.ts index 408c9d956..b21575be0 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -285,9 +285,9 @@ export async function increaseTime(duration: number): Promise { } export async function waitForBlocks(blocks: number): Promise { - const currentBlock = await UtilsInternal.lastBlock(); + const currentBlock = await UtilsInternal.lastBlockNumber(); /* tslint:disable-next-line:no-empty */ - while ((await UtilsInternal.lastBlock()) - currentBlock < blocks) { } + while ((await UtilsInternal.lastBlockNumber()) - currentBlock < blocks) { } } export async function getDaoScheme( diff --git a/test/redeemer.ts b/test/redeemer.ts index d7e00db58..d59cdc56d 100644 --- a/test/redeemer.ts +++ b/test/redeemer.ts @@ -174,7 +174,7 @@ describe("Redeemer", () => { // assert(redeemable.voterTokenAmount.eq(stakeAmount.mul(.25)), `wrong voterTokenAmount`); assert.equal(web3.fromWei(redeemable.voterReputationAmount).toNumber(), 7); - const latestBlock = await UtilsInternal.lastBlock(); + const latestBlock = await UtilsInternal.lastBlockNumber(); const redeemed = (await redeemer.redeem({ avatarAddress: dao.avatar.address, diff --git a/test/tokens.ts b/test/tokens.ts index de4683206..d61f07641 100644 --- a/test/tokens.ts +++ b/test/tokens.ts @@ -36,7 +36,7 @@ describe("Tokens", () => { const amount = web3.toWei(1); - let currentBlock = await UtilsInternal.lastBlock(); + let currentBlock = await UtilsInternal.lastBlockNumber(); await genToken.approve({ amount, @@ -53,7 +53,7 @@ describe("Tokens", () => { assert.equal(approvalEvents[0].args.value.toString(), amount); assert.equal(approvalEvents[0].args.owner.toString(), accounts[1]); - currentBlock = await UtilsInternal.lastBlock(); + currentBlock = await UtilsInternal.lastBlockNumber(); const eventsReceived = new Array(); @@ -86,7 +86,7 @@ describe("Tokens", () => { it("can transfer", async () => { const amount = web3.toWei(1); - const currentBlock = await UtilsInternal.lastBlock(); + const currentBlock = await UtilsInternal.lastBlockNumber(); const eventsReceived = new Array(); @@ -118,7 +118,7 @@ describe("Tokens", () => { it("can mint", async () => { const amount = web3.toWei(1); - const currentBlock = await UtilsInternal.lastBlock(); + const currentBlock = await UtilsInternal.lastBlockNumber(); const eventsReceived = new Array(); @@ -149,7 +149,7 @@ describe("Tokens", () => { it("can burn", async () => { const amount = web3.toWei(1); - const currentBlock = await UtilsInternal.lastBlock(); + const currentBlock = await UtilsInternal.lastBlockNumber(); const eventsReceived = new Array(); @@ -181,7 +181,7 @@ describe("Tokens", () => { it("can increaseApproval", async () => { const amount = web3.toWei(1); - const currentBlock = await UtilsInternal.lastBlock(); + const currentBlock = await UtilsInternal.lastBlockNumber(); const currentAllowance = await mintableToken.allowance( { owner: accounts[1], @@ -242,7 +242,7 @@ describe("Tokens", () => { await helpers.increaseTime(1); - const currentBlock = await UtilsInternal.lastBlock(); + const currentBlock = await UtilsInternal.lastBlockNumber(); const currentAllowance = await mintableToken.allowance( { diff --git a/test/web3EventService.ts b/test/web3EventService.ts index f58bea8b5..b5eb6b7ff 100644 --- a/test/web3EventService.ts +++ b/test/web3EventService.ts @@ -35,7 +35,7 @@ describe("Web3EventService", () => { const token = await StandardTokenFactory.at(tokenAddress); assert.isOk(token); - const initialBlockNumber = await UtilsInternal.lastBlock(); + const initialBlockNumber = await UtilsInternal.lastBlockNumber(); let currentBlockNumber = initialBlockNumber; let eventBlockNumber = currentBlockNumber; @@ -64,7 +64,7 @@ describe("Web3EventService", () => { fetcher.get(async (error: Error, entitiesPromise: Promise>) => { const entities = await entitiesPromise; for (const entity of entities) { - currentBlockNumber = await UtilsInternal.lastBlock(); + currentBlockNumber = await UtilsInternal.lastBlockNumber(); eventBlockNumber = entity.blockNumber; } resolve(); @@ -81,7 +81,7 @@ describe("Web3EventService", () => { const token = await StandardTokenFactory.at(tokenAddress); assert.isOk(token); - const initialBlockNumber = await UtilsInternal.lastBlock(); + const initialBlockNumber = await UtilsInternal.lastBlockNumber(); let currentBlockNumber = initialBlockNumber; let eventBlockNumber = currentBlockNumber; @@ -99,7 +99,7 @@ describe("Web3EventService", () => { reject: () => void): Promise => { fetcher.watch(async (error: Error, entity: EntityType) => { - currentBlockNumber = await UtilsInternal.lastBlock(); + currentBlockNumber = await UtilsInternal.lastBlockNumber(); eventBlockNumber = entity.blockNumber; done = true; resolve(); @@ -139,7 +139,7 @@ describe("Web3EventService", () => { const token = await StandardTokenFactory.at(tokenAddress); assert.isOk(token); - const initialBlockNumber = await UtilsInternal.lastBlock(); + const initialBlockNumber = await UtilsInternal.lastBlockNumber(); let currentBlockNumber = initialBlockNumber; let eventBlockNumber = currentBlockNumber; let done = false; @@ -150,7 +150,7 @@ describe("Web3EventService", () => { resolve: () => void, reject: () => void): Promise => { fetcher.watch(async (error: Error, event: DecodedLogEntryEvent) => { - currentBlockNumber = await UtilsInternal.lastBlock(); + currentBlockNumber = await UtilsInternal.lastBlockNumber(); eventBlockNumber = event.blockNumber; done = true; resolve();