Skip to content
This repository was archived by the owner on Dec 21, 2021. It is now read-only.

Commit 567d1cb

Browse files
authored
Validate client's Ethereum address options (#223)
If user overrides some Ethereum address option in StreamrClientOptions, we validate that value already in the constructor. Using the option is therefore easier in the code, as we know that the value is always valid.
1 parent a37874f commit 567d1cb

File tree

7 files changed

+111
-28
lines changed

7 files changed

+111
-28
lines changed

package-lock.json

Lines changed: 38 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@
7474
"@types/debug": "^4.1.5",
7575
"@types/express": "^4.17.11",
7676
"@types/jest": "^26.0.20",
77+
"@types/lodash.get": "^4.4.6",
78+
"@types/lodash.has": "^4.5.6",
79+
"@types/lodash.set": "^4.3.6",
7780
"@types/lodash.uniqueid": "^4.0.6",
7881
"@types/node": "^14.14.31",
7982
"@types/node-fetch": "^2.5.8",
@@ -136,6 +139,9 @@
136139
"debug": "^4.3.2",
137140
"eventemitter3": "^4.0.7",
138141
"lodash.uniqueid": "^4.0.1",
142+
"lodash.has": "^4.0.1",
143+
"lodash.get": "^4.0.1",
144+
"lodash.set": "^4.0.1",
139145
"mem": "^8.0.0",
140146
"node-abort-controller": "^1.1.0",
141147
"node-fetch": "^2.6.1",

src/Config.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { getVersionString } from './utils'
1010
import { ConnectionInfo } from '@ethersproject/web'
1111
import { EthereumAddress, Todo } from './types'
1212
import { BytesLike } from '@ethersproject/bytes'
13+
import { isAddress } from '@ethersproject/address'
14+
import has from 'lodash.has'
15+
import get from 'lodash.get'
1316

1417
export type EthereumConfig = ExternalProvider|JsonRpcFetchFunc
1518

@@ -79,6 +82,17 @@ export type StreamrClientOptions = Partial<Omit<StrictStreamrClientOptions, 'dat
7982
const { ControlMessage } = ControlLayer
8083
const { StreamMessage } = MessageLayer
8184

85+
const validateOverridedEthereumAddresses = (opts: any, propertyPaths: string[]) => {
86+
for (const propertyPath of propertyPaths) {
87+
if (has(opts, propertyPath)) {
88+
const value = get(opts, propertyPath)
89+
if (!isAddress(value)) {
90+
throw new Error(`${propertyPath} is not a valid Ethereum address`)
91+
}
92+
}
93+
}
94+
}
95+
8296
/**
8397
* @category Important
8498
*/
@@ -131,6 +145,22 @@ export const STREAM_CLIENT_DEFAULTS: StrictStreamrClientOptions = {
131145

132146
/** @internal */
133147
export default function ClientConfig(opts: StreamrClientOptions = {}) {
148+
149+
// validate all Ethereum addresses which are required in StrictStreamrClientOptions: if user
150+
// overrides a setting, which has a default value, it must be a non-null valid Ethereum address
151+
// TODO could also validate
152+
// - other optional Ethereum address (if there will be some)
153+
// - other overriden options (e.g. regexp check that "restUrl" is a valid url)
154+
validateOverridedEthereumAddresses(opts, [
155+
'streamrNodeAddress',
156+
'tokenAddress',
157+
'tokenSidechainAddress',
158+
'dataUnion.factoryMainnetAddress',
159+
'dataUnion.factorySidechainAddress',
160+
'dataUnion.templateMainnetAddress',
161+
'dataUnion.templateSidechainAddress'
162+
])
163+
134164
const options: StrictStreamrClientOptions = {
135165
...STREAM_CLIENT_DEFAULTS,
136166
...opts,

src/StreamrClient.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -434,12 +434,8 @@ export class StreamrClient extends EventEmitter { // eslint-disable-line no-rede
434434
*/
435435
async getTokenBalance(address: EthereumAddress): Promise<BigNumber> {
436436
const { tokenAddress } = this.options
437-
if (!tokenAddress) {
438-
throw new Error('StreamrClient has no tokenAddress configuration.')
439-
}
440437
const addr = getAddress(address)
441438
const provider = this.ethereum.getMainnetProvider()
442-
443439
const token = new Contract(tokenAddress, balanceOfAbi, provider)
444440
return token.balanceOf(addr)
445441
}
@@ -449,12 +445,8 @@ export class StreamrClient extends EventEmitter { // eslint-disable-line no-rede
449445
*/
450446
async getSidechainTokenBalance(address: EthereumAddress): Promise<BigNumber> {
451447
const { tokenSidechainAddress } = this.options
452-
if (!tokenSidechainAddress) {
453-
throw new Error('StreamrClient has no sidechainTokenAddress configuration.')
454-
}
455448
const addr = getAddress(address)
456449
const provider = this.ethereum.getSidechainProvider()
457-
458450
const token = new Contract(tokenSidechainAddress, balanceOfAbi, provider)
459451
return token.balanceOf(addr)
460452
}

src/dataunion/Contracts.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ export class Contracts {
4747
}
4848

4949
getDataUnionMainnetAddress(dataUnionName: string, deployerAddress: EthereumAddress) {
50-
validateAddress('StreamrClient factoryMainnetAddress', this.factoryMainnetAddress)
51-
validateAddress('StreamrClient templateMainnetAddress', this.templateMainnetAddress)
5250
// This magic hex comes from https://github.com/streamr-dev/data-union-solidity/blob/master/contracts/CloneLib.sol#L19
5351
const codeHash = keccak256(`0x3d602d80600a3d3981f3363d3d373d3d3d363d73${this.templateMainnetAddress.slice(2)}5af43d82803e903d91602b57fd5bf3`)
5452
const salt = keccak256(defaultAbiCoder.encode(['string', 'address'], [dataUnionName, deployerAddress]))
@@ -62,8 +60,6 @@ export class Contracts {
6260
}
6361

6462
getDataUnionSidechainAddress(mainnetAddress: EthereumAddress) {
65-
validateAddress('StreamrClient factorySidechainAddress', this.factorySidechainAddress)
66-
validateAddress('StreamrClient templateSidechainAddress', this.templateSidechainAddress)
6763
// This magic hex comes from https://github.com/streamr-dev/data-union-solidity/blob/master/contracts/CloneLib.sol#L19
6864
const code = `0x3d602d80600a3d3981f3363d3d373d3d3d363d73${this.templateSidechainAddress.slice(2)}5af43d82803e903d91602b57fd5bf3`
6965
const codeHash = keccak256(code)
@@ -293,15 +289,13 @@ export class Contracts {
293289
throw new Error(`Mainnet data union "${duName}" contract ${duMainnetAddress} already exists!`)
294290
}
295291

296-
validateAddress('StreamrClient factoryMainnetAddress', this.factoryMainnetAddress)
297-
298292
if (await mainnetProvider.getCode(this.factoryMainnetAddress) === '0x') {
299293
throw new Error(
300294
`Data union factory contract not found at ${this.factoryMainnetAddress}, check StreamrClient.options.dataUnion.factoryMainnetAddress!`
301295
)
302296
}
303297

304-
const factoryMainnet = new Contract(this.factoryMainnetAddress!, factoryMainnetABI, mainnetWallet)
298+
const factoryMainnet = new Contract(this.factoryMainnetAddress, factoryMainnetABI, mainnetWallet)
305299
const ethersOptions: any = {}
306300
if (gasPrice) {
307301
ethersOptions.gasPrice = gasPrice

src/dataunion/DataUnion.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,7 @@ export class DataUnion {
491491
} else {
492492
// streamrNode needs to be joinPartAgent so that EE join with secret works (and join approvals from Marketplace UI)
493493
agentAddressList = [ownerAddress]
494-
if (client.options.streamrNodeAddress) {
495-
agentAddressList.push(getAddress(client.options.streamrNodeAddress))
496-
}
494+
agentAddressList.push(getAddress(client.options.streamrNodeAddress))
497495
}
498496

499497
const contract = await new Contracts(client).deployDataUnion({

test/unit/Config.test.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,49 @@
11
import { arrayify, BytesLike } from '@ethersproject/bytes'
22
import { StreamrClient } from '../../src/StreamrClient'
3+
import set from 'lodash.set'
34

4-
const createClient = (privateKey: BytesLike) => {
5-
return new StreamrClient({
6-
auth: {
7-
privateKey
5+
describe('Config', () => {
6+
7+
describe('validate ethereum addresses', () => {
8+
const createClient = (propertyPaths: string, value: string|undefined|null) => {
9+
const opts: any = {}
10+
set(opts, propertyPaths, value)
11+
return new StreamrClient(opts)
12+
}
13+
const propertyPaths: string[] = [
14+
'streamrNodeAddress',
15+
'tokenAddress',
16+
'tokenSidechainAddress',
17+
'dataUnion.factoryMainnetAddress',
18+
'dataUnion.factorySidechainAddress',
19+
'dataUnion.templateMainnetAddress',
20+
'dataUnion.templateSidechainAddress'
21+
]
22+
for (const propertyPath of propertyPaths) {
23+
it(propertyPath, () => {
24+
const errorMessage = `${propertyPath} is not a valid Ethereum address`
25+
expect(() => createClient(propertyPath, 'invalid-address')).toThrow(errorMessage)
26+
expect(() => createClient(propertyPath, undefined)).toThrow(errorMessage)
27+
expect(() => createClient(propertyPath, null)).toThrow(errorMessage)
28+
expect(() => createClient(propertyPath, '0x1234567890123456789012345678901234567890')).not.toThrow()
29+
})
830
}
931
})
10-
}
1132

12-
describe('Config', () => {
1333
describe('private key', () => {
34+
const createAuthenticatedClient = (privateKey: BytesLike) => {
35+
return new StreamrClient({
36+
auth: {
37+
privateKey
38+
}
39+
})
40+
}
1441
it('string', async () => {
15-
const client = createClient('0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF')
42+
const client = createAuthenticatedClient('0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF')
1643
expect(await client.getAddress()).toBe('0xFCAd0B19bB29D4674531d6f115237E16AfCE377c')
1744
})
1845
it('byteslike', async () => {
19-
const client = createClient(arrayify('0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'))
46+
const client = createAuthenticatedClient(arrayify('0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'))
2047
expect(await client.getAddress()).toBe('0xFCAd0B19bB29D4674531d6f115237E16AfCE377c')
2148
})
2249
})

0 commit comments

Comments
 (0)