From 43fe8634d246be833e7422bb5e2b895da0c73f56 Mon Sep 17 00:00:00 2001 From: Sebastian Gerske Date: Thu, 22 Nov 2018 09:22:27 +0100 Subject: [PATCH] fix lockPayment --- src/keeper/contracts/ServiceAgreement.ts | 7 +- src/ocean/Ocean.ts | 9 +- src/ocean/ServiceAgreements/Condition.ts | 2 + src/ocean/ServiceAgreements/Method.ts | 3 + src/ocean/ServiceAgreements/Parameter.ts | 4 + .../ServiceAgreements/ServiceAgreement.ts | 82 ++++++++------ .../ServiceAgreementTemplate.ts | 5 +- .../ServiceAgreements/Templates/Access.ts | 40 +++++++ test/ocean/ServiceAgreement.test.ts | 103 ++++++++++-------- 9 files changed, 164 insertions(+), 91 deletions(-) create mode 100644 src/ocean/ServiceAgreements/Parameter.ts diff --git a/src/keeper/contracts/ServiceAgreement.ts b/src/keeper/contracts/ServiceAgreement.ts index ecfcf4c..75438c8 100644 --- a/src/keeper/contracts/ServiceAgreement.ts +++ b/src/keeper/contracts/ServiceAgreement.ts @@ -10,8 +10,11 @@ export default class ServiceAgreement extends ContractBase { return serviceAgreement } - public async setupAgreementTemplate(templateId: string, methodReflections: MethodReflection[], - dependencyMatrix: number[], name: any, fulfillmentIndices: number[], + public async setupAgreementTemplate(templateId: string, + methodReflections: MethodReflection[], + dependencyMatrix: number[], + name: any, + fulfillmentIndices: number[], fulfillmentOperator: number, ownerAddress: string) : Promise { diff --git a/src/ocean/Ocean.ts b/src/ocean/Ocean.ts index cde057f..4784da7 100644 --- a/src/ocean/Ocean.ts +++ b/src/ocean/Ocean.ts @@ -13,7 +13,6 @@ import Service from "../ddo/Service" import Keeper from "../keeper/Keeper" import Web3Provider from "../keeper/Web3Provider" import Config from "../models/Config" -import InputType from "../models/InputType" import ValueType from "../models/ValueType" import SecretStoreProvider from "../secretstore/SecretStoreProvider" import Logger from "../utils/Logger" @@ -107,11 +106,11 @@ export default class Ocean { return null } - const parameters: Parameter[] = condition.methodReflection.inputs.map((input: InputType) => { + const parameters: Parameter[] = condition.parameters.map((parameter: Parameter) => { return { - name: input.name, - type: input.type, - value: mapParameterValueToName(input.name), + name: parameter.name, + type: parameter.type, + value: mapParameterValueToName(parameter.name), } as Parameter }) diff --git a/src/ocean/ServiceAgreements/Condition.ts b/src/ocean/ServiceAgreements/Condition.ts index d43996f..b8db3d6 100644 --- a/src/ocean/ServiceAgreements/Condition.ts +++ b/src/ocean/ServiceAgreements/Condition.ts @@ -1,4 +1,5 @@ import MethodReflection from "../../models/MethodReflection" +import Parameter from "./Parameter" export default class Condition { public methodReflection: MethodReflection @@ -7,4 +8,5 @@ export default class Condition { public dependencyTimeoutFlags: number[] public isTerminalCondition: boolean public timeout: number + public parameters: Parameter[] } diff --git a/src/ocean/ServiceAgreements/Method.ts b/src/ocean/ServiceAgreements/Method.ts index 4d8dbe6..1e31f46 100644 --- a/src/ocean/ServiceAgreements/Method.ts +++ b/src/ocean/ServiceAgreements/Method.ts @@ -1,8 +1,11 @@ +import Parameter from "./Parameter" + export default class Method { public name: string public contractName: string public methodName: string public timeout: number + public parameters: Parameter[] public dependencies: string[] public dependencyTimeoutFlags: number[] public isTerminalCondition: boolean diff --git a/src/ocean/ServiceAgreements/Parameter.ts b/src/ocean/ServiceAgreements/Parameter.ts new file mode 100644 index 0000000..ce82667 --- /dev/null +++ b/src/ocean/ServiceAgreements/Parameter.ts @@ -0,0 +1,4 @@ +export default class Parameter { + public name: string + public type: string +} diff --git a/src/ocean/ServiceAgreements/ServiceAgreement.ts b/src/ocean/ServiceAgreements/ServiceAgreement.ts index e296ec5..2520f7e 100644 --- a/src/ocean/ServiceAgreements/ServiceAgreement.ts +++ b/src/ocean/ServiceAgreements/ServiceAgreement.ts @@ -19,18 +19,12 @@ export default class ServiceAgreement extends OceanBase { // Logger.log("signing SA", serviceAgreementId) const service: Service = ddo.findServiceById(serviceDefinitionId) - const values: ValuePair[] = ServiceAgreement.getValuesFromService(service, serviceAgreementId) - const valueHashes = ServiceAgreement.createValueHashes(values) + const values: ValuePair[][] = ServiceAgreement.getValuesFromService(service, serviceAgreementId) + const valueHashes: string[] = ServiceAgreement.createValueHashes(values) const timeoutValues: number[] = ServiceAgreement.getTimeoutValuesFromService(service) - const serviceAgreementHashSignature = await ServiceAgreement - .createSAHashSignature( - service, - serviceAgreementId, - values, - valueHashes, - timeoutValues, - consumer) + const serviceAgreementHashSignature = await ServiceAgreement.createSAHashSignature(service, serviceAgreementId, + valueHashes, timeoutValues, consumer) return serviceAgreementHashSignature } @@ -46,8 +40,8 @@ export default class ServiceAgreement extends OceanBase { // Logger.log("executing SA", serviceAgreementId) const service: Service = ddo.findServiceById(serviceDefinitionId) - const values: ValuePair[] = ServiceAgreement.getValuesFromService(service, serviceAgreementId) - const valueHashes = ServiceAgreement.createValueHashes(values) + const values: ValuePair[][] = ServiceAgreement.getValuesFromService(service, serviceAgreementId) + const valueHashes: string[] = ServiceAgreement.createValueHashes(values) const timeoutValues: number[] = ServiceAgreement.getTimeoutValuesFromService(service) // todo get consumer from ddo @@ -60,11 +54,9 @@ export default class ServiceAgreement extends OceanBase { private static async createSAHashSignature(service: Service, serviceAgreementId: string, - values: ValuePair[], valueHashes: string[], timeoutValues: number[], - consumer: Account): - Promise { + consumer: Account): Promise { if (!service.templateId) { throw new Error("TemplateId not found in ddo.") @@ -74,13 +66,16 @@ export default class ServiceAgreement extends OceanBase { return condition.conditionKey }) - const serviceAgreementHash = ServiceAgreement - .hashServiceAgreement( - service.templateId, - serviceAgreementId, - conditionKeys, - valueHashes, - timeoutValues) + if (conditionKeys.length !== valueHashes.length) { + throw new Error("Hashing SA failed!") + } + + const serviceAgreementHash = ServiceAgreement.hashServiceAgreement( + service.templateId, + serviceAgreementId, + conditionKeys, + valueHashes, + timeoutValues) const serviceAgreementHashSignature = await Web3Provider .getWeb3().eth.sign(serviceAgreementHash, consumer.getId()) @@ -130,18 +125,31 @@ export default class ServiceAgreement extends OceanBase { ) } - private static createValueHashes(valuePairs: ValuePair[]): any[] { - return valuePairs.map((valuePair) => { - return ServiceAgreement.hashSingleValue(valuePair) + private static createValueHashes(parameterValuePairs: ValuePair[][]): string[] { + + const hashes: string[] = [] + parameterValuePairs.map((valuePairs: ValuePair[]) => { + + hashes.push(ServiceAgreement.hashValuePairArray(valuePairs)) }) + + return hashes } - private static hashSingleValue(data: ValuePair): string { + private static hashValuePairArray(valuePairs: ValuePair[]): string { + let hash: string try { - return Web3Provider.getWeb3().utils.soliditySha3(data).toString("hex") + hash = Web3Provider.getWeb3().utils.soliditySha3(...valuePairs).toString("hex") } catch (err) { - Logger.error(`Hashing of ${JSON.stringify(data, null, 2)} failed.`) + Logger.error(`Hashing of ${JSON.stringify(valuePairs, null, 2)} failed.`) + throw err } + + if (!hash) { + throw new Error("hashValuePairArray failed to create hash.") + } + + return hash } private static hashServiceAgreement(serviceAgreementTemplateId: string, @@ -168,20 +176,22 @@ export default class ServiceAgreement extends OceanBase { return timeoutValues } - private static getValuesFromService(service: Service, serviceAgreementId: string): ValuePair[] { + private static getValuesFromService(service: Service, serviceAgreementId: string): ValuePair[][] { - const values: ValuePair[] = [] + const values: ValuePair[][] = [] - service.conditions.forEach((condition) => { + service.conditions.forEach((condition, i) => { + const contionValues: ValuePair[] = [] condition.parameters.forEach((parameter) => { - values.push({ + + contionValues.push({ type: parameter.type, value: parameter.name === "serviceId" ? "0x" + serviceAgreementId : parameter.value, } as ValuePair) }) - }) - // Logger.log("Values", JSON.stringify(values, null, 2)) + values[i] = contionValues + }) return values } @@ -198,11 +208,11 @@ export default class ServiceAgreement extends OceanBase { public async lockPayment(assetId: string, price: number, consumer: Account): Promise { const {paymentConditions} = await Keeper.getInstance() - const lockPaymentRceipt = + const lockPaymentReceipt = await paymentConditions.lockPayment(this.getId(), assetId, price, consumer.getId()) - return lockPaymentRceipt.status + return lockPaymentReceipt.status } public async grantAccess(assetId: string, documentId: string): Promise { diff --git a/src/ocean/ServiceAgreements/ServiceAgreementTemplate.ts b/src/ocean/ServiceAgreements/ServiceAgreementTemplate.ts index a51c64e..c111f2d 100644 --- a/src/ocean/ServiceAgreements/ServiceAgreementTemplate.ts +++ b/src/ocean/ServiceAgreements/ServiceAgreementTemplate.ts @@ -12,8 +12,8 @@ import TemplateBase from "./Templates/TemplateBase" export default class ServiceAgreementTemplate extends OceanBase { - private static generateConditionsKey(serviceAgreementTemplateId: string, methodReflection: MethodReflection) - : string { + private static generateConditionsKey(serviceAgreementTemplateId: string, + methodReflection: MethodReflection): string { const values = [ {type: "bytes32", value: serviceAgreementTemplateId} as ValuePair, {type: "address", value: methodReflection.address} as ValuePair, @@ -106,6 +106,7 @@ export default class ServiceAgreementTemplate extends OceanBase { return { methodReflection, timeout: method.timeout, + parameters: method.parameters, dependencies: method.dependencies, dependencyTimeoutFlags: method.dependencyTimeoutFlags, isTerminalCondition: method.isTerminalCondition, diff --git a/src/ocean/ServiceAgreements/Templates/Access.ts b/src/ocean/ServiceAgreements/Templates/Access.ts index 4fd13f5..8c41126 100644 --- a/src/ocean/ServiceAgreements/Templates/Access.ts +++ b/src/ocean/ServiceAgreements/Templates/Access.ts @@ -11,6 +11,16 @@ export default class Access extends TemplateBase { contractName: "PaymentConditions", methodName: "lockPayment", timeout: 0, + parameters: [ + { + name: "assetId", + type: "bytes32", + }, + { + name: "price", + type: "uint256", + }, + ], dependencies: [], dependencyTimeoutFlags: [], isTerminalCondition: false, @@ -20,6 +30,16 @@ export default class Access extends TemplateBase { contractName: "AccessConditions", methodName: "grantAccess", timeout: 10, + parameters: [ + { + name: "assetId", + type: "bytes32", + }, + { + name: "documentKeyId", + type: "bytes32", + }, + ], dependencies: ["lockPayment"], dependencyTimeoutFlags: [0], isTerminalCondition: false, @@ -29,6 +49,16 @@ export default class Access extends TemplateBase { contractName: "PaymentConditions", methodName: "releasePayment", timeout: 10, + parameters: [ + { + name: "assetId", + type: "bytes32", + }, + { + name: "price", + type: "uint256", + }, + ], dependencies: ["grantAccess"], dependencyTimeoutFlags: [0], isTerminalCondition: true, @@ -38,6 +68,16 @@ export default class Access extends TemplateBase { contractName: "PaymentConditions", methodName: "refundPayment", timeout: 10, + parameters: [ + { + name: "assetId", + type: "bytes32", + }, + { + name: "price", + type: "uint256", + }, + ], dependencies: ["lockPayment", "grantAccess"], dependencyTimeoutFlags: [0, 1], isTerminalCondition: true, diff --git a/test/ocean/ServiceAgreement.test.ts b/test/ocean/ServiceAgreement.test.ts index 8cbcdac..c11d35a 100644 --- a/test/ocean/ServiceAgreement.test.ts +++ b/test/ocean/ServiceAgreement.test.ts @@ -7,7 +7,6 @@ import EventHandlers from "../../src/ddo/EventHandlers" import MetaData from "../../src/ddo/MetaData" import Parameter from "../../src/ddo/Parameter" import Service from "../../src/ddo/Service" -import InputType from "../../src/models/InputType" import Account from "../../src/ocean/Account" import IdGenerator from "../../src/ocean/IdGenerator" import Ocean from "../../src/ocean/Ocean" @@ -15,6 +14,7 @@ import Condition from "../../src/ocean/ServiceAgreements/Condition" import ServiceAgreement from "../../src/ocean/ServiceAgreements/ServiceAgreement" import ServiceAgreementTemplate from "../../src/ocean/ServiceAgreements/ServiceAgreementTemplate" import Access from "../../src/ocean/ServiceAgreements/Templates/Access" +import Logger from "../../src/utils/Logger" import WebServiceConnectorProvider from "../../src/utils/WebServiceConnectorProvider" import config from "../config" import TestContractHandler from "../keeper/TestContractHandler" @@ -49,58 +49,60 @@ describe("ServiceAgreement", () => { const conditions: Condition[] = await serviceAgreementTemplate.getConditions() // create ddo conditions out of the keys - const ddoConditions: DDOCondition[] = conditions.map((condition, index): DDOCondition => { + const ddoConditions: DDOCondition[] = conditions + .map((condition: Condition, index): DDOCondition => { - const events: Event[] = [ - { - name: "PaymentReleased", - actorType: [ - "consumer", - ], - handlers: { - moduleName: "serviceAgreement", - functionName: "fulfillAgreement", - version: "0.1", - } as EventHandlers, - } as Event, - ] + const events: Event[] = [ + { + name: "PaymentReleased", + actorType: [ + "consumer", + ], + handlers: { + moduleName: "serviceAgreement", + functionName: "fulfillAgreement", + version: "0.1", + } as EventHandlers, + } as Event, + ] - const mapParameterValueToName = (name) => { + const mapParameterValueToName = (name) => { - switch (name) { - case "price": - return metadata.base.price - case "assetId": - return "0x" + assetId - case "documentKeyId": - return "0x1234" + switch (name) { + case "price": + return metadata.base.price + case "assetId": + return "0x" + assetId + case "documentKeyId": + return "0x1234" + } + + return null } - return null - } + const parameters: Parameter[] = condition.parameters + .map((parameter: Parameter) => { + return { + name: parameter.name, + type: parameter.type, + value: mapParameterValueToName(parameter.name), + } as Parameter + }) - const parameters: Parameter[] = condition.methodReflection.inputs.map((input: InputType) => { return { - name: input.name, - type: input.type, - value: mapParameterValueToName(input.name), - } as Parameter + contractName: condition.methodReflection.contractName, + methodName: condition.methodReflection.methodName, + timeout: condition.timeout, + index, + conditionKey: condition.condtionKey, + parameters, + events, + dependencies: condition.dependencies, + dependencyTimeoutFlags: condition.dependencyTimeoutFlags, + isTerminalCondition: condition.isTerminalCondition, + } as DDOCondition }) - return { - contractName: condition.methodReflection.contractName, - methodName: condition.methodReflection.methodName, - timeout: condition.timeout, - index, - conditionKey: condition.condtionKey, - parameters, - events, - dependencies: condition.dependencies, - dependencyTimeoutFlags: condition.dependencyTimeoutFlags, - isTerminalCondition: condition.isTerminalCondition, - } as DDOCondition - }) - accessService = { type: "Access", serviceDefinitionId: IdGenerator.generateId(), @@ -185,7 +187,7 @@ describe("ServiceAgreement", () => { }) describe("#lockPayment()", () => { - xit("should lock the payment in that service agreement", async () => { + it("should lock the payment in that service agreement", async () => { const id: string = IdGenerator.generateId() const did: string = `did:op:${id}` @@ -204,6 +206,11 @@ describe("ServiceAgreement", () => { serviceAgreementId, serviceAgreementSignature, consumerAccount, publisherAccount) assert(serviceAgreement) + // get funds + await consumerAccount.requestTokens(metaDataService.metadata.base.price) + + Logger.log(await consumerAccount.getBalance()) + const paid: boolean = await serviceAgreement.lockPayment(assetId, metaDataService.metadata.base.price, consumerAccount) assert(paid) @@ -230,7 +237,11 @@ describe("ServiceAgreement", () => { serviceAgreementId, serviceAgreementSignature, consumerAccount, publisherAccount) assert(serviceAgreement) - const paid: boolean = await serviceAgreement.lockPayment(assetId, 10, consumerAccount) + // get funds + await consumerAccount.requestTokens(metaDataService.metadata.base.price) + + const paid: boolean = await serviceAgreement.lockPayment(assetId, metaDataService.metadata.base.price, + consumerAccount) assert(paid) const accessGranted: boolean = await serviceAgreement.grantAccess(assetId, IdGenerator.generateId())