import { TemplateStoreManager, AgreementStoreManager, ConditionStoreManager } from '../managers' import DIDRegistry from '../DIDRegistry' import { LockRewardCondition } from '../conditions/LockRewardCondition' import { AccessSecretStoreCondition } from '../conditions/AccessSecretStoreCondition' import { EscrowReward } from '../conditions/EscrowReward' import { DDO } from '../../../ddo/DDO' import { generateId, LoggerInstance, Logger, zeroX } from '../../../utils' import { ComputeExecutionCondition, Condition, ConditionState, conditionStateNames } from '../conditions' import { ServiceType } from '../../../ddo/Service' export interface Conditions { lockRewardCondition: LockRewardCondition accessSecretStoreCondition?: AccessSecretStoreCondition computeExecutionCondition?: ComputeExecutionCondition escrowReward: EscrowReward } export interface AgreementConditionsStatus { [condition: string]: { condition: string contractName: string state: ConditionState blocked: boolean blockedBy: string[] } } export class AgreementTemplateBase { public templateName: string public templateManager: TemplateStoreManager public agreementStoreManager: AgreementStoreManager public didRegistry: DIDRegistry public conditions: Conditions private logger: Logger public constructor( templateManager: TemplateStoreManager, agreementStoreManager: AgreementStoreManager, didRegistry: DIDRegistry, conditions: Conditions ) { this.templateManager = templateManager this.agreementStoreManager = agreementStoreManager this.didRegistry = didRegistry this.conditions = conditions this.logger = LoggerInstance this.templateName = 'invalid' } public async createAgreementFromDDO( agreementId: string, ddo: DDO, serviceType: ServiceType, consumer: string, provider: string, from?: string ) { return !!(await this.createFullAgreement( ddo.shortId(), ddo.findServiceByType(serviceType).attributes.main.price, consumer, provider, from, agreementId )) } public async getConditionIdsFromDDO( agreementId: string, ddo: DDO, consumer: string, from?: string ) { return this.createFullAgreementData( agreementId, ddo.shortId(), ddo.findServiceByType('metadata').attributes.main.price, consumer ) } public getName() { return this.templateName } public getId() { return this.templateManager.generateId(this.getName()) } /** * Create a agreement using EscrowAccessSecretStoreTemplate using only the most important information. * @param {string} did Asset DID. * @param {number} amount Asset price. * @param {string} consumer ethereum address of consumer. * @param {string} provider ethereum address of service provider (brizo address) * @param {string} from Consumer address. * @param {string} agreementId bytes32 agreement id. * * @return {Promise} Agreement ID. */ public async createFullAgreement( did: string, amount: number | string, consumer: string, provider: string, from?: string, agreementId: string = generateId() ): Promise { const conditionIds = await this.createFullAgreementData( agreementId, did, amount, consumer ) const timeouts = [0, 0, 0] const timelocks = [0, 0, 0] await this.agreementStoreManager.createAgreement( agreementId, did, this.getId(), conditionIds, timelocks, timeouts, [consumer, provider], from ) return zeroX(agreementId) } protected async createFullAgreementData( agreementId: string, did: string, amount: number | string, consumer: string ): Promise { return null } /** * Conditions address list. * @return {Promise} Conditions address. */ public async getConditionTypes(): Promise { return this.templateManager.getConditionTypes(this.getId()) } /** * List of condition contracts. * @return {Promise} Conditions contracts. */ public async getConditions(): Promise { return this.templateManager.getConditions(await this.getConditionTypes()) } public async getServiceAgreementTemplate() { return null } public async getServiceAgreementTemplateConditions() { const serviceAgreementTemplate = await this.getServiceAgreementTemplate() return serviceAgreementTemplate.conditions } public async getServiceAgreementTemplateConditionByRef(ref: string) { const name = (await this.getServiceAgreementTemplateConditions()).find( ({ name: conditionRef }) => conditionRef === ref ).contractName return (await this.getConditions()).find( (condition) => condition.contractName === name ) } public async getServiceAgreementTemplateDependencies() { const serviceAgreementTemplate = await this.getServiceAgreementTemplate() return serviceAgreementTemplate.conditionDependency } /** * Returns the status of the conditions. * @param {string} agreementId Agreement ID. * @param {ConditionStoreManager} conditionStoreManager * @return {Promise} Conditions status. */ public async getAgreementStatus( agreementId: string, conditionStoreManager: ConditionStoreManager ): Promise { const dependencies = await this.getServiceAgreementTemplateDependencies() const { conditionIds } = await this.agreementStoreManager.getAgreement( agreementId ) if (!conditionIds.length) { // this.logger.error(`Agreement not creeated yet: "${agreementId}"`) return false } const conditionIdByCondition = (await this.getConditions()).reduce( (acc, { contractName }, i) => ({ ...acc, [contractName]: conditionIds[i] }), {} ) const statesPromises = Object.keys(dependencies).map(async (ref, i) => { const { contractName } = await this.getServiceAgreementTemplateConditionByRef( ref ) return { ref, contractName, state: ( await conditionStoreManager.getCondition( conditionIdByCondition[contractName] ) ).state } }) const states = await Promise.all(statesPromises) return states.reduce((acc, { contractName, ref, state }) => { const blockers = dependencies[ref] .map((dependency) => states.find((_) => _.ref === dependency)) .filter((condition) => condition.state !== ConditionState.Fulfilled) return { ...acc, [ref]: { condition: ref, contractName, state, blocked: !!blockers.length, blockedBy: blockers.map((_) => _.ref) } } }, {}) } /** * Prints the agreement status. * @param {string} agreementId Agreement ID. * @param {ConditionStoreManager} conditionStoreManager */ public async printAgreementStatus( agreementId: string, conditionStoreManager: ConditionStoreManager ) { const status = await this.getAgreementStatus(agreementId, conditionStoreManager) this.logger.bypass('-'.repeat(80)) this.logger.bypass('Template:', this.templateName) this.logger.bypass('Agreement ID:', agreementId) this.logger.bypass('-'.repeat(40)) if (!status) { this.logger.bypass('Agreement not created yet!') } Object.values(status || []).forEach( ({ condition, contractName, state, blocked, blockedBy }, i) => { if (i) { this.logger.bypass('-'.repeat(20)) } this.logger.bypass(`${condition} (${contractName})`) this.logger.bypass(' Status:', state, `(${conditionStateNames[state]})`) if (blocked) { this.logger.bypass(' Blocked by:', blockedBy) } } ) this.logger.bypass('-'.repeat(80)) } /** * Generates and returns the agreement creation event. * @param {string} agreementId Agreement ID. * @return {Event} Agreement created event. */ public getAgreementCreatedEvent(agreementId: string) { return this.agreementStoreManager.getAgreementCreatedEvent(agreementId) } /** * Return actor type ids for this template (specified by subclass) * */ public async getActorTypeIds() { const { actorTypeIds } = await this.templateManager.getTemplate(this.getId()) return actorTypeIds } /** * Return actor types (strings) for this template (specified by subclass) * */ public async getActorTypes() { const actorTypeIds = await this.getActorTypeIds() return actorTypeIds.map((typeId) => this.templateManager.getActorTypeValue(typeId) ) } }