Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Marketplace: add more checks #899

Merged
merged 9 commits into from
Apr 20, 2019
56 changes: 56 additions & 0 deletions systemservices/marketplace/src/contracts/isDomainName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@

// Source: https://golang.org/src/net/dnsclient.go
// IsDomainName checks if a string is a presentation-format domain name
// (currently restricted to hostname-compatible "preferred name" LDH labels and
// SRV-like "underscore labels"; see golang.org/issue/12421).
export default (s: string): boolean => {
// See RFC 1035, RFC 3696.
// Presentation format has dots before every label except the first, and the
// terminal empty label is optional here because we assume fully-qualified
// (absolute) input. We must therefore reserve space for the first and last
// labels' length octets in wire format, where they are necessary and the
// maximum total length is 255.
// So our _effective_ maximum is 253, but 254 is not rejected if the last
// character is a dot.
const l = s.length
if (l === 0 || l > 254 || l === 254 && s[l-1] != '.') {
return false
}

let last = '.'
let ok = false // Ok once we've seen a letter.
let partlen = 0
for (let i = 0; i < s.length; i++) {
const c = s[i]
if ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c === '_') {
ok = true
partlen++
} else if ('0' <= c && c <= '9') {
// fine
partlen++
} else if (c === '-') {
// Byte before dash cannot be dot.
if (last === '.') {
return false
}
partlen++
} else if (c === '.') {
// Byte before dot cannot be dot, dash.
if (last === '.' || last === '-') {
return false
}
if (partlen > 63 || partlen === 0) {
return false
}
partlen = 0
} else {
return false
}
last = c
}
if (last === '-' || partlen > 63) {
return false
}

return ok
}
4 changes: 2 additions & 2 deletions systemservices/marketplace/src/contracts/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ BigNumber.config({ EXPONENTIAL_AT: 100 })

const hexToString = Web3.utils.hexToString
const stringToHex = Web3.utils.stringToHex
const sha3 = Web3.utils.sha3
const keccak256 = Web3.utils.soliditySha3

const toUnit = (x: string|BigNumber): string => {
const n = new BigNumber(x).times(1e18)
Expand Down Expand Up @@ -88,7 +88,7 @@ const findInAbi = (abi: ABIDefinition[], eventName: string): ABIDefinition => {
export {
hexToString,
stringToHex,
sha3,
keccak256,
toUnit,
fromUnit,
parseTimestamp,
Expand Down
25 changes: 18 additions & 7 deletions systemservices/marketplace/src/contracts/version.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import BigNumber from "bignumber.js"
import { Marketplace } from "./Marketplace"
import { Version } from "../types/version";
import { hexToString, parseTimestamp, stringToHex, hexToHash, hashToHex } from "./utils";
import { hexToString, parseTimestamp, stringToHex, hexToHash, hashToHex, keccak256 } from "./utils";
import { getManifest } from "./manifest";
import { requireServiceExist } from "./service";
import * as assert from "assert";

const getServiceVersions = async (contract: Marketplace, sid: string): Promise<Version[]> => {
await requireServiceExist(contract, sid)
Expand All @@ -21,11 +22,8 @@ const getServiceVersionWithIndex = async (contract: Marketplace, sid: string, ve
}

const getServiceVersion = async (contract: Marketplace, versionHash: string): Promise<Version> => {
const versionHashHex = hashToHex(versionHash)
if (!await contract.methods.isServiceVersionExist(versionHashHex).call()) {
throw new Error(`version ${versionHash} does not exist`)
}
const version = await contract.methods.serviceVersion(versionHashHex).call()
assert.ok(await isVersionExist(contract, versionHash), `version does not exist`)
const version = await contract.methods.serviceVersion(hashToHex(versionHash)).call()
let manifestData = null
try {
manifestData = await getManifest(hexToString(version.manifestProtocol), hexToString(version.manifest))
Expand All @@ -42,4 +40,17 @@ const getServiceVersion = async (contract: Marketplace, versionHash: string): Pr
}
}

export { getServiceVersions, getServiceVersion }
const isVersionExist = async (contract: Marketplace, versionHash: string): Promise<boolean> => {
return contract.methods.isServiceVersionExist(hashToHex(versionHash)).call()
}

const computeVersionHash = (from: string, sid: string, manifest: string, manifestProtocol: string) => {
return hexToHash(keccak256(from, stringToHex(sid), stringToHex(manifest), stringToHex(manifestProtocol)))
}

export {
getServiceVersions,
getServiceVersion,
isVersionExist,
computeVersionHash,
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { stringToHex, CreateTransaction } from "../contracts/utils";
import { Manifest } from "../types/manifest";
import { getService, isServiceExist } from "../contracts/service";
import * as assert from "assert";
import { computeVersionHash, isVersionExist } from "../contracts/version";
import isDomainName from "../contracts/isDomainName";

const manifestProtocol = 'ipfs'

export default (
marketplace: Marketplace,
Expand All @@ -13,7 +17,7 @@ export default (
// check inputs
const sid = inputs.service.definition.sid
assert.ok(sid.length >= 1 && sid.length <= 63, 'sid\'s length must be 1 at min and 63 at max') // See Core service validation (https://github.com/mesg-foundation/core)
// TODO: add check on SID is domain name (see go implementation)
assert.ok(isDomainName(sid), 'sid must respect domain-style notation, eg author.name')

if(await isServiceExist(marketplace, sid)) {
// get service
Expand All @@ -23,19 +27,21 @@ export default (
assert.strictEqual(inputs.from.toLowerCase(), service.owner.toLowerCase(), `service's owner is different`)
}

// TODO: check if version already exist on marketplace

// upload manifest
const manifestHash = await uploadManifest({
version: '1',
service: inputs.service
})

// check if version already exist on marketplace
const versionHash = computeVersionHash(inputs.from, sid, manifestHash, manifestProtocol)
assert.ok(!(await isVersionExist(marketplace, versionHash)), `service version already exist`)

// create transaction
const transactionData = marketplace.methods.publishServiceVersion(
stringToHex(sid),
stringToHex(manifestHash),
stringToHex('ipfs')
stringToHex(manifestProtocol)
).encodeABI()
return outputs.success(await createTransaction(marketplace, inputs, transactionData))
}
Expand Down
6 changes: 3 additions & 3 deletions systemservices/marketplace/src/tasks/preparePurchase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ export default (
assert.notStrictEqual(inputs.from.toLowerCase(), service.owner.toLowerCase(), `service's owner cannot purchase its own service`)

// get offer data
const offerIndex = new BigNumber(inputs.offerIndex).toNumber()
assert.ok(offerIndex >= 0 && offerIndex < service.offers.length, 'offer index is out of range')
const offer = service.offers[offerIndex]
const offerIndex = new BigNumber(inputs.offerIndex)
assert.ok(offerIndex.isInteger() && offerIndex.isGreaterThanOrEqualTo(0) && offerIndex.isLessThan(service.offers.length), 'offer index is out of range')
const offer = service.offers[offerIndex.toNumber()]

// check if offer is active
assert.ok(offer.active, 'offer is not active')
Expand Down